diff --git a/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@cn/DEFAULT b/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@cn/DEFAULT index f1f6409560..0d444f4ac4 100644 --- a/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@cn/DEFAULT +++ b/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@cn/DEFAULT @@ -2,7 +2,7 @@ 策略名称: {{.name}} 触发时间: {{.start_time}} 报警级别: {{.level}} -触发条件: {{.description}} +触发条件: {{.description | unescaped}} 资源数量:{{len .matches}} 资源名称:{{.resource_name}} diff --git a/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@en/DEFAULT b/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@en/DEFAULT index 0ee07a2ae4..0cfd74b27b 100644 --- a/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@en/DEFAULT +++ b/build/monitor/root/opt/yunion/share/notify_templates/alerter/content@en/DEFAULT @@ -2,7 +2,7 @@ AlertName: {{.name}} Time: {{.start_time}} Level: {{.level}} -TriggerCondition: {{html .description}} +TriggerCondition: {{.description | unescaped}} ResourceCount: {{len .matches}} ResourceName: {{.resource_name}} diff --git a/cmd/apsaracli/main.go b/cmd/apsaracli/main.go new file mode 100644 index 0000000000..f0d7a0113c --- /dev/null +++ b/cmd/apsaracli/main.go @@ -0,0 +1,144 @@ +// 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 main + +import ( + "fmt" + "os" + + "yunion.io/x/structarg" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/apsara" + _ "yunion.io/x/onecloud/pkg/multicloud/apsara/shell" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type BaseOptions struct { + Debug bool `help:"debug mode"` + Help bool `help:"Show help"` + AccessKey string `help:"Access key" default:"$APSARA_ACCESS_KEY" metavar:"APSARA_ACCESS_KEY"` + Secret string `help:"Secret" default:"$APSARA_SECRET" metavar:"APSARA_SECRET"` + cloudprovider.SApsaraEndpoints `help:"Endpoints for Apsara"` + RegionId string `help:"RegionId" default:"$APSARA_REGION" metavar:"APSARA_REGION"` + SUBCOMMAND string `help:"apsaracli subcommand" subcommand:"true"` +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parse, e := structarg.NewArgumentParser(&BaseOptions{}, + "apsaracli", + "Command-line interface to apsara API.", + `See "apsaracli 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 *BaseOptions) (*apsara.SRegion, error) { + if len(options.AccessKey) == 0 { + return nil, fmt.Errorf("Missing accessKey") + } + + if len(options.Secret) == 0 { + return nil, fmt.Errorf("Missing secret") + } + + cli, err := apsara.NewApsaraClient( + apsara.NewApsaraClientConfig( + options.AccessKey, + options.Secret, + options.SApsaraEndpoints, + ).Debug(options.Debug), + ) + if err != nil { + return nil, err + } + + region := cli.GetRegion(options.RegionId) + if region == nil { + return nil, fmt.Errorf("No such region %s", options.RegionId) + } + + return region, nil +} + +func main() { + parser, e := getSubcommandParser() + if e != nil { + showErrorAndExit(e) + } + e = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + return + } + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if e != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(e) + } + suboptions := subparser.Options() + if options.SUBCOMMAND == "help" { + e = subcmd.Invoke(suboptions) + } else { + var region *apsara.SRegion + region, e = newClient(options) + if e != nil { + showErrorAndExit(e) + } + e = subcmd.Invoke(region, suboptions) + } + if e != nil { + showErrorAndExit(e) + } +} diff --git a/cmd/climc/shell/compute/buckets.go b/cmd/climc/shell/compute/buckets.go index ab0c9b356e..0a71cb5290 100644 --- a/cmd/climc/shell/compute/buckets.go +++ b/cmd/climc/shell/compute/buckets.go @@ -398,6 +398,7 @@ func init() { AllowedHeaders []string MaxAgeSeconds int ExposeHeaders []string + RuleId string } R(&BucketSetCorsOption{}, "bucket-set-cors", "Set bucket cors", func(s *mcclient.ClientSession, args *BucketSetCorsOption) error { @@ -407,8 +408,9 @@ func init() { AllowedHeaders: args.AllowedHeaders, MaxAgeSeconds: args.MaxAgeSeconds, ExposeHeaders: args.ExposeHeaders, + Id: args.RuleId, } - rules := api.BucketCORSRules{Rules: []api.BucketCORSRule{rule}} + rules := api.BucketCORSRules{Data: []api.BucketCORSRule{rule}} result, err := modules.Buckets.PerformAction(s, args.ID, "set-cors", jsonutils.Marshal(rules)) if err != nil { return err @@ -430,10 +432,13 @@ func init() { }) type BucketDeleteCorsOption struct { - ID string `help:"ID or name of bucket" json:"-"` + ID string `help:"ID or name of bucket" json:"-"` + Id []string `"help:Id of rules to delete"` } - R(&BucketGetWebsiteConfOption{}, "bucket-delete-cors", "Delete bucket cors", func(s *mcclient.ClientSession, args *BucketGetWebsiteConfOption) error { - result, err := modules.Buckets.PerformAction(s, args.ID, "delete-cors", nil) + R(&BucketDeleteCorsOption{}, "bucket-delete-cors", "Delete bucket cors", func(s *mcclient.ClientSession, args *BucketDeleteCorsOption) error { + input := api.BucketCORSRuleDeleteInput{} + input.Id = args.Id + result, err := modules.Buckets.PerformAction(s, args.ID, "delete-cors", jsonutils.Marshal(input)) if err != nil { return err } diff --git a/cmd/climc/shell/compute/inter_vpc_network.go b/cmd/climc/shell/compute/inter_vpc_network.go new file mode 100644 index 0000000000..9cf8d8326c --- /dev/null +++ b/cmd/climc/shell/compute/inter_vpc_network.go @@ -0,0 +1,32 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.InterVpcNetworks).WithKeyword("inter-vpc-network") + cmd.List(&options.InterVpcNetworkListOPtions{}) + cmd.Show(&options.InterVpcNetworkIdOPtions{}) + cmd.Create(&options.InterVpcNetworkCreateOPtions{}) + cmd.Delete(&options.InterVpcNetworkIdOPtions{}) + cmd.Perform("syncstatus", &options.InterVpcNetworkIdOPtions{}) + cmd.Perform("addvpc", &options.InterVpcNetworkAddVpcOPtions{}) + cmd.Perform("removevpc", &options.InterVpcNetworkRemoveVpcOPtions{}) +} diff --git a/cmd/climc/shell/compute/inter_vpc_network_routeset.go b/cmd/climc/shell/compute/inter_vpc_network_routeset.go new file mode 100644 index 0000000000..09103e9667 --- /dev/null +++ b/cmd/climc/shell/compute/inter_vpc_network_routeset.go @@ -0,0 +1,29 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.InterVpcNetworkRouteSets).WithKeyword("inter-vpc-network-routeset") + cmd.List(&options.InterVpcNetworkRouteListOptions{}) + cmd.Show(&options.InterVpcNetworkRouteIdptions{}) + cmd.Perform("enable", &options.InterVpcNetworkRouteIdptions{}) + cmd.Perform("disable", &options.InterVpcNetworkRouteIdptions{}) +} diff --git a/cmd/climc/shell/compute/schedtags.go b/cmd/climc/shell/compute/schedtags.go index bd9ef500c1..4cd01861eb 100644 --- a/cmd/climc/shell/compute/schedtags.go +++ b/cmd/climc/shell/compute/schedtags.go @@ -15,155 +15,20 @@ package compute import ( - "fmt" - - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" ) func init() { - type SchedtagListOptions struct { - options.BaseListOptions - Type string `help:"Filter by resource type"` - } - R(&SchedtagListOptions{}, "schedtag-list", "List schedule tags", func(s *mcclient.ClientSession, args *SchedtagListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err + cmd := shell.NewResourceCmd(&modules.Schedtags) - } - } - if len(args.Type) > 0 { - params.Add(jsonutils.NewString(args.Type), "resource_type") - } - result, err := modules.Schedtags.List(s, params) - if err != nil { - return err - } - printList(result, modules.Schedtags.GetColumns(s)) - return nil - }) - - type SchedtagShowOptions struct { - ID string `help:"ID or Name of the scheduler tag to show"` - } - R(&SchedtagShowOptions{}, "schedtag-show", "Show scheduler tag details", func(s *mcclient.ClientSession, args *SchedtagShowOptions) error { - result, err := modules.Schedtags.Get(s, args.ID, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - - R(&SchedtagShowOptions{}, "schedtag-delete", "Delete a scheduler tag", func(s *mcclient.ClientSession, args *SchedtagShowOptions) error { - result, err := modules.Schedtags.Delete(s, args.ID, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - - type SchedtagCreateOptions struct { - NAME string `help:"Name of new schedtag"` - Strategy string `help:"Policy" choices:"require|exclude|prefer|avoid"` - Desc string `help:"Description"` - Scope string `help:"Resource scope" choices:"system|domain|project"` - Type string `help:"Resource type" choices:"hosts|storages|networks|cloudproviders|cloudregions|zones"` - } - R(&SchedtagCreateOptions{}, "schedtag-create", "Create a schedule tag", func(s *mcclient.ClientSession, args *SchedtagCreateOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.NAME), "name") - if len(args.Strategy) > 0 { - params.Add(jsonutils.NewString(args.Strategy), "default_strategy") - } - if len(args.Desc) > 0 { - params.Add(jsonutils.NewString(args.Desc), "description") - } - if len(args.Type) > 0 { - params.Add(jsonutils.NewString(args.Type), "resource_type") - } - if len(args.Scope) > 0 { - params.Add(jsonutils.NewString(args.Scope), "scope") - } - schedtag, err := modules.Schedtags.Create(s, params) - if err != nil { - return err - } - printObject(schedtag) - return nil - }) - - type SchedtagUpdateOptions struct { - ID string `help:"ID or Name of schetag"` - Name string `help:"New name of schedtag"` - Strategy string `help:"Policy" choices:"require|exclude|prefer|avoid"` - Desc string `help:"Description"` - ClearStrategy bool `help:"Clear default schedule policy"` - } - R(&SchedtagUpdateOptions{}, "schedtag-update", "Update a schedule tag", func(s *mcclient.ClientSession, args *SchedtagUpdateOptions) error { - params := jsonutils.NewDict() - if len(args.Name) > 0 { - params.Add(jsonutils.NewString(args.Name), "name") - } - if len(args.Strategy) > 0 { - params.Add(jsonutils.NewString(args.Strategy), "default_strategy") - } - if len(args.Desc) > 0 { - params.Add(jsonutils.NewString(args.Desc), "description") - } - if args.ClearStrategy { - params.Add(jsonutils.NewString(""), "default_strategy") - } - if params.Size() == 0 { - return fmt.Errorf("No valid data to update") - } - schedtag, err := modules.Schedtags.Update(s, args.ID, params) - if err != nil { - return err - } - printObject(schedtag) - return nil - }) - - type SetScopeOptions struct { - ID []string `help:"ID or Name of schetag"` - Project string `help:"ID or Name of project"` - Domain string `help:"ID or Name of domain"` - System bool `help:"Set to system scope"` - } - R(&SetScopeOptions{}, "schedtag-set-scope", "Set schedtag scope", func(s *mcclient.ClientSession, args *SetScopeOptions) error { - params := jsonutils.NewDict() - domainId := args.Domain - projectId := args.Project - if args.System { - domainId = "" - projectId = "" - } - params.Add(jsonutils.NewString(domainId), "domain") - params.Add(jsonutils.NewString(projectId), "project") - ret := modules.Schedtags.BatchPerformAction(s, args.ID, "set-scope", params) - printBatchResults(ret, modules.Schedtags.GetColumns(s)) - return nil - }) - - R(&options.ResourceMetadataOptions{}, "schedtag-set-user-metadata", "Set metadata of a server", func(s *mcclient.ClientSession, opts *options.ResourceMetadataOptions) error { - params, err := opts.Params() - if err != nil { - return err - } - result, err := modules.Schedtags.PerformAction(s, opts.ID, "user-metadata", params) - if err != nil { - return err - } - printObject(result) - return nil - }) + cmd.List(new(options.SchedtagListOptions)) + cmd.Show(new(options.SchedtagShowOptions)) + cmd.Delete(new(options.SchedtagShowOptions)) + cmd.Create(new(options.SchedtagCreateOptions)) + cmd.Update(new(options.SchedtagUpdateOptions)) + cmd.BatchPerform("set-scope", new(options.SchedtagSetScopeOptions)) + cmd.PerformWithKeyword("set-user-metadata", "user-metadata", new(options.ResourceMetadataOptions)) + cmd.Perform("set-resource", new(options.SchedtagSetResource)) } diff --git a/cmd/climc/shell/identity/domains.go b/cmd/climc/shell/identity/domains.go index f74f57dfa9..e0cf0e0b7e 100644 --- a/cmd/climc/shell/identity/domains.go +++ b/cmd/climc/shell/identity/domains.go @@ -25,7 +25,8 @@ import ( func init() { type DomainListOptions struct { options.BaseListOptions - IdpId string `help:"filter by idp_id"` + IdpId string `help:"filter by idp_id"` + IdpEntityId string `help:"filter by idp_entity_id"` } R(&DomainListOptions{}, "domain-list", "List domains", func(s *mcclient.ClientSession, args *DomainListOptions) error { params, err := options.ListStructToParams(args) diff --git a/cmd/climc/shell/identity/identityproviders.go b/cmd/climc/shell/identity/identityproviders.go index 3dbed83ef7..945ea623cb 100644 --- a/cmd/climc/shell/identity/identityproviders.go +++ b/cmd/climc/shell/identity/identityproviders.go @@ -472,6 +472,9 @@ func init() { AutoCreateProject bool `help:"automatically create a default project when importing domain" json:"-"` NoAutoCreateProject bool `help:"do not create default project when importing domain" json:"-"` + AutoCreateUser bool `help:"automatically create a user" json:"-"` + NoAutoCreateUser bool `help:"do not automatically create a user" json:"-"` + TargetDomain string `help:"target domain without creating new domain" json:"-"` api.SOIDCIdpConfigOptions @@ -488,6 +491,11 @@ func init() { } else if args.NoAutoCreateProject { params.Add(jsonutils.JSONFalse, "auto_create_project") } + if args.AutoCreateUser { + params.Add(jsonutils.JSONTrue, "auto_create_user") + } else if args.NoAutoCreateUser { + params.Add(jsonutils.JSONFalse, "auto_create_user") + } params.Add(jsonutils.NewString("oidc"), "driver") params.Add(jsonutils.Marshal(args), "config", "oidc") diff --git a/cmd/climc/shell/identity/users.go b/cmd/climc/shell/identity/users.go index 57dc462719..436eea5786 100644 --- a/cmd/climc/shell/identity/users.go +++ b/cmd/climc/shell/identity/users.go @@ -31,6 +31,7 @@ func init() { OrderByDomain string `help:"order by domain name" choices:"asc|desc"` Role string `help:"Filter by role"` IdpId string `help:"filter by idp_id"` + IdpEntityId string `help:"filter by idp_entity_id"` } R(&UserListOptions{}, "user-list", "List users", func(s *mcclient.ClientSession, args *UserListOptions) error { params, err := options.ListStructToParams(args) diff --git a/go.sum b/go.sum index ae6ba2180c..903d69a053 100644 --- a/go.sum +++ b/go.sum @@ -1124,8 +1124,6 @@ vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj yunion.io/x/executor v0.0.0-20200227030256-a18417815e74 h1:A15C6VdVRWvmQ9pAJHrUs9yan5qKlYH7uaRxHg1kRbk= yunion.io/x/executor v0.0.0-20200227030256-a18417815e74/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws= yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634= -yunion.io/x/jsonutils v0.0.0-20201105032201-9d0fba742954 h1:gGU3uGxw82voqh3tVrTY4q43lLLooqgF662PqOeEpKY= -yunion.io/x/jsonutils v0.0.0-20201105032201-9d0fba742954/go.mod h1:p0nyMqGA/apTxxyLIU/o1k4V7Vujl2O6ey30L594sYE= yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769 h1:LIQ4hhLGQuQK+XxlV+8JrKBuL37WUT+5ZTVxBwHOTD4= yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769/go.mod h1:p0nyMqGA/apTxxyLIU/o1k4V7Vujl2O6ey30L594sYE= yunion.io/x/log v0.0.0-20190514041436-04ce53b17c6b/go.mod h1:+gauLs73omeJAPlsXcevLsJLKixV+sR/E7WSYTSx1fE= diff --git a/locales/locales.go b/locales/locales.go index 03759faa2b..aa1e85b8cb 100644 --- a/locales/locales.go +++ b/locales/locales.go @@ -44,396 +44,409 @@ func init() { var messageKeyToIndex = map[string]int{ "%s %s %s not found": 38, - "%s %s %s not support %s": 389, - "%s %s not found": 435, - "%s %s not support policy value %s": 394, - "%s %s not supported dns type %s": 387, - "%s %s not supported policy type %s": 388, + "%s %s %s not support %s": 402, + "%s %s not found": 449, + "%s %s not support policy value %s": 408, + "%s %s not supported dns type %s": 400, + "%s %s not supported policy type %s": 401, "%s allow %s %s not found": 44, - "%s backend group not support change port": 1202, - "%s backend group not support change port or weight": 1203, - "%s cannot be set to 0": 1220, + "%s backend group not support change port": 1231, + "%s backend group not support change port or weight": 1232, + "%s cannot be set to 0": 1249, "%s disk cannot exceed 8": 147, - "%s does not currently support creating loadbalancer": 1259, - "%s does not currently support creating loadbalancer acl": 1255, - "%s does not currently support creating loadbalancer certificate": 1256, - "%s does not support creating loadbalancer": 1262, - "%s does not support creating loadbalancer acl": 1263, - "%s does not support creating loadbalancer certificate": 1264, + "%s does not currently support creating loadbalancer": 1288, + "%s does not currently support creating loadbalancer acl": 1284, + "%s does not currently support creating loadbalancer certificate": 1285, + "%s does not support creating loadbalancer": 1291, + "%s does not support creating loadbalancer acl": 1292, + "%s does not support creating loadbalancer certificate": 1293, "%s for %s features are not compatible for creating instance": 148, - "%s is not modifiable": 463, + "%s is not modifiable": 482, "%s is not mount point %s": 192, - "%s is out of network IP ranges": 784, - "%s is reserved for aliyun %s, please use another": 1239, - "%s length must less 500 letters": 1211, - "%s listener port %d is already taken by listener %s(%s)": 946, + "%s is out of network IP ranges": 803, + "%s is reserved for aliyun %s, please use another": 1268, + "%s length must less 500 letters": 1240, + "%s listener port %d is already taken by listener %s(%s)": 965, "%s method not found": 16, "%s method params length not match, expected %d, input %d": 17, "%s not allow to %s %s": 45, "%s not allow to get property %s": 35, "%s not allow to get spec %s": 39, - "%s not support": 276, + "%s not support": 287, "%s not support cdrom params": 157, - "%s not support close tcp or udp loadbalancer listener health check": 1221, - "%s not support create account": 1290, + "%s not support close tcp or udp loadbalancer listener health check": 1250, + "%s not support create account": 1320, "%s not support create eip": 141, "%s not support create eip, it only support bind eip": 158, - "%s not support create subscription": 277, + "%s not support create subscription": 288, "%s not support create virtual machine with eip": 159, - "%s not support policy type %s": 393, - "%s not support rebuild root with a different image": 540, - "%s not support recovery": 1284, - "%s not support this operation": 332, - "%s only support aliyun %s": 1243, - "%s only support aliyun %s or %s": 1242, - "%s rds Support up to %d security groups": 319, - "%s rds not support secgroup": 318, - "%s request the mask range should be between 16 and 28": 1316, - "%s require disk size must in 40 ~ 4000 GB": 1272, + "%s not support policy type %s": 407, + "%s not support rebuild root with a different image": 559, + "%s not support recovery": 1313, + "%s not support saml auth": 268, + "%s not support this operation": 345, + "%s only support aliyun %s": 1272, + "%s only support aliyun %s or %s": 1271, + "%s rds Support up to %d security groups": 330, + "%s rds not support secgroup": 329, + "%s request the mask range should be between 16 and 28": 1347, + "%s require disk size must in 40 ~ 4000 GB": 1301, "%s requires that the eip bandwidth must be less than 100Mbps": 124, - "%s requires the virtual machine state to be %s before it can be added backendgroup, but current state of the virtual machine is %s": 920, + "%s requires the virtual machine state to be %s before it can be added backendgroup, but current state of the virtual machine is %s": 939, "%s reset disk required guest status is running or ready": 186, - "%s shall bind up to %d security groups": 734, + "%s shall bind up to %d security groups": 753, "%s(%s) not allow to delete": 48, - "%s: %s cannot be ip address: %s": 424, - "%s: %s must be domain name: %s": 423, - "%s: Invalid IP address %s": 993, - "%s: bad base64 encoded string: %s": 885, - "%s: bad template: %s": 886, - "%s: invalid domain name: %s": 417, - "%s: name cannot be ip address: %s": 420, - "%s: new time is in the future: %s > %s": 891, - "%s: time error: %s": 890, - "%s: unknown record type": 425, - "A: record value must be ipv4 address: %s": 421, - "AAAA: record value must be ipv6 address: %s": 422, - "Access address located in different zone than specified": 789, - "Access ip %s has been used": 787, - "Access network has no zone???": 788, - "Account %s(%s) does not have database %s(%s) permissions": 305, - "Account auto sync enabled": 266, - "Account disabled": 265, - "Account status is not %s current status is %s": 302, + "%s: %s cannot be ip address: %s": 438, + "%s: %s must be domain name: %s": 437, + "%s: Invalid IP address %s": 1019, + "%s: bad base64 encoded string: %s": 904, + "%s: bad template: %s": 905, + "%s: invalid domain name: %s": 431, + "%s: name cannot be ip address: %s": 434, + "%s: new time is in the future: %s > %s": 910, + "%s: time error: %s": 909, + "%s: unknown record type": 439, + "A: record value must be ipv4 address: %s": 435, + "AAAA: record value must be ipv6 address: %s": 436, + "Access address located in different zone than specified": 808, + "Access ip %s has been used": 806, + "Access network has no zone???": 807, + "Account %s(%s) does not have database %s(%s) permissions": 316, + "Account auto sync enabled": 277, + "Account disabled": 276, + "Account status is not %s current status is %s": 313, "Action %s not found": 56, - "ActionNotFoundError": 1344, - "Active download session not expired": 1143, - "Address %s has been used": 977, - "Address %s not in network": 981, - "Address %s not in range": 974, - "Address %s not reserved": 976, - "Address been assigned out of new range": 1006, - "Aliyun %s not support recovery": 1223, - "Aliyun %s only 8.0 and 5.7 high_availability local_ssd or 5.6 high_availability support recovery from it self backups": 1225, - "Aliyun %s only support recover from it self backups": 1224, - "Aliyun DBInstance account name length shoud be 2~16 characters": 1238, - "Aliyun instance weight must be in the range of 0 ~ 100": 1198, - "Aliyun not allow to change certificate": 1194, + "ActionNotFoundError": 1375, + "Active download session not expired": 1171, + "Address %s has been used": 1003, + "Address %s not in network": 1007, + "Address %s not in range": 1000, + "Address %s not reserved": 1002, + "Address been assigned out of new range": 1032, + "Alert is already paused": 1545, + "Alert is already un-paused": 1544, + "Alert is missing conditions": 1593, + "Alert notification used by %d alert": 1567, + "Alert resource driver duplicate match": 1555, + "Alert resource driver not found": 1554, + "Aliyun %s not support recovery": 1252, + "Aliyun %s only 8.0 and 5.7 high_availability local_ssd or 5.6 high_availability support recovery from it self backups": 1254, + "Aliyun %s only support recover from it self backups": 1253, + "Aliyun DBInstance account name length shoud be 2~16 characters": 1267, + "Aliyun instance weight must be in the range of 0 ~ 100": 1227, + "Aliyun not allow to change certificate": 1223, "Aliyun reset disk required guest status is running or ready": 176, - "Already have backup server": 613, - "At least two networks are required under vpc %s(%s) with aliyun %s(%s)": 1236, + "Already have backup server": 632, + "At least two networks are required under vpc %s(%s) with aliyun %s(%s)": 1265, "Attach nfs storage require host status is online": 191, "Attach rbd storage require host status is online": 188, "Aws not support reset disk, you can create new disk with snapshot": 177, "Azure Mv2-series instance sku only support UEFI image": 127, "Azure UEFI image %s not support this instance sku": 128, "Azure not support reset disk, you can create new disk with snapshot": 179, - "Backup host is offline": 621, - "Backup only support hypervisor kvm": 615, - "BadGateway": 1337, - "BadRequestError": 1360, - "Bandwidth limit cannot exceed %dMbps": 978, - "Bandwidth must be non-negative": 572, + "Backup host is offline": 640, + "Backup only support hypervisor kvm": 634, + "BadGateway": 1368, + "BadRequestError": 1391, + "Bandwidth limit cannot exceed %dMbps": 1004, + "Bandwidth must be non-negative": 591, "Baremetal %s is not ready": 134, "Baremetal %s is occupied": 135, - "Baremetal %s not enabled": 753, + "Baremetal %s not enabled": 772, "Baremetal agent not found": 203, - "Baremetal host is aleady occupied": 831, + "Baremetal host is aleady occupied": 850, "Baremetal package not prepared": 204, - "BgpType attribute is only useful for eip network": 1027, + "BgpType attribute is only useful for eip network": 1053, "Bucket has %d task active, can't sync status": 236, - "CD-ROM not empty, please eject first": 521, - "CNAME cannot mix with other types": 415, - "CPU core count must be 1 ~ %d": 712, - "Can not delete disk snapshots, have manual snapshot": 1139, - "Can not get disk snapshot": 354, - "Can not rebuild root with with diff uefi image": 538, - "Can't clone guest with backup guest": 504, - "Can't do instance snapshot with backup guest": 669, - "Can't trigger scaling policy without status 'ready'": 1056, - "Cannot Delete disk %s snapshots, disk exist": 1137, - "Cannot add security groups for hypervisor %s": 525, - "Cannot add security groups in status %s": 456, - "Cannot assign security rules in status %s": 530, - "Cannot attach network in status %s": 570, - "Cannot cache image with no checksum": 828, - "Cannot change bandwidth in status %s": 571, + "CD-ROM not empty, please eject first": 540, + "CNAME cannot mix with other types": 429, + "CPU core count must be 1 ~ %d": 731, + "Can not delete disk snapshots, have manual snapshot": 1167, + "Can not get disk snapshot": 367, + "Can not rebuild root with with diff uefi image": 557, + "Can't clone guest with backup guest": 523, + "Can't do instance snapshot with backup guest": 688, + "Can't trigger scaling policy without status 'ready'": 1086, + "Cannot Delete disk %s snapshots, disk exist": 1165, + "Cannot add security groups for hypervisor %s": 544, + "Cannot add security groups in status %s": 471, + "Cannot assign security rules in status %s": 549, + "Cannot attach network in status %s": 589, + "Cannot cache image with no checksum": 847, + "Cannot change bandwidth in status %s": 590, "Cannot change config for baremtal": 130, - "Cannot change config in %s": 576, - "Cannot change config in status %s": 333, - "Cannot change config with different instance family": 577, - "Cannot change network ip_addr in status %s": 565, - "Cannot change server sku name": 1108, - "Cannot change setting in status %s": 573, - "Cannot clone VM in status %s": 506, - "Cannot create backup with isolated device": 730, - "Cannot create backup with isolated devices": 616, - "Cannot create backup with shared storage": 614, - "Cannot create backup with snapshot": 618, - "Cannot create disk with disabled storage[%s]": 347, - "Cannot create disk with offline storage[%s]": 348, - "Cannot delete keypair used by servers": 871, - "Cannot delete server disk %s must not have snapshots.": 710, - "Cannot delete server on disabled host": 708, - "Cannot delete server on offline host": 709, - "Cannot delete snapshot in status %s": 1132, - "Cannot delete snapshot on disk reset": 1135, - "Cannot delete the last cache": 1144, - "Cannot deploy in status %s": 508, - "Cannot detach network in status %s": 569, - "Cannot detach sys disk": 549, - "Cannot do Ipmi-probe in status %s": 806, - "Cannot do eject-iso in status %s": 847, - "Cannot do initialization in status %s": 807, - "Cannot do insert-iso in status %s": 846, - "Cannot do io throttle in status %s": 656, + "Cannot change config in %s": 595, + "Cannot change config in status %s": 346, + "Cannot change config with different instance family": 596, + "Cannot change network ip_addr in status %s": 584, + "Cannot change server sku name": 1136, + "Cannot change setting in status %s": 592, + "Cannot change state on pause alert": 1542, + "Cannot clone VM in status %s": 525, + "Cannot create backup with isolated device": 749, + "Cannot create backup with isolated devices": 635, + "Cannot create backup with shared storage": 633, + "Cannot create backup with snapshot": 637, + "Cannot create disk with disabled storage[%s]": 360, + "Cannot create disk with offline storage[%s]": 361, + "Cannot delete keypair used by servers": 890, + "Cannot delete server disk %s must not have snapshots.": 729, + "Cannot delete server on disabled host": 727, + "Cannot delete server on offline host": 728, + "Cannot delete snapshot in status %s": 1160, + "Cannot delete snapshot on disk reset": 1163, + "Cannot delete system alert": 1559, + "Cannot delete the last cache": 1172, + "Cannot deploy in status %s": 527, + "Cannot detach network in status %s": 588, + "Cannot detach sys disk": 568, + "Cannot do Ipmi-probe in status %s": 825, + "Cannot do eject-iso in status %s": 866, + "Cannot do initialization in status %s": 826, + "Cannot do insert-iso in status %s": 865, + "Cannot do io throttle in status %s": 675, "Cannot do live migrate, too low qemu version": 156, - "Cannot do maintenance in status %s": 798, - "Cannot do maintenance while guest status %s": 799, - "Cannot do reboot dbinstance in status %s": 326, - "Cannot do recovery dbinstance in status %s required status %s": 320, - "Cannot do renew dbinstance in status %s required status %s": 328, - "Cannot do restart elasticcache instance in status %s": 440, - "Cannot do restart server in status %s": 592, - "Cannot do snapshot when VM in status %s": 1333, - "Cannot do start server in status %s": 520, - "Cannot do unmaintenance in status %s": 800, - "Cannot enable deleting account": 253, - "Cannot keep detached disk": 550, - "Cannot live migrate in status %s": 503, + "Cannot do maintenance in status %s": 817, + "Cannot do maintenance while guest status %s": 818, + "Cannot do reboot dbinstance in status %s": 337, + "Cannot do recovery dbinstance in status %s required status %s": 331, + "Cannot do renew dbinstance in status %s required status %s": 339, + "Cannot do restart elasticcache instance in status %s": 454, + "Cannot do restart server in status %s": 611, + "Cannot do snapshot when VM in status %s": 1364, + "Cannot do start server in status %s": 539, + "Cannot do unmaintenance in status %s": 819, + "Cannot enable deleting account": 263, + "Cannot keep detached disk": 569, + "Cannot live migrate in status %s": 522, "Cannot live migrate with cdrom": 154, "Cannot live migrate with isolated devices": 155, "Cannot migrate with isolated devices": 153, - "Cannot mix different types of records, %s != %s": 429, - "Cannot modify Memory and CPU in status %s": 713, - "Cannot modify memory for baremetal": 714, + "Cannot mix different types of records, %s != %s": 443, + "Cannot modify Memory and CPU in status %s": 732, + "Cannot modify memory for baremetal": 733, "Cannot normal migrate guest in status %s, try rescue mode or server-live-migrate?": 151, - "Cannot perform cache image in status %s": 826, - "Cannot prepare baremetal in server status %s": 805, - "Cannot prepare baremetal in status %s": 804, - "Cannot purge elastic_ip on enabled cloud provider": 488, - "Cannot purge network on enabled cloud provider": 1010, - "Cannot purge route_table on enabled cloud provider": 1030, - "Cannot purge server on enabled host": 533, - "Cannot purge snapshot on enabled cloud provider": 1140, - "Cannot purge vpc on enabled cloud provider": 1178, - "Cannot reduce disk size": 585, - "Cannot reset VM in status %s": 589, - "Cannot reset baremetal in status %s": 824, - "Cannot reset baremetal with active guest": 825, - "Cannot reset disk %s(%s),Snapshot is belong to disk %s": 359, - "Cannot reset disk in status %s": 357, - "Cannot reset disk with snapshot in status %s": 358, - "Cannot reset root in status %s": 541, + "Cannot perform cache image in status %s": 845, + "Cannot prepare baremetal in server status %s": 824, + "Cannot prepare baremetal in status %s": 823, + "Cannot purge elastic_ip on enabled cloud provider": 507, + "Cannot purge network on enabled cloud provider": 1036, + "Cannot purge route_table on enabled cloud provider": 1060, + "Cannot purge server on enabled host": 552, + "Cannot purge snapshot on enabled cloud provider": 1168, + "Cannot purge vpc on enabled cloud provider": 1206, + "Cannot reduce disk size": 604, + "Cannot reset VM in status %s": 608, + "Cannot reset baremetal in status %s": 843, + "Cannot reset baremetal with active guest": 844, + "Cannot reset disk %s(%s),Snapshot is belong to disk %s": 372, + "Cannot reset disk in status %s": 370, + "Cannot reset disk with snapshot in status %s": 371, + "Cannot reset root in status %s": 560, "Cannot resize disk for baremtal": 131, - "Cannot resume VM in status %s": 518, - "Cannot revoke security groups in status %s": 528, + "Cannot resume VM in status %s": 537, + "Cannot revoke security groups in status %s": 547, "Cannot save image for baremtal": 136, - "Cannot save image in status %s": 499, - "Cannot send command in status %s": 497, - "Cannot send keys in status %s": 593, - "Cannot set default strategy of %s": 1071, - "Cannot set security group for this guest %s": 532, - "Cannot set security rules in status %s": 531, - "Cannot start a non-baremetal host": 793, - "Cannot start baremetal with active guest": 794, - "Cannot stop a non-baremetal host": 795, - "Cannot stop baremetal with active guest": 797, - "Cannot stop baremetal with non-active guest": 796, - "Cannot stop server in status %s": 591, - "Cannot suspend VM in status %s": 517, - "Cannot switch OS between %s-%s": 537, - "Cannot swith to backup when guest in status %s": 606, - "Cannot sync config a non-baremetal host": 848, - "Cannot sync in status %s": 502, - "Cannot sync status a non-baremetal host": 823, - "Cannot uncache in status %s": 1145, - "Cannot unconvert in status %s": 840, - "Cannot update external resource": 295, - "Check input guests is exist": 667, - "Check set pending quota error %s": 648, - "Cloudaccount disabled": 285, - "Cloudprovider disabled": 284, - "Conflict address space with existing networks": 1005, - "Conflict address space with existing networks in vpc %q": 1003, + "Cannot save image in status %s": 518, + "Cannot send command in status %s": 516, + "Cannot send keys in status %s": 612, + "Cannot set default strategy of %s": 1101, + "Cannot set security group for this guest %s": 551, + "Cannot set security rules in status %s": 550, + "Cannot start a non-baremetal host": 812, + "Cannot start baremetal with active guest": 813, + "Cannot stop a non-baremetal host": 814, + "Cannot stop baremetal with active guest": 816, + "Cannot stop baremetal with non-active guest": 815, + "Cannot stop server in status %s": 610, + "Cannot suspend VM in status %s": 536, + "Cannot switch OS between %s-%s": 556, + "Cannot swith to backup when guest in status %s": 625, + "Cannot sync config a non-baremetal host": 867, + "Cannot sync in status %s": 521, + "Cannot sync status a non-baremetal host": 842, + "Cannot uncache in status %s": 1173, + "Cannot unconvert in status %s": 859, + "Cannot update external resource": 306, + "Check input guests is exist": 686, + "Check set pending quota error %s": 667, + "Cloudaccount disabled": 296, + "Cloudprovider disabled": 295, + "Condition is missing the threshold parameter": 1589, + "Condition is missing the type parameter": 1590, + "Conflict address space with existing networks": 1031, + "Conflict address space with existing networks in vpc %q": 1029, "Conflict manager_uri %s": 206, - "ConflictError": 1368, - "Connot convert hypervisor in status %s": 832, + "ConflictError": 1399, + "Connot convert hypervisor in status %s": 851, "Container not support %s": 140, "Content-Length negative %d": 229, - "Convert error: %s": 836, - "Couldn't delete snapshot policy binding to disks": 1125, - "Create disk on host error: %s": 588, - "Currently only kvm platform supports creating wire": 1185, - "DBInstance %s(%s) status is %s require status is %s": 297, - "DBInstance backup has %d task active, can't sync status": 307, - "DBInstance has %d task active, can't sync status": 327, - "DBInstance has opened the outer network connection": 330, - "DBInstance is locked, cannot delete": 336, - "DBinstance has not valid cloudprovider": 306, + "Convert error: %s": 855, + "Couldn't delete snapshot policy binding to disks": 1153, + "Create disk on host error: %s": 607, + "Currently only kvm platform supports creating wire": 1213, + "DBInstance %s(%s) status is %s require status is %s": 308, + "DBInstance backup has %d task active, can't sync status": 318, + "DBInstance has %d task active, can't sync status": 338, + "DBInstance has opened the outer network connection": 343, + "DBInstance is locked, cannot delete": 349, + "DBinstance has not valid cloudprovider": 317, "DIRECT setting cannot be changed": 62, "DIRECT setting cannot be deleted": 63, - "DISK Index %d has been occupied": 697, + "DISK Index %d has been occupied": 716, "Data disk size must be an integer multiple of 10G": 166, - "Database status is not %s current is %s": 304, + "Database status is not %s current is %s": 315, + "Default data source not found": 1561, "Default quota %s not allow to delete": 68, - "Description can not start with http:// or https://": 1237, - "Directly creating cloudprovider is not supported, create cloudaccount instead": 281, - "Disk %s and guest not belong to the same account": 509, - "Disk %s and guest not belong to the same zone": 510, - "Disk %s don't need convert snapshots": 353, - "Disk %s dose not have snapshot": 1138, - "Disk %s dosen't attach guest ?": 1331, - "Disk %s has been attached": 512, - "Disk %s not attached": 552, - "Disk %s not belong the guest's host": 513, - "Disk %s not found": 516, - "Disk %s snapshot full, cannot take any more": 1335, + "Description can not start with http:// or https://": 1266, + "Directly creating cloudprovider is not supported, create cloudaccount instead": 292, + "Disk %s and guest not belong to the same account": 528, + "Disk %s and guest not belong to the same zone": 529, + "Disk %s don't need convert snapshots": 366, + "Disk %s dose not have snapshot": 1166, + "Disk %s dosen't attach guest ?": 1362, + "Disk %s has been attached": 531, + "Disk %s not attached": 571, + "Disk %s not belong the guest's host": 532, + "Disk %s not found": 535, + "Disk %s snapshot full, cannot take any more": 1366, "Disk attach muti guests": 193, - "Disk attached Guest has backup, Can't create snapshot": 1332, + "Disk attached Guest has backup, Can't create snapshot": 1363, "Disk attached guest status must be ready": 194, - "Disk cannot be thrink": 361, + "Disk cannot be thrink": 374, "Disk dosen't attach guest": 195, - "Disk has %d task active, can't sync status": 381, - "Disk in %s not able to attach": 514, + "Disk has %d task active, can't sync status": 394, + "Disk in %s not able to attach": 533, "Disk must be dettached": 185, - "Diskinfo index %d: both imageID and size are absent": 377, - "Do not need to update": 1124, - "Duplicate ID %s %s": 1382, - "Duplicate image name %s": 366, + "Diskinfo index %d: both imageID and size are absent": 390, + "Do not need to update": 1152, + "Duplicate ID %s %s": 1413, + "Duplicate image name %s": 379, "Duplicate manager_uri %s": 207, - "Duplicate name %s": 1018, - "Duplicate name %s %s": 1381, - "Duplicate sku %s": 1105, + "Duplicate name %s": 1044, + "Duplicate name %s %s": 1412, + "Duplicate sku %s": 1133, "DuplicateIdError": 115, - "DuplicateNameError": 1366, - "DuplicateResourceError": 1367, - "Duration %s invalid": 979, - "Eject ISO not allowed in status %s": 524, - "Elastic cache is locked, cannot delete": 441, - "Elastic cache is not expired, cannot delete": 442, - "Elasticcache has %d task active, can't sync status": 453, - "Empty import disks": 633, - "Empty import nics": 630, - "Empty record": 426, - "Empty spec query key": 1324, - "EmptyRequestError": 1361, - "Fail to mark cache status: %s": 1150, - "Failed fetching secgroup %s": 1087, - "Failed to found database %s for dbinstance %s(%s): %v": 300, - "Failed to unmarshal input: %v": 1086, - "Fetch guest error %s": 612, - "Fetch instance snapshot error %s": 1133, - "Fetch netif error %s": 822, - "Fetch snapshot count failed %s": 352, - "Fetch storage error: %s": 586, + "DuplicateNameError": 1397, + "DuplicateResourceError": 1398, + "Duration %s invalid": 1005, + "Eject ISO not allowed in status %s": 543, + "Elastic cache is locked, cannot delete": 455, + "Elastic cache is not expired, cannot delete": 456, + "Elasticcache has %d task active, can't sync status": 468, + "Empty import disks": 652, + "Empty import nics": 649, + "Empty record": 440, + "Empty spec query key": 1355, + "EmptyRequestError": 1392, + "Fail to mark cache status: %s": 1178, + "Failed fetching secgroup %s": 1116, + "Failed to found database %s for dbinstance %s(%s): %v": 311, + "Failed to unmarshal input: %v": 1115, + "Fetch guest error %s": 631, + "Fetch instance snapshot error %s": 1161, + "Fetch netif error %s": 841, + "Fetch snapshot count failed %s": 365, + "Fetch storage error: %s": 605, "FetchCustomizeColumns return incorrect number of results": 28, "FetchCustomizeColumns returns incorrect results": 32, - "For default vpc, only system level sharing can be set": 1180, - "ForbiddenError": 1364, + "For default vpc, only system level sharing can be set": 1208, + "ForbiddenError": 1395, "General error: general error for %q: %s": 99, - "Generate ifname hint failed %s": 1020, - "Generate snapshot name failed %s": 677, - "Generate xml failed: %s": 642, - "GenerateName fail %s": 1019, - "Get convert snapshot failed: %s": 355, - "Get object error: %v": 1326, - "GetAllocatedNicCount fail %s": 963, - "GetDiskCount fail %s": 774, + "Generate ifname hint failed %s": 1046, + "Generate snapshot name failed %s": 696, + "Generate xml failed: %s": 661, + "GenerateName fail %s": 1045, + "Get convert snapshot failed: %s": 368, + "Get object error: %v": 1357, + "GetAllocatedNicCount fail %s": 989, + "GetDiskCount fail %s": 793, "GetGuestCount fail %s": 174, - "GetGuestDiskCount fail %s": 860, - "GetGuestDiskCount for disk %s fail %s": 374, - "GetGuestnicsCount fail %s": 861, - "GetGuestsCount fail %s": 1093, - "GetHostCount fail %s": 1155, + "GetGuestDiskCount fail %s": 879, + "GetGuestDiskCount for disk %s fail %s": 387, + "GetGuestnicsCount fail %s": 880, + "GetGuestsCount fail %s": 1122, + "GetHostCount fail %s": 1183, "GetIObject error %s": 230, "GetIObject fail %s": 220, - "GetLinkedGuestsCount failed %s": 870, - "GetNatgatewayCount fail %v": 1172, - "GetNetworkCount fail %s": 1170, - "GetObjectCount fail %s": 1072, - "GetRequesterVpcPeeringConnections fail %v": 1175, - "GetRuningGuestCount fail %s": 368, - "GetSnapshotCount fail %s": 382, - "GetVpcCount fail %s": 291, - "GetZoneCount fail %s": 290, - "Google dbinstance not support prepaid billing type": 1265, - "Guest %s not found": 611, - "Guest %s not support attach disk in status %s": 515, - "Guest '%s' don't belong to ScalingGroup '%s'": 1050, - "Guest Insert error: %s": 809, - "Guest backup host not found": 620, - "Guest can't switch to backup, mirror job not ready": 608, - "Guest has %d task active, can't sync status": 590, - "Guest have backup not allow to change config": 575, + "GetLinkedGuestsCount failed %s": 889, + "GetNatgatewayCount fail %v": 1200, + "GetNetworkCount fail %s": 1198, + "GetObjectCount fail %s": 1102, + "GetRequesterVpcPeeringConnections fail %v": 1203, + "GetRuningGuestCount fail %s": 381, + "GetSnapshotCount fail %s": 395, + "GetVpcCount fail %s": 302, + "GetZoneCount fail %s": 301, + "Google dbinstance not support prepaid billing type": 1294, + "Guest %s not found": 630, + "Guest %s not support attach disk in status %s": 534, + "Guest '%s' don't belong to ScalingGroup '%s'": 1080, + "Guest Insert error: %s": 828, + "Guest backup host not found": 639, + "Guest can't switch to backup, mirror job not ready": 627, + "Guest has %d task active, can't sync status": 609, + "Guest have backup not allow to change config": 594, "Guest have backup, can't migrate": 150, - "Guest hypervisor %s does not support clone": 505, - "Guest no backup host": 607, - "Guest without backup": 619, - "GuestDisksHasSnapshot fail %s": 617, + "Guest hypervisor %s does not support clone": 524, + "Guest no backup host": 626, + "Guest without backup": 638, + "GuestDisksHasSnapshot fail %s": 636, "Handler not found": 10, "Host %s already have mount point %s with other storage": 190, - "Host %s can't migrate guests %s in status %s": 845, + "Host %s can't migrate guests %s in status %s": 864, "Host %s is not a baremetal": 133, "Host %s is not online": 173, "Host %s not found": 149, - "Host is a converted baremetal, should be unconverted before delete": 770, - "Host is not disabled": 771, - "Host missing": 495, - "Host resource is not enough": 587, - "Host should be disabled": 839, - "HostCount fail %s": 1186, - "Huawei %s rds not support recovery from it self rds backup": 1285, - "Huawei DBInstance Disk cannot be thrink": 1279, - "Huawei DBInstance backup name length shoud be 4~64 characters": 1277, - "Huawei DBInstance category cannot change": 1280, - "Huawei DBInstance storage type cannot change": 1281, - "Huawei current not support reset dbinstance account password": 1282, - "Huawei dbinstance name length shoud be 4~64 characters": 1271, - "Huawei only %s engine support databases recovery": 1286, - "Huawei only supports specified databases with %s": 1278, - "Huawei rds password cannot be in the same reverse order as the account": 1275, - "Hypervisor %s can't do io throttle": 655, - "Hypervisor %s can't generate libvirt xml": 641, - "Hypervisor %s not supported": 758, - "IP %s not attach to any wire": 815, - "IP %s not attach to wire %s": 814, - "IPMI address located in different zone than specified": 786, - "IPMI has no password information": 778, - "IPMI infomation not configured": 808, - "IPMI network has no zone???": 785, - "IPMI network has not zone???": 791, + "Host is a converted baremetal, should be unconverted before delete": 789, + "Host is not disabled": 790, + "Host missing": 514, + "Host resource is not enough": 606, + "Host should be disabled": 858, + "HostCount fail %s": 1214, + "Huawei %s rds not support recovery from it self rds backup": 1314, + "Huawei DBInstance Disk cannot be thrink": 1308, + "Huawei DBInstance backup name length shoud be 4~64 characters": 1306, + "Huawei DBInstance category cannot change": 1309, + "Huawei DBInstance storage type cannot change": 1310, + "Huawei current not support reset dbinstance account password": 1311, + "Huawei dbinstance name length shoud be 4~64 characters": 1300, + "Huawei only %s engine support databases recovery": 1315, + "Huawei only supports specified databases with %s": 1307, + "Huawei rds password cannot be in the same reverse order as the account": 1304, + "Hypervisor %s can't do io throttle": 674, + "Hypervisor %s can't generate libvirt xml": 660, + "Hypervisor %s not supported": 777, + "IP %s not attach to any wire": 834, + "IP %s not attach to wire %s": 833, + "IPMI address located in different zone than specified": 805, + "IPMI has no password information": 797, + "IPMI infomation not configured": 827, + "IPMI network has no zone???": 804, + "IPMI network has not zone???": 810, "Illegal Content-Length %s": 228, - "Image %s not found": 1378, - "Image is in use": 1142, - "Image name is required": 370, - "Image status is not active": 380, - "ImageNotFoundError": 1341, - "Inconsistent: local storage is not empty???": 776, - "Incontinuity Network for %s and %s": 1013, + "Image %s not found": 1409, + "Image is in use": 1170, + "Image name is required": 383, + "Image status is not active": 393, + "ImageNotFoundError": 1372, + "Inconsistent: local storage is not empty???": 795, + "Incontinuity Network for %s and %s": 1039, + "Influxdb invalid status": 1587, "InformerBackend not init": 94, - "InputParameterError": 1349, - "Insert ISO not allowed in status %s": 522, + "InputParameterError": 1380, + "Insert ISO not allowed in status %s": 541, "Insert shared resource failed %s": 81, - "Instance sanpshot not ready": 674, - "Instance snapshot not ready": 718, - "Instance status is not %s current status is %s": 303, - "InsufficientResourceError": 1352, - "Interface %s not exist": 818, - "Interface %s not exists": 820, + "Instance sanpshot not ready": 693, + "Instance snapshot not ready": 737, + "Instance status is not %s current status is %s": 314, + "InsufficientResourceError": 1383, + "Interface %s not exist": 837, + "Interface %s not exists": 839, "Internal server error": 8, "Internal server error: %s": 7, - "InternalServerError": 1338, - "Invaild mac address": 810, + "InternalServerError": 1369, + "Invaild mac address": 829, "Invald %s return value": 34, "Invald CustomizeDelete return value": 26, "Invald ListItemFilter return value count %d": 19, @@ -444,1018 +457,1204 @@ var messageKeyToIndex = map[string]int{ "Invalid FetchCustomizeColumns return value type, not a slice!": 23, "Invalid FetchCustomizeColumns return value, inconsistent obj count: input %d != output %d": 24, "Invalid GetExtraDetails return value count %d": 21, - "Invalid IP %s": 1015, - "Invalid Target Network %s: inconsist %s": 1012, - "Invalid bandwidth": 487, + "Invalid IP %s": 1041, + "Invalid Target Network %s: inconsist %s": 1038, + "Invalid bandwidth": 506, "Invalid choice error: invalid %q, want %s, got %s": 101, + "Invalid condition evaluator type": 1591, "Invalid data JSONObject": 46, - "Invalid default stragegy %s": 1070, - "Invalid desc: %s": 626, + "Invalid default stragegy %s": 1100, + "Invalid desc: %s": 645, "Invalid handler %s": 9, - "Invalid host ip %s": 637, + "Invalid host ip %s": 656, + "Invalid interval format: %s": 1584, "Invalid length error: %q too long, got %d, max %d": 103, "Invalid length error: %q too short, got %d, min %d": 102, - "Invalid mac address": 821, - "Invalid masklen %d": 989, - "Invalid medium type %s": 1153, + "Invalid level format: %s": 1556, + "Invalid mac address": 840, + "Invalid masklen %d": 1015, + "Invalid medium type %s": 1181, + "Invalid period format: %s": 1557, "Invalid raid config: %v": 132, + "Invalid refresh format: %s": 1546, "Invalid request header: %v": 12, - "Invalid root image: %s": 722, - "Invalid schedtag %s": 1069, - "Invalid server ip address %s": 639, - "Invalid server mac address %s": 638, - "Invalid server_type: %s": 985, - "Invalid start ip: %s %s": 990, - "Invalid storage type %s": 1152, + "Invalid root image: %s": 741, + "Invalid schedtag %s": 1099, + "Invalid server ip address %s": 658, + "Invalid server mac address %s": 657, + "Invalid server_type: %s": 1011, + "Invalid start ip: %s %s": 1016, + "Invalid storage type %s": 1180, + "Invalid time_from format: %s": 1575, "Invalid type error: expecting %s type for %q: %s": 100, - "Invalid userdata: %v": 735, + "Invalid userdata: %v": 754, "Invalid value error: invalid %q: ": 107, "Invalid value error: invalid %q: %s": 105, "Invalid value error: invalid %q: %v": 106, - "InvalidCredentialError": 1363, - "InvalidFormatError": 1348, + "InvalidCredentialError": 1394, + "InvalidFormatError": 1379, "InvalidProvider": 120, "InvalidStatusError": 116, - "InvalidToken": 1384, - "Ip %s not in network %s(%s) range": 311, - "Isolated device %s not found": 555, - "Isolated device already attached to another guest: %s": 866, - "Isolated device is not attached to this guest": 556, - "Isolated device used by server": 864, - "Isolated device used by server: %s": 867, - "IsolatedDevice %s not found": 865, - "Keypair %s not found": 732, - "Kvm snapshot missing storage ??": 1309, - "Loadbalancer's manager %s does not match vpc's(%s(%s)) (%s)": 1249, - "Loadbalancer's manager (%s(%s)) does not match vpc's(%s(%s)) (%s)": 1193, - "Local host storage is not empty???": 775, - "Master dbinstance memory <64GB, up to 5 read-only instances are allowed to be created": 1235, - "Master dbinstance memory ≥64GB, up to 10 read-only instances are allowed to be created": 1234, - "Memory size must be 8MB ~ %d GB": 711, - "Memory size must be number[+unit], like 256M, 1G or 256": 579, - "Miss operating system???": 725, - "Missing isolated device": 554, + "InvalidToken": 1512, + "Ip %s not in network %s(%s) range": 322, + "Isolated device %s not found": 574, + "Isolated device already attached to another guest: %s": 885, + "Isolated device is not attached to this guest": 575, + "Isolated device used by server": 883, + "Isolated device used by server: %s": 886, + "IsolatedDevice %s not found": 884, + "Keypair %s not found": 751, + "Kvm snapshot missing storage ??": 1339, + "Loadbalancer's manager %s does not match vpc's(%s(%s)) (%s)": 1278, + "Loadbalancer's manager (%s(%s)) does not match vpc's(%s(%s)) (%s)": 1222, + "Local host storage is not empty???": 794, + "Master dbinstance memory <64GB, up to 5 read-only instances are allowed to be created": 1264, + "Master dbinstance memory ≥64GB, up to 10 read-only instances are allowed to be created": 1263, + "Memory size must be 8MB ~ %d GB": 730, + "Memory size must be number[+unit], like 256M, 1G or 256": 598, + "Miss operating system???": 744, + "Missing isolated device": 573, "Missing key error: missing %q": 98, "Missing name or generate_name": 42, - "Missing parameter %s": 1380, - "MissingParameterError": 1351, + "Missing parameter %s": 1411, + "MissingParameterError": 1382, "Model manager error: failed getting model manager for %q": 108, "Model not found error: cannot find %q with id/name %q": 109, "Model not found error: cannot find %q with id/name %q: %s": 110, - "Must be a baremetal host": 830, - "NIC Index %d has been occupied": 700, - "Name %s not found": 1385, - "Nat gateway has %d task active, can't sync status": 958, - "Network %s not found": 1011, - "Network %s not found: %v": 973, + "Must be a baremetal host": 849, + "NIC Index %d has been occupied": 719, + "Name %s not found": 1513, + "Nat gateway has %d task active, can't sync status": 977, + "Network %s not found": 1037, + "Network %s not found: %v": 999, "Network not found": 202, - "Network not in range of VPC cidrblock %s": 1001, - "NetworkCount fail %s": 1188, - "New IPMI address located in another zone!": 792, - "New databases name can not be one of %s": 1287, - "NewTask error: %s": 640, - "No Disk Info Provided": 543, - "No ISO to eject": 523, - "No cloudregion???": 604, + "Network not in range of VPC cidrblock %s": 1027, + "NetworkCount fail %s": 1216, + "New IPMI address located in another zone!": 811, + "New databases name can not be one of %s": 1316, + "NewTask error: %s": 659, + "No Disk Info Provided": 562, + "No ISO to eject": 542, + "No cloudregion???": 623, "No context manager": 29, - "No disk information provided": 721, - "No eip to dissociate": 601, - "No host for server": 498, - "No host???": 602, - "No ipmi information was found for host %s": 777, - "No login key: %s": 1400, - "No login secret found": 1386, - "No need to grant or revoke privilege for admin account": 1283, - "No password found": 1390, - "No previous deployment info available": 906, + "No disk information provided": 740, + "No eip to dissociate": 620, + "No host for server": 517, + "No host???": 621, + "No ipmi information was found for host %s": 796, + "No login key: %s": 1528, + "No login secret found": 1514, + "No need to grant or revoke privilege for admin account": 1312, + "No password found": 1518, + "No previous deployment info available": 925, "No request key: %s": 11, "No return value, so why query?": 36, - "No root image": 500, - "No ssh password: %s": 1391, + "No root image": 519, + "No ssh password: %s": 1519, "No such context %s(%s)": 30, - "No such eip": 957, - "No template for root disk, cannot rebuild root": 539, - "No valid cloud provider": 603, - "No valid host": 544, - "No valid storage on current host": 545, - "No zone for this disk": 365, + "No such eip": 976, + "No template for root disk, cannot rebuild root": 558, + "No valid cloud provider": 622, + "No valid host": 563, + "No valid storage on current host": 564, + "No zone for this disk": 378, "NoBalancePermission": 121, - "NoProjectError": 1372, + "NoProjectError": 1403, "Not Implement RequestAttachStorage": 183, "Not Implement RequestDetachStorage": 184, "Not Implement ValidateAttachStorage": 182, "Not Implement ValidateCreateEip": 137, "Not Implement ValidateResetDisk": 181, - "Not Implemented": 309, + "Not Implemented": 320, "Not Implemented GetProvider": 113, - "Not a baremetal": 837, - "Not a prepaid recycle host": 766, + "Not a baremetal": 856, + "Not a prepaid recycle host": 785, "Not allow empty records": 14, "Not allow for hypervisor %s": 138, "Not allow set scope to domain %s": 79, "Not allow set scope to project %s": 80, "Not allow set scope to system": 78, "Not allow to attach": 49, - "Not allow to change config": 574, + "Not allow to change config": 593, "Not allow to create item": 40, "Not allow to get details": 37, "Not allow to update item": 47, - "Not an converted hypervisor": 841, - "Not an empty host": 773, - "Not being convert to hypervisor": 838, - "Not enough free space": 351, - "Not eough storage space on current host": 546, - "Not find executor for data source": 1404, - "Not found key in query: %v": 1402, - "Not found kind in query: %v": 1401, - "Not found network by ip %s": 632, + "Not an converted hypervisor": 860, + "Not an empty host": 792, + "Not being convert to hypervisor": 857, + "Not enough free space": 364, + "Not eough storage space on current host": 565, + "Not find executor for data source": 1588, + "Not found key in query: %v": 1530, + "Not found kind in query: %v": 1529, + "Not found network by ip %s": 651, "Not in range error: invalid %q: %d, want [%d,%d]": 104, - "Not support": 395, - "Not support %s for account %s, supported %s": 398, - "Not support %s for vpc %s, supported %s": 397, - "Not support associate type %s, only support %s": 465, - "Not support brand %s, only support %s": 258, - "Not support cache classic security group": 1090, - "Not support create %s storage": 1154, - "Not support create Qcloud databases": 1322, - "Not support create account for huawei cloud %s instance": 1274, - "Not support create database for huawei cloud %s instance": 1276, + "Not support": 409, + "Not support %s for account %s, supported %s": 412, + "Not support %s for vpc %s, supported %s": 411, + "Not support associate type %s, only support %s": 484, + "Not support brand %s, only support %s": 269, + "Not support cache classic security group": 1119, + "Not support create %s storage": 1182, + "Not support create Qcloud databases": 1353, + "Not support create account for huawei cloud %s instance": 1303, + "Not support create database for huawei cloud %s instance": 1305, "Not support create local storage disks": 170, - "Not support create read-only dbinstance for %s": 1270, - "Not support create readonly dbinstance for MySQL %s": 1230, - "Not support create readonly dbinstance for MySQL %s %s": 1228, - "Not support create readonly dbinstance for MySQL %s %s with storage type %s, only support %s": 1229, - "Not support create readonly dbinstance with master dbinstance engine %s": 1233, + "Not support create read-only dbinstance for %s": 1299, + "Not support create readonly dbinstance for MySQL %s": 1259, + "Not support create readonly dbinstance for MySQL %s %s": 1257, + "Not support create readonly dbinstance for MySQL %s %s with storage type %s, only support %s": 1258, + "Not support create readonly dbinstance with master dbinstance engine %s": 1262, + "Not support modify routetable for provider %s": 1059, "Not support resource %s tag filter": 55, - "Not support resource_type %s": 1065, + "Not support resource_type %s": 1095, "Not supported, please use kubectl": 139, - "NotAcceptableError": 1365, - "NotEmptyError": 1359, + "NotAcceptableError": 1396, + "NotEmptyError": 1390, "NotFoundError": 114, "NotImplementedError": 118, - "NotSufficientPrivilegeError": 1357, + "NotSufficientPrivilegeError": 1388, "NotSupportedError": 119, "Object %s %s has attached %s %s": 50, - "Only %s guest support this operation": 687, - "Only %s support cache for account": 403, - "Only ADMIN and IPMI nic can be enable": 819, - "Only allowed to attach isolated device when guest is ready": 553, - "Only one of that sourceCIDR and netword_id is needed": 959, - "Only support cache sku for private cloud": 1116, - "Only support on premise network": 1023, - "Only support server type %s": 1022, - "Only system admin allowed to use reserved ip": 975, - "Only system admin can assign host": 855, + "Only %s dbinstance support this operation": 342, + "Only %s elastic cache support renew operation": 481, + "Only %s elastic cache support set auto renew operation": 479, + "Only %s guest support this operation": 706, + "Only %s support cache for account": 417, + "Only ADMIN and IPMI nic can be enable": 838, + "Only allowed to attach isolated device when guest is ready": 572, + "Only one of that sourceCIDR and netword_id is needed": 978, + "Only support cache sku for private cloud": 1144, + "Only support on premise network": 1049, + "Only support server type %s": 1048, + "Only system admin allowed to use reserved ip": 1001, + "Only system admin can assign host": 874, "OpenStack not support reset disk, you can create new disk with snapshot": 196, - "Out of IP address": 971, - "Out of eip quota: %s": 605, - "OutOfLimit": 1356, - "OutOfQuotaError": 1354, - "OutOfRange": 1355, - "OutOfResource": 1353, - "PTR cannot mix with other types": 416, - "PTR: invalid ptr record name: %s": 419, - "Params vcpu_count parse error": 578, - "Params vmem_size parse error": 580, - "Parse Ip Failed": 1009, - "Parse disk info error: %s": 583, + "Out of IP address": 997, + "Out of eip quota: %s": 624, + "OutOfLimit": 1387, + "OutOfQuotaError": 1385, + "OutOfRange": 1386, + "OutOfResource": 1384, + "PTR cannot mix with other types": 430, + "PTR: invalid ptr record name: %s": 433, + "Params vcpu_count parse error": 597, + "Params vmem_size parse error": 599, + "Parse Ip Failed": 1035, + "Parse disk info error: %s": 602, "Parse remote ip error %s": 201, - "Parse spec key %s error: %v": 1325, - "PaymentError": 1340, - "Please disable this ScalingGroup firstly": 1047, + "Parse spec key %s error: %v": 1356, + "PaymentError": 1371, + "Please disable this ScalingGroup firstly": 1077, "Please input new disk backend type": 171, - "PolicyDefinitionError": 1377, - "Port value error": 953, - "Prohibit making default vpc private": 1181, - "Project %s(%s) not belong to domain %s(%s)": 257, - "ProtectedResourceError": 1371, - "Qcloud Basic MySQL instance not support create backup": 1321, + "PolicyDefinitionError": 1408, + "Port value error": 972, + "Prohibit making default vpc private": 1209, + "Project %s(%s) not belong to domain %s(%s)": 267, + "ProtectedResourceError": 1402, + "Qcloud Basic MySQL instance not support create backup": 1352, "Qcloud reset disk required guest status is running or read": 197, "Query database error %s": 59, "Query host storage error %s": 189, "Quota %s not found": 67, "Records limit exceeded.": 15, - "Region %s not found": 282, - "RequireLicenseError": 1370, + "Region %s not found": 293, + "RequireLicenseError": 1401, "Rescue mode requires all disk store in shared storages": 152, - "Resize disk when disk is READY": 360, - "Resource type %s not support": 433, - "ResourceBusyError": 1369, - "ResourceNotFoundError": 1342, - "ResourceNotReadyError": 1339, - "ResourceType %q not support": 1063, - "Retention days must in 1~%d or -1": 1119, - "Retention days must in 1~65535 or -1": 1123, - "SQL Server cannot have more than seven read-only dbinstances": 1232, - "SQL Server only support create readonly dbinstance for 2017_ent": 1231, - "SRV cannot mix with other types": 414, - "SRV: insufficient param: %s": 408, - "SRV: invalid port number: %s": 409, - "SRV: invalid priority number: %s": 412, - "SRV: invalid srv record name: %s": 418, - "SRV: invalid weight number: %s": 410, - "SRV: priority number %d not in range [0,65535]": 413, - "SRV: weight number %d not in range [0,65535]": 411, - "Save disk when disk is READY": 367, - "Save disk when not being USED": 369, - "ScalingGroup should have some networks": 1036, - "Schedtag %s": 1066, - "Schedtag %s ResourceType is %s, not match %s": 1078, - "Schedtag %s not found": 768, - "Schedtag %s resource_type mismatch: %s != %s": 1067, - "Secgroup %s not found": 733, - "Server %s already exists": 629, - "Server Id is empty": 627, - "Server Name is empty": 628, - "Server in %s not able to detach disk": 551, - "ServerStatusError": 1347, - "SetLimit error %s": 241, - "Snapshot %s dose not have convert snapshot": 356, - "Snapshot %s not found": 378, - "Snapshot %s storage %s not found, is public cloud?": 379, - "Snapshot error: disk index %d > 0 but disk type is %s": 727, - "Snapshot for %s name can't start with auto, http:// or https://": 1222, - "Snapshot has %d task active, can't sync status": 1136, - "Snapshot reference(by disk) count > 0, can not delete": 1329, - "Some disk not ready": 519, - "Some host config missing host ip": 636, - "Some host config missing xml_file_path": 635, - "SpecNotFoundError": 1343, - "Split IP %s is the start ip": 1016, - "Split IP %s out of range": 1017, - "Storage %s not found": 344, - "Storage type[%s] not match backend %s": 349, - "StorageInUse": 856, - "Storage[%s] must attach to a host": 350, - "Support only by KVM Hypervisor": 501, + "Resize disk when disk is READY": 373, + "Resource type %s not support": 447, + "ResourceBusyError": 1400, + "ResourceNotFoundError": 1373, + "ResourceNotReadyError": 1370, + "ResourceType %q not support": 1093, + "Retention days must in 1~%d or -1": 1147, + "Retention days must in 1~65535 or -1": 1151, + "SQL Server cannot have more than seven read-only dbinstances": 1261, + "SQL Server only support create readonly dbinstance for 2017_ent": 1260, + "SRV cannot mix with other types": 428, + "SRV: insufficient param: %s": 422, + "SRV: invalid port number: %s": 423, + "SRV: invalid priority number: %s": 426, + "SRV: invalid srv record name: %s": 432, + "SRV: invalid weight number: %s": 424, + "SRV: priority number %d not in range [0,65535]": 427, + "SRV: weight number %d not in range [0,65535]": 425, + "Save disk when disk is READY": 380, + "Save disk when not being USED": 382, + "ScalingGroup should have some networks": 1066, + "Schedtag %s": 1096, + "Schedtag %s ResourceType is %s, not match %s": 1108, + "Schedtag %s not found": 787, + "Schedtag %s resource_type mismatch: %s != %s": 1097, + "Secgroup %s not found": 752, + "Server %s already exists": 648, + "Server Id is empty": 646, + "Server Name is empty": 647, + "Server in %s not able to detach disk": 570, + "ServerStatusError": 1378, + "SetLimit error %s": 250, + "Snapshot %s dose not have convert snapshot": 369, + "Snapshot %s not found": 391, + "Snapshot %s storage %s not found, is public cloud?": 392, + "Snapshot error: disk index %d > 0 but disk type is %s": 746, + "Snapshot for %s name can't start with auto, http:// or https://": 1251, + "Snapshot has %d task active, can't sync status": 1164, + "Snapshot reference(by disk) count > 0, can not delete": 1360, + "Some disk not ready": 538, + "Some host config missing host ip": 655, + "Some host config missing xml_file_path": 654, + "SpecNotFoundError": 1374, + "Split IP %s is the start ip": 1042, + "Split IP %s out of range": 1043, + "Storage %s not found": 357, + "Storage type[%s] not match backend %s": 362, + "StorageInUse": 875, + "Storage[%s] must attach to a host": 363, + "SuggestSysRuleConfig type is empty": 1576, + "Support only by KVM Hypervisor": 520, "System disk does not support %s disk": 146, - "System disk does not support iso image, please consider using cdrom parameter": 723, - "Tag is associated with %s": 1073, - "TenantNotFoundError": 1345, + "System disk does not support iso image, please consider using cdrom parameter": 742, + "Tag is associated with %s": 1103, + "TenantNotFoundError": 1376, "The %s disk size must be in the range of %dGB ~ %dGB": 123, "The %s disk size must be in the range of 100GB ~ 16000GB": 163, "The %s disk size must be in the range of 10GB ~ 16000GB": 161, "The %s disk size must be in the range of 20GB ~ 32000GB": 164, "The %s disk size must be in the range of 50GB ~ 16000GB": 162, - "The %s guest not support public ip to eip operation": 686, - "The account %s(%s) has permission %s to the database %s(%s)": 301, - "The account has been registered": 260, - "The backend %s is already registered on port %d": 1258, + "The %s guest not support public ip to eip operation": 705, + "The account %s(%s) has permission %s to the database %s(%s)": 312, + "The account has been registered": 271, + "The backend %s is already registered on port %d": 1287, + "The dbinstance status need be %s, current is %s": 341, "The disk is locally stored and does not support detach": 172, - "The disk_size_gb must be an integer multiple of 10": 1273, - "The extranet connection is not open": 331, - "The guest %s does not have any public IP": 684, - "The guest status need be %s or %s, current is %s": 685, - "The image has been cached on storages": 246, - "The secgroup name %s does not meet the requirements, please change the name": 461, - "The specified Scheduler %s is invalid for performance sharing loadbalancer": 1216, + "The disk_size_gb must be an integer multiple of 10": 1302, + "The elastic cache status need be %s, current is %s": 478, + "The extranet connection is not open": 344, + "The guest %s does not have any public IP": 703, + "The guest status need be %s or %s, current is %s": 704, + "The image has been cached on storages": 255, + "The secgroup name %s does not meet the requirements, please change the name": 476, + "The specified Scheduler %s is invalid for performance sharing loadbalancer": 1245, "The system disk is locally stored and does not support changing configuration": 169, - "There are some guests in this ScalingGroup, please delete them firstly": 1048, - "This RBD Storage[%s/%s] has already exist": 1336, - "This scheduled task is being executed now, please try later": 1083, + "There are some guests in this ScalingGroup, please delete them firstly": 1078, + "This RBD Storage[%s/%s] has already exist": 1367, + "This scheduled task is being executed now, please try later": 1113, "TimeoutError": 117, - "TooLargeEntity": 1373, - "TooManyFailedAttempts": 1374, - "TooManyRequests": 1375, + "TooLargeEntity": 1404, + "TooManyFailedAttempts": 1405, + "TooManyRequests": 1406, "Ucloud only support data disk reset operation": 199, "Ucloud reset disk operation required disk not be attached": 198, - "Unauthorized": 1383, - "UnauthorizedError": 1362, - "Unavailable IP %s: occupied": 652, - "Unknown backend group type %s": 1204, + "Unauthorized": 1511, + "UnauthorizedError": 1393, + "Unavailable IP %s: occupied": 671, + "Unknown alert condition": 1592, + "Unknown backend group type %s": 1233, "Unknown google storage type %s": 145, - "Unknown privilege %s": 1244, - "Unknown sticky_session_type, only support %s or %s": 1214, - "Unmarshal data error %s": 634, - "Unmarshal disks configure error %s": 582, - "Unmarshal input error %s": 507, - "Unmarshal input error: %v": 334, - "Unmarshal input failed %s": 952, - "Unmarshel input failed %s": 1122, - "Unreachable IP %s: %s": 651, + "Unknown privilege %s": 1273, + "Unknown sticky_session_type, only support %s or %s": 1243, + "Unkown alert condition type: %s": 1595, + "Unkown operator %s": 1596, + "Unmarshal data error %s": 653, + "Unmarshal disks configure error %s": 601, + "Unmarshal input error %s": 526, + "Unmarshal input error: %v": 347, + "Unmarshal input failed %s": 971, + "Unmarshel input failed %s": 1150, + "Unreachable IP %s: %s": 670, "Unsupport attach %s storage for %s host": 187, - "Unsupport backendgorup type %s": 1196, - "Unsupport driver type %s": 834, - "UnsupportOperationError": 1358, - "Unsupported %s": 470, - "Unsupported provider %s": 256, - "Unsupported scheme %s": 868, - "UnsupportedProtocol": 1376, - "Update error %s": 242, - "UserNotFoundError": 1346, - "VPC %s not found": 294, - "VPC not empty, please delete nat gateway first": 1173, - "VPC not empty, please delete network first": 1171, - "VPC not ready": 997, - "VPC peering not empty, please delete vpc peering first": 1176, - "ValidateDeleteCondition error %s": 245, - "Virtual disk %s(%s) used by virtual servers": 375, + "Unsupport backendgorup type %s": 1225, + "Unsupport driver type %s": 853, + "UnsupportOperationError": 1389, + "Unsupported %s": 489, + "Unsupported notification type": 1585, + "Unsupported provider %s": 266, + "Unsupported scheme %s": 887, + "UnsupportedProtocol": 1407, + "Update error %s": 251, + "UserNotFoundError": 1377, + "VPC %s not found": 305, + "VPC not empty, please delete nat gateway first": 1201, + "VPC not empty, please delete network first": 1199, + "VPC not ready": 1023, + "VPC peering not empty, please delete vpc peering first": 1204, + "ValidateDeleteCondition error %s": 254, + "Virtual disk %s(%s) used by virtual servers": 388, "Virtual resource freezed, can't do %s": 90, - "Virtual resource type %s not support": 434, - "Virtual server is locked, cannot delete": 706, - "WeakPasswordError": 1350, - "Wire %s not found": 757, - "Wrong guest status %s": 801, + "Virtual resource type %s not support": 448, + "Virtual server is locked, cannot delete": 725, + "WeakPasswordError": 1381, + "Wire %s not found": 776, + "Wrong guest status %s": 820, "ZStack reset disk operation requried guest status is ready": 200, - "Zone %s not found": 283, - "a recycle host shoud not allocate more than 1 guest": 764, - "account %s conflict": 271, - "account %s has been cached": 404, - "account %s not share for domain %s": 489, - "account is enabled": 250, - "account is not idle": 251, - "account name can not start or end with _": 1241, - "account_privilege %s only support redis version 4.0": 1246, - "acl %s is still referred to by %d %s": 878, - "acl cidr duplicate %s": 876, - "address %s is already occupied": 967, - "address %s is not in the range of network %s(%s)": 965, - "all networks should in the same vpc. (%s).": 1250, - "allow only internal zone, got %s(%s)": 934, - "already associate with eip": 595, - "already has one network in the zone %s. (%s).": 1251, - "attach devices is not string array": 560, - "auth mode aready in status %s": 448, - "authenticate error: %v": 903, - "back and instance not in same cloudaccount": 323, - "backend group %s is default backend group": 917, - "backend group %s is still referred by %d %s": 919, - "backend group %s(%s) belongs to loadbalancer %s instead of %s": 1206, - "backend group %s(%s) belongs to loadbalancer %s, not %s": 948, - "backend group type must be normal": 1207, - "backend_group argument is missing": 1301, - "backendgroup %s not support this operation": 1200, - "backup %s(%s) not contain database %s": 321, - "backup and instance not in same cloudregion": 324, - "bad gateway ip: %v": 994, - "bad network type %q, want %q": 1306, - "bandwidth must be greater than 0": 1183, - "beyond security group quantity limit, max items %d.": 460, + "Zone %s not found": 294, + "a recycle host shoud not allocate more than 1 guest": 783, + "account %s conflict": 282, + "account %s has been cached": 418, + "account %s not enable saml auth": 259, + "account %s not share for domain %s": 508, + "account is enabled": 260, + "account is not idle": 261, + "account name 'root' is not allowed": 1343, + "account name can not start or end with _": 1270, + "account_privilege %s only support redis version 4.0": 1275, + "acl %s is still referred to by %d %s": 897, + "acl cidr duplicate %s": 895, + "address %s is already occupied": 993, + "address %s is not in the range of network %s(%s)": 991, + "alert already attached to notification": 1543, + "all networks should in the same vpc. (%s).": 1279, + "allocate ip addr: %v": 988, + "allow only internal zone, got %s(%s)": 953, + "already associate with eip": 614, + "already has one network in the zone %s. (%s).": 1280, + "app_id is empty": 1535, + "app_secret is empty": 1536, + "attach devices is not string array": 579, + "auth mode aready in status %s": 462, + "authenticate error: %v": 922, + "back and instance not in same cloudaccount": 334, + "backend group %s is default backend group": 936, + "backend group %s is still referred by %d %s": 938, + "backend group %s(%s) belongs to loadbalancer %s instead of %s": 1235, + "backend group %s(%s) belongs to loadbalancer %s, not %s": 967, + "backend group type must be normal": 1236, + "backend_group argument is missing": 1331, + "backendgroup %s not support this operation": 1229, + "backup %s(%s) not contain database %s": 332, + "backup and instance not in same cloudregion": 335, + "bad gateway ip: %v": 1020, + "bad ip": 1605, + "bad network type %q, want %q": 1336, + "bandwidth must be greater than 0": 1211, + "beyond security group quantity limit, max items %d.": 475, "body is not a json?": 41, - "bps must > 0": 657, + "bps must > 0": 676, "bucket.GetQuotaKeys %s": 222, "bucket.GetQuotaKeys fail %s": 232, - "can not bind guest from disabled guest": 491, - "can not bind or unbind disabled instance group": 683, - "can not change specification in status %s": 447, - "can not make backup in status %s": 1313, - "can not recover data from diff rds engine": 325, - "can not sync record sets in %s": 401, - "can not unbind guest from disabled guest": 492, - "can not update instance_type for public cloud %s": 1107, - "can't delete snapshot in deleting": 863, - "can't detach host in status online": 1161, - "can't find instance snapshot %s": 716, + "can not bind guest from disabled guest": 510, + "can not bind or unbind disabled instance group": 702, + "can not change specification in status %s": 461, + "can not find dashboard:%s": 1550, + "can not make backup in status %s": 1344, + "can not recover data from diff rds engine": 336, + "can not sync record sets in %s": 415, + "can not unbind guest from disabled guest": 511, + "can not update instance_type for public cloud %s": 1135, + "can't delete instance snapshot with wrong status": 882, + "can't detach host in status online": 1189, + "can't find instance snapshot %s": 735, "can't get string field": 57, "can't rebuild root for a guest with instance snapshots": 144, - "can't rescue geust %s with local storage": 661, + "can't rescue geust %s with local storage": 680, "can't resize disk for guest with instance snapshots": 142, - "can't restore elastic cache in status %s": 436, - "candidate %s out of range": 970, - "cannot allocate ifname": 698, - "cannot associate eip and instance in different provider": 600, - "cannot associate eip and instance in different region": 598, - "cannot associate eip and instance in different zone": 599, - "cannot associate eip in status %s": 594, - "cannot associate eip with same network": 474, - "cannot associate pending delete server": 471, - "cannot associate server in status %s": 473, - "cannot assoicate with eip %s: different cloudprovider": 748, - "cannot assoicate with eip %s: different region": 749, - "cannot change CPU/Memory spec in status %s": 581, - "cannot change bandwidth in status %s": 486, - "cannot change loadbalancer listener listener_port": 1312, - "cannot change loadbalancer listener listener_type": 1311, - "cannot change mac when guest is running": 566, - "cannot change to a different domain from a private cloud account": 287, - "cannot create prepaid server on prepaid resource type": 728, - "cannot delete a recycle host without active instance": 763, - "cannot derive valid ifname hint: %v": 987, - "cannot enable auto sync in status %s": 274, - "cannot find region info": 947, - "cannot migrate with cdrom": 664, - "cannot recycle in status %s": 759, - "cannot run hypervisor %s on specified host with type %s": 754, + "can't restore elastic cache in status %s": 450, + "candidate %s out of range": 996, + "cannot allocate ifname": 717, + "cannot alter name of role": 1468, + "cannot alter sysadmin user name": 1479, + "cannot alter system project name": 1465, + "cannot associate eip and instance in different provider": 619, + "cannot associate eip and instance in different region": 617, + "cannot associate eip and instance in different zone": 618, + "cannot associate eip in status %s": 613, + "cannot associate eip with same network": 493, + "cannot associate pending delete server": 490, + "cannot associate server in status %s": 492, + "cannot assoicate with eip %s: different cloudprovider": 767, + "cannot assoicate with eip %s: different region": 768, + "cannot change CPU/Memory spec in status %s": 600, + "cannot change bandwidth in status %s": 505, + "cannot change loadbalancer listener listener_port": 1342, + "cannot change loadbalancer listener listener_type": 1341, + "cannot change mac when guest is running": 585, + "cannot change to a different domain from a private cloud account": 298, + "cannot create prepaid server on prepaid resource type": 747, + "cannot delete a recycle host without active instance": 782, + "cannot delete default SQL identity provider": 1450, + "cannot delete default domain": 1427, + "cannot delete enabled idp": 1451, + "cannot delete enabled policy": 1460, + "cannot delete non-local user": 1481, + "cannot delete system policy": 1459, + "cannot delete system project": 1461, + "cannot delete system role": 1469, + "cannot delete system user": 1483, + "cannot derive valid ifname hint: %v": 1013, + "cannot enable auto sync in status %s": 285, + "cannot fetch network of guestnetwork %d": 984, + "cannot find region info": 966, + "cannot join read-only group": 1485, + "cannot join user and group in differnt domain": 1484, + "cannot leave read-only group": 1486, + "cannot migrate with cdrom": 683, + "cannot recycle in status %s": 778, + "cannot remove current user from current project": 1421, + "cannot run hypervisor %s on specified host with type %s": 773, "cannot support change azure disk name": 178, "cannot support change azure instance name": 129, "cannot support more than 1 nic": 122, - "cannot uncache non-customized images": 1148, - "cannot undo a recycle host with pending_deleted guest": 765, - "cannot undo recycle in status %s": 760, - "certificate %s is still referred to by %d %s": 929, - "charge type %s not supported": 466, - "check %s duplication fail %s": 779, - "check access_mac duplication fail %s": 782, - "check account_id duplication error %s": 263, + "cannot uncache non-customized images": 1176, + "cannot undo a recycle host with pending_deleted guest": 784, + "cannot undo recycle in status %s": 779, + "cannot update config when enabled and connected": 1443, + "cannot update config when not idle": 1444, + "cannot update in sync status": 1454, + "certificate %s is still referred to by %d %s": 948, + "channel is empty": 1537, + "charge type %s not supported": 485, + "check %s duplication fail %s": 798, + "check access_mac duplication fail %s": 801, + "check account_id duplication error %s": 274, "check agent uniqness fail %s": 205, - "check disk index uniqueness fail %s": 696, - "check disk snapshot count fail %s": 1334, - "check instance": 1109, - "check isAttach2Disk fail %s": 548, - "check mac uniqueness fail %s": 567, + "check disk index uniqueness fail %s": 715, + "check disk snapshot count fail %s": 1365, + "check instance": 1137, + "check isAttach2Disk fail %s": 567, + "check mac uniqueness fail %s": 586, "check name duplication error: %s": 53, - "check uniqness fail %s": 259, - "check uniqueness fail %s": 270, - "checkout guestdisk count fail %s": 704, - "checkout nic index uniqueness fail %s": 699, - "checkout server sku name duplicate error: %v": 1104, - "cidr %s is not in range vpc %s": 960, - "cloud account %s is not available": 372, - "cloud provider %s is not available": 371, - "cloudprovider %s %s %s %s %s not supported CrossCloud vpcpeering": 1166, - "cloudprovider %s %s %s %s %s not supported CrossRegion vpcpeering": 1167, - "cloudprovider %s not available": 345, - "cloudprovider %s(%s) disabled": 1118, - "cloudprovider %s(%s) is not available": 312, - "cloudregion %s not support create %s rds": 316, - "cloudregion %s not support create rds": 315, - "cloudregion %s(%s) not support %s scheduler": 1218, - "cluster wire affiliation does not match network's: %s != %s": 950, - "cluster zone %s does not match network zone %s ": 949, - "comment contains non-printable char: %v": 875, - "comment too long (%d>=%d)": 874, - "condition values limit (5 per rule). %d given.": 942, - "conflict database %s for instance %s(%s)": 322, - "conflict with lbagent %s(%s): %v": 888, - "count must > 0": 676, - "cpu_core_count should be range of 1~256": 1101, - "create instance snapshot failed: %s": 672, + "check name duplication fail %s": 1607, + "check uniqness fail %s": 270, + "check uniqueness fail %s": 281, + "checkout guestdisk count fail %s": 723, + "checkout nic index uniqueness fail %s": 718, + "checkout server sku name duplicate error: %v": 1132, + "cidr %s is not in range vpc %s": 979, + "cloud account %s is not available": 385, + "cloud provider %s is not available": 384, + "cloudprovider %s %s %s %s %s not supported CrossCloud vpcpeering": 1194, + "cloudprovider %s %s %s %s %s not supported CrossRegion vpcpeering": 1195, + "cloudprovider %s not available": 358, + "cloudprovider %s(%s) disabled": 1146, + "cloudprovider %s(%s) is not available": 323, + "cloudregion %s not support create %s rds": 327, + "cloudregion %s not support create rds": 326, + "cloudregion %s(%s) not support %s scheduler": 1247, + "cluster wire affiliation does not match network's: %s != %s": 969, + "cluster zone %s does not match network zone %s ": 968, + "comment contains non-printable char: %v": 894, + "comment too long (%d>=%d)": 893, + "condition values limit (5 per rule). %d given.": 961, + "conflict database %s for instance %s(%s)": 333, + "conflict with lbagent %s(%s): %v": 907, + "count must > 0": 695, + "cpu_core_count should be range of 1~256": 1129, + "create instance snapshot failed: %s": 691, + "dashboard_id is empty": 1549, "data disk not support storage type %s": 160, - "dbinstance billing type %s not support cancel expire": 338, - "dbinstance billing type is %s": 337, - "delete sku %s failed.": 1115, - "desire_instance_number should between min_instance_number and max_instance_number": 1034, - "detach devices is not string array": 561, - "disk %s has too many snapshot policy attached": 1128, - "disk %s not attached to server": 654, - "disk %s not found": 703, - "disk and snapshotpolicy should have same domain": 1260, - "disk and snapshotpolicy should have same project": 1261, - "disk has no valid storage": 362, - "disk need at least one of snapshot as backing file": 1330, - "disk size gb must in range 10 ~ 30720 Gb": 1266, - "disk.GetQuotaKeys fail %s": 363, - "dns zone can not cache in status %s": 402, - "dns zone can not uncache in status %s": 405, - "duplicate %s %s": 780, - "duplicate access_mac %s": 783, - "duplicate instanceType %s": 1113, + "dbinstance billing type %s not support cancel expire": 351, + "dbinstance billing type is %s": 350, + "default domain is protected": 1436, + "delete sku %s failed.": 1143, + "desire_instance_number should between min_instance_number and max_instance_number": 1064, + "detach devices is not string array": 580, + "disabled user": 1418, + "disk %s has too many snapshot policy attached": 1156, + "disk %s not attached to server": 673, + "disk %s not found": 722, + "disk and snapshotpolicy should have same domain": 1289, + "disk and snapshotpolicy should have same project": 1290, + "disk has no valid storage": 375, + "disk need at least one of snapshot as backing file": 1361, + "disk size gb must in range 10 ~ 30720 Gb": 1295, + "disk.GetQuotaKeys fail %s": 376, + "dns zone can not cache in status %s": 416, + "dns zone can not uncache in status %s": 419, + "domain contains external resources": 1434, + "domain is disabled": 1455, + "domain is enabled": 1428, + "domain is in use by policy": 1433, + "domain is in use by project": 1431, + "domain is in use by role": 1432, + "domain is in use by user": 1429, + "driver %s already exists": 1449, + "driver %s not supported": 1448, + "duplicate %s %s": 799, + "duplicate access_mac %s": 802, + "duplicate instanceType %s": 1141, "duplicate route cidr %s": 6, - "duplicated dnsrecord with existed dnsrecord can not distinguish by %s policy": 391, - "duplicated dnsrecord with existed dnsrecord not support": 392, - "duplicated with CNAME dnsrecord name not support": 390, - "eip %s has been associated": 747, - "eip %s not found": 596, - "eip %s status invalid %s": 746, - "eip and server are not in the same region": 477, - "eip and server are not in the same zone": 478, - "eip cannot associate in status %s": 468, - "eip cannot dissociate in status %s": 481, - "eip has been associated": 597, - "eip has been associated with instance": 467, - "eip has been binding to another instance": 955, - "eip has been binding to dnat rules": 961, - "eip has been binding to snat rules": 956, - "eip network can only exist in default vpc, got %s(%s)": 998, - "eip not supported for %s": 745, - "eip region is not found???": 476, - "eip's manager (%s(%s)) does not match vpc's(%s(%s)) (%s)": 1267, - "elasticcache billing type %s not support cancel expire": 455, - "elasticcache billing type is %s": 454, + "duplicate username": 1501, + "duplicated dnsrecord with existed dnsrecord can not distinguish by %s policy": 405, + "duplicated dnsrecord with existed dnsrecord not support": 406, + "duplicated with CNAME dnsrecord name not support": 404, + "eip %s has been associated": 766, + "eip %s not found": 615, + "eip %s status invalid %s": 765, + "eip and server are not in the same region": 496, + "eip and server are not in the same zone": 497, + "eip cannot associate in status %s": 487, + "eip cannot dissociate in status %s": 500, + "eip has been associated": 616, + "eip has been associated with instance": 486, + "eip has been binding to another instance": 974, + "eip has been binding to dnat rules": 980, + "eip has been binding to snat rules": 975, + "eip network can only exist in default vpc, got %s(%s)": 1024, + "eip not supported for %s": 764, + "eip region is not found???": 495, + "eip's manager (%s(%s)) does not match vpc's(%s(%s)) (%s)": 1296, + "elastic cache no related region found": 480, + "elastic cache sku zone (%s) and subnet zone (%s) mismatch": 1318, + "elasticcache billing type %s not support cancel expire": 470, + "elasticcache billing type is %s": 469, + "empty DN": 1415, + "empty auth request": 1494, "empty directory name": 218, - "empty host %s field": 902, - "empty host name": 894, - "empty ip list": 650, + "empty host %s field": 921, + "empty host name": 913, + "empty id": 1416, + "empty ip list": 669, "empty keys": 224, - "empty project_id/tenant_id": 1327, - "engine version mismatch: instance version %s, sku version %s": 446, - "error getting host of guest %s": 1297, - "error loadbalancer of backend group %s": 1298, + "empty name": 1417, + "empty project_id/tenant_id": 1358, + "enabled domain %s cannot be deleted": 1453, + "encrypt error %s": 1426, + "endpoint is enabled": 1438, + "engine version mismatch: instance version %s, sku version %s": 460, + "error getting host of guest %s": 1327, + "error loadbalancer of backend group %s": 1328, "esxi guest migrate require prefer_host": 143, - "every scaling policy belong to a scaling group": 1051, + "every scaling policy belong to a scaling group": 1081, "expire time is before current expire at": 209, - "fail to GetNetworks of vpc: %v": 1002, + "expired access key": 1497, + "expired token": 1490, + "fail to GetNetworks of vpc: %v": 1028, "fail to decode body": 66, - "fail to fetch hostwire by mac %s: %s": 817, - "fail to fetch netif by mac %s: %s": 816, - "fail to find storage for disk %s": 364, + "fail to decode policy data": 1458, + "fail to decode request body": 1500, + "fail to fetch hostwire by mac %s: %s": 836, + "fail to fetch netif by mac %s: %s": 835, + "fail to find storage for disk %s": 377, "fail to generate temp url: %s": 217, "fail to get http response writer from context": 31, "fail to get objects: %s": 215, - "fail to get provider driver %s": 288, + "fail to get provider driver %s": 299, "fail to mkdir: %s": 223, - "fail to parse icon url '%s'": 1097, - "failed getting guest %s": 922, - "failed parsing url %q: %v": 1409, - "failed to find %s": 534, - "failed to find SecurityGroup %s": 1406, - "failed to find acl %s": 1310, - "failed to find cloudregion for zone %s(%s)": 1112, - "failed to find disk %s": 547, - "failed to find guest %s": 910, - "failed to find host %s": 912, - "failed to find host %s to attach storage": 858, - "failed to find host for storage %s with disk %s": 343, - "failed to find loadbalancer's %s(%s) region": 1215, - "failed to find region for loadbalancer %s": 915, - "failed to find region for loadbalancer listener %s": 944, - "failed to find region for loadbalancer listener rule %s": 945, - "failed to find storage %s to attach host": 857, - "failed to find storage for disk %s": 342, + "fail to parse icon url '%s'": 1126, + "failed getting guest %s": 941, + "failed parsing url %q: %v": 1604, + "failed to find %s": 553, + "failed to find SecurityGroup %s": 1601, + "failed to find acl %s": 1340, + "failed to find cloudregion for zone %s(%s)": 1140, + "failed to find disk %s": 566, + "failed to find guest %s": 929, + "failed to find host %s": 931, + "failed to find host %s to attach storage": 877, + "failed to find host for storage %s with disk %s": 356, + "failed to find loadbalancer's %s(%s) region": 1244, + "failed to find region for loadbalancer %s": 934, + "failed to find region for loadbalancer listener %s": 963, + "failed to find region for loadbalancer listener rule %s": 964, + "failed to find storage %s to attach host": 876, + "failed to find storage for disk %s": 355, "failed to find subformat vhd for image %s, please append 'vhd' for glance options(target_image_formats)": 126, - "failed to found backendgroup for backend %s(%s)": 926, - "failed to found cloudregion %s": 1099, - "failed to found dbinstance %s": 296, - "failed to found dbinstance %s(%s) account %s: %v": 308, - "failed to found dbinstance %s(%s) database %s: %v": 299, - "failed to found disk %s": 1130, - "failed to found guest %s": 609, - "failed to found loadbalancer for listener %s(%s)": 1217, - "failed to found provider factory error: %v": 268, - "failed to found region for dbinstance %s(%s)": 298, - "failed to found region for disk's storage %s(%s)": 1131, - "failed to found region for loadbalancer backend %s": 925, + "failed to found backendgroup for backend %s(%s)": 945, + "failed to found cloudregion %s": 1219, + "failed to found dbinstance %s": 307, + "failed to found dbinstance %s(%s) account %s: %v": 319, + "failed to found dbinstance %s(%s) database %s: %v": 310, + "failed to found disk %s": 1158, + "failed to found guest %s": 628, + "failed to found loadbalancer for listener %s(%s)": 1246, + "failed to found provider factory error: %v": 279, + "failed to found region for dbinstance %s(%s)": 309, + "failed to found region for disk's storage %s(%s)": 1159, + "failed to found region for loadbalancer backend %s": 944, "failed to found storage for disk %s(%s)": 168, - "failed to found storagecache %s": 248, + "failed to found storagecache %s": 257, "failed to found system disk error: %v": 167, - "failed to found vpc for network %s(%s)": 1307, - "failed to found zone %s": 1100, - "failed to get cloudprovider for region %s(%s)": 1117, - "failed to match any skus for change config": 335, - "failed to match any skus in the network %s(%s) zone %s(%s)": 1227, - "failed to unmarshal input params: %v": 269, - "fetch disk size failed": 750, - "fetch gpu failed %s": 558, - "fetch instance snapshot error %s": 717, - "fetch lbagents of other clusters: %v": 887, - "find Wire %s error: %s": 811, - "find guest %s: %v": 897, - "find host %s: %v": 895, - "find listener of listener rule %s(%s)": 872, - "fixed eip cannot be associated": 469, - "fixed eip cannot sync status": 485, - "fixed public eip cannot be dissociated": 482, + "failed to found vpc for network %s(%s)": 1337, + "failed to get cloudprovider for region %s(%s)": 1145, + "failed to match any skus for change config": 348, + "failed to match any skus in the network %s(%s) zone %s(%s)": 1256, + "failed to unmarshal input params: %v": 280, + "fetch disk size failed": 769, + "fetch gpu failed %s": 577, + "fetch guest %s: %v": 982, + "fetch guest nic: %v": 983, + "fetch instance snapshot error %s": 736, + "fetch lbagents of other clusters: %v": 906, + "field %s is readonly": 1437, + "find Wire %s error: %s": 830, + "find guest %s: %v": 916, + "find host %s: %v": 914, + "find listener of listener rule %s(%s)": 891, + "fixed eip cannot be associated": 488, + "fixed eip cannot sync status": 504, + "fixed public eip cannot be dissociated": 501, "forbidden": 88, - "found %d wires for zone %s and vpc %s": 983, - "gateway ip must be in the same subnet as start, end ip": 995, - "get %s service %s url: %v": 905, - "get acl count fail %s": 877, - "get certificate refcount fail %s": 928, - "get isDefault fail %s": 916, - "get lbcluster refcount fail %v": 937, + "found %d wires for zone %s and vpc %s": 1009, + "gateway ip must be in the same subnet as start, end ip": 1021, + "get %s service %s url: %v": 924, + "get acl count fail %s": 896, + "get certificate refcount fail %s": 947, + "get isDefault fail %s": 935, + "get lbcluster refcount fail %v": 956, "get proxysetting refcount fail %s": 64, - "get refCount fail %s": 918, - "get reserved ip error": 982, - "getDynamicSchedtagCount fail %s": 1074, - "getFreeAddressCount fail %s": 968, - "getGuestCount fail %s": 772, - "getReferenceCount fail %s": 1141, - "getSchedPoliciesCount fail %s": 1076, - "group %s not found": 705, - "group and guest should belong to same project": 682, - "guest %q not found": 339, - "guest %s band to up to %d security groups": 526, - "guest %s has backup, can't migrate": 660, - "guest %s host %s isolated device not enough": 559, - "guest %s hypervisor %s can't migrate": 659, - "guest %s not found": 1129, - "guest %s status %s can't migrate": 662, - "guest %s status %s can't migrate with local storage": 665, - "guest %s status %s has isolated device, can't do migrate": 663, - "guest %s unsupport postpaid expire": 625, - "guest %s(%s) is already in the backendgroup %s(%s)": 924, - "guest %s(%s) vpc %s(%s) not same as loadbalancer vpc %s": 921, - "guest %s(%s) vpc %s(%s) not same as vpc %s(%s)": 923, - "guest and instance group should belong to same project": 494, - "guest attach gpu count must > 0": 557, - "guest billing type %s not support cancel expire": 623, - "guest billing type is %s": 624, - "guest can't do snapshot in status %s": 670, - "guest doesn't need reconcile backup": 622, - "guest has been converted": 645, - "guest hypervisor %s can't create instance snapshot": 668, - "guest on the host are using networks on this wire": 862, - "guest status must be ready": 647, - "guest template %s used by scalig group %s": 695, - "guest template %s used by service catalog %s": 694, - "guests disk %d snapshot full, can't take anymore": 671, - "health_check_domain must be in the range of 1 ~ 80": 1210, - "host %s can't reserve %d cpu for each isolated device, not enough": 852, - "host %s can't reserve %dM memory for each isolated device, not enough": 853, - "host %s can't reserve %dM storage for each isolated device, not enough": 854, - "host %s has no access ip": 1296, - "host %s is not kvm host": 646, - "host %s not found": 701, - "host %s storage %s not found": 1162, + "get refCount fail %s": 937, + "get reserved ip error": 1008, + "get sensitive config requires admin priviliges": 1442, + "getDynamicSchedtagCount fail %s": 1104, + "getFreeAddressCount fail %s": 994, + "getGuestCount fail %s": 791, + "getReferenceCount fail %s": 1169, + "getSchedPoliciesCount fail %s": 1106, + "got unknown parent type %q, expect %s": 987, + "got unknown type %q, expect %s": 986, + "group %s not found": 724, + "group and guest should belong to same project": 701, + "group is in use by group": 1430, + "guest %q not found": 352, + "guest %s band to up to %d security groups": 545, + "guest %s has backup, can't migrate": 679, + "guest %s host %s isolated device not enough": 578, + "guest %s hypervisor %s can't migrate": 678, + "guest %s not found": 1157, + "guest %s status %s can't migrate": 681, + "guest %s status %s can't migrate with local storage": 684, + "guest %s status %s has isolated device, can't do migrate": 682, + "guest %s unsupport postpaid expire": 644, + "guest %s(%s) is already in the backendgroup %s(%s)": 943, + "guest %s(%s) vpc %s(%s) not same as loadbalancer vpc %s": 940, + "guest %s(%s) vpc %s(%s) not same as vpc %s(%s)": 942, + "guest and instance group should belong to same project": 513, + "guest attach gpu count must > 0": 576, + "guest billing type %s not support cancel expire": 642, + "guest billing type is %s": 643, + "guest can't do snapshot in status %s": 689, + "guest doesn't need reconcile backup": 641, + "guest has been converted": 664, + "guest hypervisor %s can't create instance snapshot": 687, + "guest on the host are using networks on this wire": 881, + "guest status must be ready": 666, + "guest template %s used by scalig group %s": 714, + "guest template %s used by service catalog %s": 713, + "guests disk %d snapshot full, can't take anymore": 690, + "health_check_domain must be in the range of 1 ~ 80": 1239, + "host %s can't reserve %d cpu for each isolated device, not enough": 871, + "host %s can't reserve %dM memory for each isolated device, not enough": 872, + "host %s can't reserve %dM storage for each isolated device, not enough": 873, + "host %s has no access ip": 1326, + "host %s is not kvm host": 665, + "host %s not found": 720, + "host %s storage %s not found": 1190, "host has been occupied": 175, - "host is not a baremetal": 680, - "host is not a prepaid recycle host": 762, - "host missing %s field": 901, - "host not connect storage %s": 584, - "host not found???": 649, - "host should be disabled": 761, - "host status %s and enabled %v, can't do server %s": 496, - "host status %s can't exit maintenance": 842, - "host type %s can't do host maintenance": 843, - "host_type must be specified": 829, - "http or https listener only supportd default or normal backendgroup": 1209, - "huawei %s mode elastic not support create backup": 1291, - "iBucket.GetIObject error %s": 244, + "host is not a baremetal": 699, + "host is not a prepaid recycle host": 781, + "host missing %s field": 920, + "host not connect storage %s": 603, + "host not found???": 668, + "host should be disabled": 780, + "host status %s and enabled %v, can't do server %s": 515, + "host status %s can't exit maintenance": 861, + "host type %s can't do host maintenance": 862, + "host_type must be specified": 848, + "http or https listener only supportd default or normal backendgroup": 1238, + "huawei %s mode elastic not support create backup": 1321, + "iBucket.DeleteCORS error %s": 244, + "iBucket.DeleteWebSiteConf error %s": 241, + "iBucket.GetCORSRules error %s": 245, + "iBucket.GetCdnDomains error %s": 246, + "iBucket.GetIObject error %s": 253, "iBucket.GetIObjects error %s": 239, - "image %s do not belong to guest image %s": 751, - "image %s not found": 827, - "image size exceeds root disk size": 536, - "inconsistent account_id, previous '%s' and now '%s'": 272, - "input data not key value dict": 610, + "iBucket.GetRefer error %s": 248, + "iBucket.GetWebsiteConf error %s": 242, + "iBucket.SetCORS error %s": 243, + "iBucket.SetRefer error %s": 247, + "iBucket.SetWebsite error %s": 240, + "identity provider with projects": 1452, + "image %s do not belong to guest image %s": 770, + "image %s not found": 846, + "image size exceeds root disk size": 555, + "inconsistent account_id, previous '%s' and now '%s'": 283, + "inconsistent domain for project and roles": 1474, + "input condition is empty": 1594, + "input data not key value dict": 629, "input key too long > %d": 85, + "input not json dict": 1568, "input value too long > %d": 86, - "instance is already associated with eip": 472, - "instance specs list query error": 1106, - "instance_type_category shoud be one of %s": 1103, - "internal error: unexpected backend type %s": 1199, - "intranet loadbalancer not support bandwidth charge type": 1192, - "invalid %s,required int": 1219, - "invalid addr %s": 873, - "invalid address: %s": 310, - "invalid aggregate_strategy: %s": 756, - "invalid any_mac address": 767, - "invalid billing_cycle %s": 438, + "instance is already associated with eip": 491, + "instance specs list query error": 1134, + "instance_type_category shoud be one of %s": 1131, + "internal error: unexpected backend type %s": 1228, + "internal server error %s": 1505, + "intranet loadbalancer not support bandwidth charge type": 1221, + "invalid %s,required int": 1248, + "invalid access key id": 1496, + "invalid addr %s": 892, + "invalid address: %s": 321, + "invalid aggregate_strategy: %s": 775, + "invalid any_mac address": 786, + "invalid auth methods": 1492, + "invalid billing_cycle %s": 452, "invalid bucket name %s: %s": 212, "invalid bucket name(%s): %s": 213, - "invalid category %s for policy definition %s(%s)": 742, + "invalid category %s for policy definition %s(%s)": 761, "invalid cert pubkey algorithm: %s, want %s": 111, - "invalid character %s for account name": 1240, - "invalid cidr_block %s": 1177, - "invalid cloud account info error: %s": 262, - "invalid condition": 430, - "invalid conditions format,required json": 940, - "invalid conditions fromat,required json array": 941, + "invalid character %s for account name": 1269, + "invalid cidr %s": 1056, + "invalid cidr_block %s": 1205, + "invalid cloud account info error: %s": 273, + "invalid condition": 444, + "invalid conditions format,required json": 959, + "invalid conditions fromat,required json array": 960, + "invalid domain": 1506, "invalid domain %s for CNAME record": 5, "invalid domain %s for MX record": 2, - "invalid domain name %s": 396, - "invalid duration %s": 313, + "invalid domain name %s": 410, + "invalid duration %s": 324, "invalid duration %s: %s": 210, - "invalid end ip: %s %s": 991, - "invalid external_access_mode %q, want %s": 1169, + "invalid end ip: %s %s": 1017, + "invalid external_access_mode %q, want %s": 1197, + "invalid fernet token": 1491, "invalid format": 95, - "invalid guest %s": 1197, - "invalid input %s": 267, + "invalid guest %s": 1226, + "invalid input %s": 278, "invalid input format": 97, - "invalid internal ip address: %s": 954, - "invalid ip address: %s": 1091, - "invalid ipaddr %s": 812, + "invalid internal ip address: %s": 973, + "invalid ip address: %s": 1120, + "invalid ipaddr %s": 831, "invalid ipv4 %s for A record": 3, "invalid ipv6 %s for AAAA record": 4, "invalid joint resources %s": 27, "invalid key %s: %s": 219, - "invalid loadbalancer backend port '%d'": 1045, - "invalid loadbalancer backend weight '%d'": 1046, - "invalid loadbalancer_spec %s": 1254, - "invalid local certificate, certificate is empty.": 931, - "invalid local certificate, private key is empty.": 930, - "invalid macAddr %s": 781, + "invalid loadbalancer backend port '%d'": 1075, + "invalid loadbalancer backend weight '%d'": 1076, + "invalid loadbalancer_spec %s": 1283, + "invalid local certificate, certificate is empty.": 950, + "invalid local certificate, private key is empty.": 949, + "invalid macAddr %s": 800, "invalid object key: %s": 226, - "invalid parameter backendgroup %s": 1253, - "invalid parameter format. json dict required": 452, - "invalid parameter loadbalancer_spec %s": 1252, - "invalid parameters for policy definition %s": 736, - "invalid policy definition %s(%s) condition %s": 739, - "invalid proxy setting %s": 254, - "invalid public error: %v": 869, - "invalid public_ip_charge_type %s": 744, - "invalid resources format": 1392, + "invalid parameter backendgroup %s": 1282, + "invalid parameter format. json dict required": 467, + "invalid parameter loadbalancer_spec %s": 1281, + "invalid parameters for policy definition %s": 755, + "invalid password: %s": 1480, + "invalid policy definition %s(%s) condition %s": 758, + "invalid project": 1504, + "invalid proxy setting %s": 264, + "invalid public error: %v": 888, + "invalid public_ip_charge_type %s": 763, + "invalid record name %s": 403, + "invalid resources format": 1520, "invalid scope %s": 76, "invalid share_mode %s": 0, - "invalid status %s": 1026, - "invalid strategy %s": 1062, - "invalid ttl: %d": 428, - "invalid ttl: %s": 427, - "invalid vrrp advert_int %d: want [1,255]": 883, - "invalid vrrp authentication pass size: %d, want [1,8]": 880, - "invalid vrrp interface %q": 879, - "invalid vrrp priority %d: want [1,255]": 881, - "invalid vrrp virtual_router_id %d: want [1,255]": 882, - "invlid image": 535, - "iops must > 0": 658, - "ip": 1021, - "ip %s not found": 562, - "ip %s or mac %s has been registered": 631, - "ip_prefix error: %s": 988, - "ipv4 range overlap": 1165, - "isAddressUsed fail %s": 966, - "isAlterNameUnique fail %s": 835, - "isAttached check failed %s": 511, - "keypair %s not found": 542, - "lbagent cannot be deployed on managed host": 896, - "lbagent cannot be deployed on public guests": 898, - "lbcluster %s(%s) already has virtual_router_id %d": 889, - "lbcluster %s(%s) is still referred to by %d %s": 938, - "lbclusters %s(%s) and %s(%s) has conflict virtual_router_id: %d ": 939, - "listener type must be http/https, got %s": 1205, - "loadbalancer aready associated with fourth layer listener %s": 1317, - "loadbalancer backendgroup aready associate with other %s listener": 1315, - "loadbalancer is locked, cannot delete": 951, - "loadbalancer is using by %d backendgroup.": 1269, - "loadbalancer is using by %d listener.": 1268, - "loadbalancer listener %s is already updating": 1314, - "loadbalancer listener %s related loadbalancer %s not found": 1257, - "loadbalancerlistenerrule %s(%s): fetching listener %s failed": 1208, - "login_account is longer than 32 chars": 720, - "mac %s not found": 563, - "mac addr %s has been occupied": 568, - "maintain time has no change": 449, - "managed network cannot change status": 1025, - "mapped ip exhausted": 1182, - "master slave backendgorup must contain two backend": 1195, - "memory_size_mb, shoud be range of 512~%d": 1102, - "metdata must less then 20": 719, - "min_instance_number should not be bigger than max_instance_number": 1033, - "min_instance_number should not be smaller than 0": 1032, - "mismatched alarm id": 1057, - "miss some subimage of guest image": 752, + "invalid status %s": 1052, + "invalid strategy %s": 1092, + "invalid template": 1446, + "invalid token": 1508, + "invalid token %s": 1509, + "invalid ttl: %d": 442, + "invalid ttl: %s": 441, + "invalid url: %v": 1533, + "invalid user": 1503, + "invalid vrrp advert_int %d: want [1,255]": 902, + "invalid vrrp authentication pass size: %d, want [1,8]": 899, + "invalid vrrp interface %q": 898, + "invalid vrrp priority %d: want [1,255]": 900, + "invalid vrrp virtual_router_id %d: want [1,255]": 901, + "invlid image": 554, + "iops must > 0": 677, + "ip": 1047, + "ip %s not found": 581, + "ip %s or mac %s has been registered": 650, + "ip_prefix error: %s": 1014, + "ipv4 range overlap": 1193, + "isAddressUsed fail %s": 992, + "isAlterNameUnique fail %s": 854, + "isAttached check failed %s": 530, + "join group into project of default domain or identical domain": 1422, + "join user into project of default domain or identical domain": 1419, + "keypair %s not found": 561, + "lbagent cannot be deployed on managed host": 915, + "lbagent cannot be deployed on public guests": 917, + "lbcluster %s(%s) already has virtual_router_id %d": 908, + "lbcluster %s(%s) is still referred to by %d %s": 957, + "lbclusters %s(%s) and %s(%s) has conflict virtual_router_id: %d ": 958, + "listener type must be http/https, got %s": 1234, + "loadbalancer aready associated with fourth layer listener %s": 1348, + "loadbalancer backendgroup aready associate with other %s listener": 1346, + "loadbalancer is locked, cannot delete": 970, + "loadbalancer is using by %d backendgroup.": 1298, + "loadbalancer is using by %d listener.": 1297, + "loadbalancer listener %s is already updating": 1345, + "loadbalancer listener %s related loadbalancer %s not found": 1286, + "loadbalancerlistenerrule %s(%s): fetching listener %s failed": 1237, + "login_account is longer than 32 chars": 739, + "mac %s not found": 582, + "mac addr %s has been occupied": 587, + "maintain time has no change": 464, + "managed network cannot change status": 1051, + "mapped ip exhausted": 1210, + "master slave backendgorup must contain two backend": 1224, + "memory_size_mb, shoud be range of 512~%d": 1130, + "metdata must less then 20": 738, + "metric %s is invalid format, usage .": 1541, + "min_instance_number should not be bigger than max_instance_number": 1063, + "min_instance_number should not be smaller than 0": 1062, + "mismatched alarm id": 1087, + "miss some subimage of guest image": 771, "missing Content-Length": 227, - "missing access_mac and uuid in no_probe mode": 790, + "missing access_mac and uuid in no_probe mode": 809, + "missing driver": 1447, "missing duration/expire_time": 208, - "missing guest id": 666, - "missing image id or name": 1151, + "missing guest id": 685, + "missing image id or name": 1179, + "missing input feild type": 1424, + "missing input field blob": 1425, + "missing input field id": 1467, + "missing input field interface": 1439, + "missing input field service/service_id": 1440, + "missing input field type": 1457, "missing key": 216, - "missing manager?": 243, + "missing manager?": 252, "missing name": 211, "missing new domain": 52, "missing new project/tenant": 93, - "missing pid in pids": 1396, - "missing pids": 1395, - "missing rid": 1398, - "missing rid in pids": 1397, - "missing uid": 1394, - "missong duration": 329, + "missing pid in pids": 1524, + "missing pids": 1523, + "missing rid": 1526, + "missing rid in pids": 1525, + "missing uid": 1522, + "missong duration": 340, "model has no field %s": 58, - "mtu must be range of 0~1000000": 1184, + "mtu must be range of 0~1000000": 1212, "mx_priority range limited to [1,50]": 1, - "name is too short": 715, + "name is too short": 734, "name longer than %d": 83, "name starts with letter, and contains letter, number and - only": 60, "name starts with letter, and contains letter, number and ._@- only": 82, - "need scheduled task": 1084, - "need valid access_mac and uuid to do prepare": 803, + "need scheduled task": 1114, + "need valid access_mac and uuid to do prepare": 822, "network %s associated route table has no internet gateway attached.": 125, - "network %s related vpc not found": 1245, - "network %s(%s) does not belong to %s": 1308, - "network %s(%s) has no free addresses": 969, - "network '%s' not in vpc '%s'": 1038, - "network server_type %s not support auto alloc": 1008, - "no allow to access network %s": 972, - "no available eip network": 1305, - "no either ip_addr or mac specified": 564, + "network %s related vpc not found": 1274, + "network %s(%s) does not belong to %s": 1338, + "network %s(%s) has no free addresses": 995, + "network '%s' not in vpc '%s'": 1068, + "network server_type %s not support auto alloc": 1034, + "no admin account found for elastic cache %s": 463, + "no allow to access network %s": 998, + "no available eip network": 1335, + "no either ip_addr or mac specified": 583, "no external bucket": 214, - "no networks on wire %s": 813, - "no recovery secrets for %s": 1388, - "no such ScalingGroup '%s'": 1049, - "no such cloud region %s": 1035, - "no such disk %s": 1126, - "no such group %s": 681, - "no such guest template": 1096, - "no such guest template %s": 1039, - "no such guest_template %s": 1098, - "no such loadbalancer backend group '%s'": 1044, - "no such model %s": 493, - "no such network": 962, + "no found rule setting": 1574, + "no networks on wire %s": 832, + "no recovery secrets for %s": 1516, + "no such ScalingGroup '%s'": 1079, + "no such cloud region %s": 1065, + "no such disk %s": 1154, + "no such driver": 1414, + "no such group %s": 700, + "no such guest template": 1125, + "no such guest template %s": 1069, + "no such guest_template %s": 1127, + "no such loadbalancer backend group '%s'": 1074, + "no such model %s": 512, + "no such network": 981, "no such provider": 112, - "no such provider %s": 261, - "no such scaling group %s": 1052, - "no such snapshotpolicy %s": 386, - "no support for instance snapshot in guest template for now": 688, - "no totp for %s": 1387, - "no valid endpoint": 1407, - "no valid host": 679, - "no valid storage on host": 755, - "no viable lbcluster": 1295, - "non http listener must have backend group set": 1304, - "non redirect lblistener rule must have backend_group set": 1302, + "no such provider %s": 272, + "no such scaling group %s": 1082, + "no such snapshotpolicy %s": 399, + "no support for instance snapshot in guest template for now": 707, + "no totp for %s": 1515, + "no valid endpoint": 1602, + "no valid host": 698, + "no valid storage on host": 774, + "no viable lbcluster": 1325, + "non http listener must have backend group set": 1334, + "non redirect lblistener rule must have backend_group set": 1332, "non-admin user not allowed to create system object": 89, - "not a baremetal": 802, - "not a baremetal server": 678, - "not a valid ip address %s: %s": 980, + "not a baremetal": 821, + "not a baremetal server": 697, + "not a valid ip address %s: %s": 1006, "not allow create %s in scope %s": 77, - "not allow to change project across domain": 286, - "not allow to create": 278, - "not allow to delete %s disk with snapshots": 385, - "not allow to delete default cloud region": 293, - "not allow to delete default security group": 1095, - "not allow to delete default vpc": 1174, + "not allow to auth": 1507, + "not allow to change project across domain": 297, + "not allow to create": 289, + "not allow to delete %s disk with snapshots": 398, + "not allow to delete default cloud region": 304, + "not allow to delete default security group": 1124, + "not allow to delete default vpc": 1202, "not allow to delete log": 61, - "not allow to delete prepaid disk in valid status": 376, - "not allow to delete prepaid server in valid status": 707, - "not allow to delete public cloud instance_type: %s": 1111, - "not allow to delete. Virtual disk must not have snapshots": 384, + "not allow to delete prepaid disk in valid status": 389, + "not allow to delete prepaid server in valid status": 726, + "not allow to delete public cloud instance_type: %s": 1139, + "not allow to delete. Virtual disk must not have snapshots": 397, + "not allow to get usage": 1510, "not allow to list domain quotas": 70, "not allow to list project quotas": 72, "not allow to perform %s": 43, - "not allow to purge. Virtual disk must not have snapshots": 383, - "not allow to query system capability": 249, + "not allow to purge. Virtual disk must not have snapshots": 396, + "not allow to query system capability": 258, "not allow to set system key, please remove the underscore at the beginning": 84, - "not allowed update content of certificate": 933, + "not allowed update content of certificate": 952, "not an empty bucket": 237, - "not an empty network %s": 964, - "not empty cloud region": 292, - "not empty zone": 1190, - "not enough privilege": 1085, + "not an empty network %s": 990, + "not empty cloud region": 303, + "not empty zone": 1218, + "not enough privilege": 1057, "not enough privilege (require:%s,allow:%s)": 75, "not enough privilege (require:%s,allow:%s,query:%s)": 54, "not enough privilege (require:%s,allow:%s:resource:%s)": 74, "not enough privilleges": 69, + "not find alert %s": 1547, + "not find notification %s": 1548, + "not found alert notification used by %s": 1565, + "not found cert %s": 1441, + "not found driver by type %q": 1579, + "not found res_id %q": 1571, + "not found resource_type %q": 1578, + "not found rule by type %q": 1583, + "not found signature": 1569, + "not found type %q": 1572, "not implement": 180, - "not match any dbinstance sku": 317, - "not support %s": 644, - "not support create": 1028, - "not support create %s zone": 1191, - "not support create definition": 1029, - "not support for cloudaccount with provider '%s'": 255, - "not support hypervisor %s": 643, - "not support update disk_type %s": 341, - "not supported bind security group": 459, - "not supported hypervisor %s": 769, - "now allow to delete inuse instance_type.please remove related servers first: %s": 1110, + "not match any dbinstance sku": 328, + "not support %s": 663, + "not support create": 1054, + "not support create %s zone": 1220, + "not support create definition": 1055, + "not support database": 1562, + "not support for cloudaccount with provider '%s'": 265, + "not support hypervisor %s": 662, + "not support type %q": 1563, + "not support update disk_type %s": 354, + "not supported bind security group": 474, + "not supported hypervisor %s": 788, + "not supported next hop type": 1058, + "not supported secondary update context %s": 1475, + "not supported update context": 1472, + "not supported update context %s": 1473, + "now allow to delete inuse instance_type.please remove related servers first: %s": 1138, "object %s not found": 238, "object count limit exceeds": 221, "object key should not ends with /": 225, "object size limit exceeds": 231, - "on-premise network cannot sync status": 1024, - "on-premise vpc cannot sync status": 1179, - "only on premise support this operation": 1014, - "only sysadmin can specify host as backend": 911, + "on-premise network cannot sync status": 1050, + "on-premise vpc cannot sync status": 1207, + "only on premise support this operation": 1040, + "only sysadmin can specify host as backend": 930, "operation not allowed": 96, "out of privileges": 71, - "parse cdrom device info error %s": 724, - "parse disk description error %s": 726, - "parse isolated device description error %s": 731, - "parse network description error %s": 729, - "password must be 12 chars of at least one digit, letter, uppercase letter and punctuate": 1379, - "path can not be emtpy": 1318, - "peer lbagent %s(%s) already has vrrp priority %d": 892, - "please retry after unbind all guests in group": 490, - "policy definition %s require cloudregion in %s": 737, - "policy definition %s require cloudregion not in %s": 738, - "policy definition %s require except tag %s": 741, - "policy definition %s require must contains tag %s": 740, - "port %d not support, only support range 1 ~ 65535": 909, - "project %s not found": 273, + "parameter %s is empty": 1538, + "parse cdrom device info error %s": 743, + "parse disk description error %s": 745, + "parse isolated device description error %s": 750, + "parse network description error %s": 748, + "password must be 12 chars of at least one digit, letter, uppercase letter and punctuate": 1410, + "path can not be emtpy": 1349, + "peer lbagent %s(%s) already has vrrp priority %d": 911, + "please retry after unbind all guests in group": 509, + "policy definition %s require cloudregion in %s": 756, + "policy definition %s require cloudregion not in %s": 757, + "policy definition %s require except tag %s": 760, + "policy definition %s require must contains tag %s": 759, + "policy is referenced": 1608, + "port %d not support, only support range 1 ~ 65535": 928, + "project %s not found": 284, + "project contains external resources": 1462, + "project contains group": 1464, + "project contains user": 1463, + "project disabled": 1488, "project in non-default domain is prohibited": 51, - "project is not found": 1399, - "provider %s: %v": 252, - "provider is enabled": 279, - "provider is not idle": 280, - "provider is shared outside of domain": 275, - "provider mismatch: %s instance can't use %s sku": 443, + "project is not found": 1527, + "project or domain is empty": 1577, + "provider %s: %v": 262, + "provider is enabled": 290, + "provider is not idle": 291, + "provider is shared outside of domain": 286, + "provider mismatch: %s instance can't use %s sku": 457, "proxysetting %s is still referred to by %d %s": 65, - "public connection aready allocated": 450, - "public ip not supported for %s": 743, + "public connection aready allocated": 465, + "public ip not supported for %s": 762, "put object error %s": 233, - "query all networks fail": 1004, - "query backend group releated resource failed.": 907, + "query all networks fail": 1030, + "query backend group releated resource failed.": 926, + "query duration `to` err: %s": 1599, + "query duration err: from: %s, to:%s": 1598, + "query error %s": 1423, "query quotas %s": 73, - "query sku list failed.": 1114, - "redirect can only be enabled for http/https listener": 1303, - "redirect must have at least one of scheme, host, path changed": 1300, - "redis version 2.8 not support create account": 1323, - "referered by storages": 1147, + "query sku list failed.": 1142, + "readonly": 1435, + "redirect can only be enabled for http/https listener": 1333, + "redirect must have at least one of scheme, host, path changed": 1330, + "redis version 2.8 not support create account": 1354, + "referered by storages": 1175, "reflect call %s fail %s": 33, - "region": 457, - "region mismatch: instance region %s, sku region %s": 444, - "region of backend %d does not match that of lb's": 914, - "region of host %q (%s) != region of loadbalancer %q (%s)": 1299, - "region of host %q (%s) != region of loadbalancer %q (%s))": 1201, - "regiondriver": 458, - "release public connection aready released": 451, - "repeat_weekdays only contains %d days at most": 1120, + "region": 472, + "region contains endpoints": 1466, + "region mismatch: instance region %s, sku region %s": 458, + "region of backend %d does not match that of lb's": 933, + "region of host %q (%s) != region of loadbalancer %q (%s)": 1329, + "region of host %q (%s) != region of loadbalancer %q (%s))": 1230, + "regiondriver": 473, + "release public connection aready released": 466, + "repeat_weekdays only contains %d days at most": 1148, "request process timeout": 13, - "require system previleges to convert host in other domain": 833, - "require validated qcloud cross region vpcPeering bandwidth values:[10, 20, 50, 100, 200, 500, 1000],unit Mbps": 1408, - "required at least %d subnet with at least 8 free ip.": 1248, - "required at least %d subnet.": 1247, - "reserved cpu must >= 0": 849, - "reserved memory must >= 0": 850, - "reserved storage must >= 0": 851, - "rule %d is invalid: %s": 1088, - "rule %s/%s already occupied by rule %s(%s)": 943, - "schedtag %s not found": 431, - "schedtag_id not provide": 1064, - "secgroup %s not found": 702, - "secgroup %s rules not equals %s rules": 1092, - "secgroups will be empty after update.": 462, - "security group %s has already been assigned to guest %s": 527, - "security group %s not assigned to guest %s": 529, - "security group id should not be empty": 1405, - "server %s not found": 464, - "server %s with port %d already in used": 1319, - "server %s with port %d aready used by other %s listener": 1320, - "server and eip are not managed by the same provider": 480, - "server host is not found???": 479, - "server is in %q state, want %q": 899, - "server region is not found???": 475, - "service %s not found error: %v": 1393, + "require system previleges to convert host in other domain": 852, + "require validated qcloud cross region vpcPeering bandwidth values:[10, 20, 50, 100, 200, 500, 1000],unit Mbps": 1603, + "required at least %d subnet with at least 8 free ip.": 1277, + "required at least %d subnet.": 1276, + "reserved cpu must >= 0": 868, + "reserved memory must >= 0": 869, + "reserved storage must >= 0": 870, + "resource is enabled": 1456, + "resource type %q of driver does not match input %q": 1580, + "resource type must provided when resource_id specified": 1581, + "role is being assigned to group": 1471, + "role is being assigned to user": 1470, + "rule %d is invalid: %s": 1117, + "rule %s/%s already occupied by rule %s(%s)": 962, + "saveConfig fail %s": 1445, + "schedtag %s not found": 445, + "schedtag_id not provide": 1094, + "secgroup %s not found": 721, + "secgroup %s rules not equals %s rules": 1121, + "secgroups will be empty after update.": 477, + "security group %s has already been assigned to guest %s": 546, + "security group %s not assigned to guest %s": 548, + "security group id should not be empty": 1600, + "select for nothing in query": 1597, + "server %s not found": 483, + "server %s with port %d already in used": 1350, + "server %s with port %d aready used by other %s listener": 1351, + "server and eip are not managed by the same provider": 499, + "server host is not found???": 498, + "server is in %q state, want %q": 918, + "server region is not found???": 494, + "service %s not found error: %v": 1521, + "service contains endpoints": 1476, + "service is enabled": 1477, "setAcl error %s": 234, - "sku %s is soldout": 1289, - "slave dbinstance not support prepaid billing type": 1226, - "snapshot referenced by instance snapshot": 1134, - "snapshotpolicy %s not found: %s": 340, - "snapshotpolicy disk has been exist": 1127, - "some disk missing!!!": 653, - "some networks not exist": 1037, - "start and end ip not in the same subnet": 992, - "start and end ip when masked are not in the same cidr subnet": 1000, - "start create snapshot task failed: %s": 673, - "start snapshot reset failed %s": 675, - "start, end ip must be in the same subnet": 1007, - "sticky_session_cookie can only contain letters, Numbers, '_' and '-'": 1213, - "sticky_session_cookie length must within 1~200": 1212, + "signature error": 1570, + "sku %s is soldout": 1319, + "slave dbinstance not support prepaid billing type": 1255, + "snapshot referenced by instance snapshot": 1162, + "snapshotpolicy %s not found: %s": 353, + "snapshotpolicy disk has been exist": 1155, + "some disk missing!!!": 672, + "some networks not exist": 1067, + "start and end ip not in the same subnet": 1018, + "start and end ip when masked are not in the same cidr subnet": 1026, + "start create snapshot task failed: %s": 692, + "start snapshot reset failed %s": 694, + "start, end ip must be in the same subnet": 1033, + "sticky_session_cookie can only contain letters, Numbers, '_' and '-'": 1242, + "sticky_session_cookie length must within 1~200": 1241, "storage %s can not be data disk": 165, - "storage %s(%s) need online and attach host for create disk": 346, - "storage cache is missing": 1159, - "storage cache not empty": 1146, - "storage classes not supported": 289, - "storage has associate hosts": 1156, - "storage has disks": 1157, - "storage has snapshots": 1158, - "storage is enabled": 1160, - "storage not cache image": 1149, - "storage of disk %s no valid host": 373, - "subnet masklen should be smaller than 30": 999, + "storage %s(%s) need online and attach host for create disk": 359, + "storage cache is missing": 1187, + "storage cache not empty": 1174, + "storage classes not supported": 300, + "storage has associate hosts": 1184, + "storage has disks": 1185, + "storage has snapshots": 1186, + "storage is enabled": 1188, + "storage not cache image": 1177, + "storage of disk %s no valid host": 386, + "subnet masklen should be smaller than 30": 1025, "syncWithCloudBucket error %s": 235, - "tag has dynamic rules": 1075, - "tag is associate with sched policies": 1077, - "telegraf params: invalid influxdb url: %s": 884, - "tenant/project %s not found": 1328, - "the %s %q in guest template is not a public resource": 691, - "the %s %q in guest template is not a public resource in %s scope": 693, - "the %s in guest template is not a public resource": 690, - "the %s in guest template is not a public resource in %s scope": 692, - "the account has been registerd %s": 264, - "the acl cache in region %s aready exists.": 927, - "the associated natgateway has corresponding dnat rules with eip %s, please delete them firstly": 484, - "the associated natgateway has corresponding snat rules with eip %s, please delete them firstly": 483, - "the certificate cache in region %s aready exists.": 932, - "the guest template %s is not valid in cloudregion %s, reason: %s": 1040, - "the image reference session has not been expired!": 247, - "the min value of cycle in alarm is 300": 1061, - "the security group is in use": 1094, - "there is no such secgroup %s descripted by guest template": 689, - "time_points only contains %d points at most": 1121, - "top level public domain name %s not support": 399, - "totp secret exists": 1389, - "unexpected backend type %s": 913, - "unknown zone type %s": 400, - "unkown expansion principle %s": 1041, - "unkown health check mode %s": 1043, - "unkown indicator in alarm %s": 1059, - "unkown label type '%s'": 1082, - "unkown operator in alarm %s": 1058, - "unkown resource operation '%s'": 1081, - "unkown resource type '%s'": 1080, - "unkown scaling policy action %s": 1054, - "unkown scaling policy unit %s": 1055, - "unkown scheduled type '%s'": 1079, - "unkown shrink principle %s": 1042, - "unkown trigger type %s": 1053, - "unkown wrapper in alarm %s": 1060, - "unmarshal JoinResourceBaseCreateInput fail %s": 859, - "unmarshal JointResourceCreateInput fail %s": 1068, - "unmarshal StandaloneResourceCreateInput fail %s": 432, - "unmarshal VirtualResourceCreateInput fail %s": 439, - "unmarshal input: %v": 900, - "unmarshal limit error %s": 240, - "unmarshaling cidrs failed: %s": 1031, - "unsupport delete %s backups": 437, - "unsupport on host status %s": 844, - "unsupported action %s": 1403, - "unsupported duration %s": 314, - "use yum requires valid repo_base_url": 893, - "user %s not found": 87, - "user must have system admin privileges": 904, - "valid vlan id": 986, - "virtual resource already freezed": 91, - "virtual resource not freezed": 92, - "vpc %s and vpc %s have already connected": 1168, - "vpc %s has already in this dns zone": 406, - "vpc %s not in dns zone": 407, - "vpc %s(%s) is not a managed resouce": 1089, - "vpc lb is not allowed for now": 1293, - "vpc on different cloudprovider peering is not supported": 1164, - "vpc_id": 1163, - "weight %d not support, only support range 0 ~ 256": 908, - "wire contains hosts": 1187, - "wire contains networks": 1189, - "wire not found for zone %s and vpc %s": 984, - "wire zone must match zone parameter, got %s, want %s(%s)": 935, - "zone %s(%s) has no lbcluster": 1294, - "zone and vpc info required when wire is absent": 996, - "zone info missing": 1292, - "zone mismatch, elastic cache sku zone %s != %s": 1288, - "zone mismatch: instance zone %s, sku zone %s": 445, - "zone of wire must be %s, got %s": 936, + "sysadmin is protected": 1420, + "tag has dynamic rules": 1105, + "tag is associate with sched policies": 1107, + "telegraf params: invalid influxdb url: %s": 903, + "tenant/project %s not found": 1359, + "the %s %q in guest template is not a public resource": 710, + "the %s %q in guest template is not a public resource in %s scope": 712, + "the %s in guest template is not a public resource": 709, + "the %s in guest template is not a public resource in %s scope": 711, + "the AlertType is illegal:%s": 1558, + "the Comparator is illegal: %s": 1551, + "the account has been registerd %s": 275, + "the acl cache in region %s aready exists.": 946, + "the associated natgateway has corresponding dnat rules with eip %s, please delete them firstly": 503, + "the associated natgateway has corresponding snat rules with eip %s, please delete them firstly": 502, + "the certificate cache in region %s aready exists.": 951, + "the evalType is illegal": 1586, + "the guest template %s is not valid in cloudregion %s, reason: %s": 1070, + "the image reference session has not been expired!": 256, + "the min value of cycle in alarm is 300": 1091, + "the reduce is illegal %s": 1552, + "the reduce is illegal: %s": 1553, + "the security group is in use": 1123, + "there is no such secgroup %s descripted by guest template": 708, + "threshold:%s should be number type": 1560, + "time_points only contains %d points at most": 1149, + "top level public domain name %s not support": 413, + "totp secret exists": 1517, + "type %s rule already exists": 1573, + "type or resource_type must provided": 1582, + "unauthorized %s": 1499, + "unexpected backend type %s": 932, + "unknown parent object id spec": 985, + "unknown zone type %s": 414, + "unkown expansion principle %s": 1071, + "unkown health check mode %s": 1073, + "unkown indicator in alarm %s": 1089, + "unkown label type '%s'": 1112, + "unkown operator in alarm %s": 1088, + "unkown resource operation '%s'": 1111, + "unkown resource type '%s'": 1110, + "unkown scaling policy action %s": 1084, + "unkown scaling policy unit %s": 1085, + "unkown scheduled type '%s'": 1109, + "unkown shrink principle %s": 1072, + "unkown trigger type %s": 1083, + "unkown wrapper in alarm %s": 1090, + "unmarshal JoinResourceBaseCreateInput fail %s": 878, + "unmarshal JointResourceCreateInput fail %s": 1098, + "unmarshal StandaloneResourceCreateInput fail %s": 446, + "unmarshal VirtualResourceCreateInput fail %s": 453, + "unmarshal input fail %s": 1606, + "unmarshal input: %v": 919, + "unmarshal limit error %s": 249, + "unmarshaling cidrs failed: %s": 1061, + "unrecognized input %s": 1498, + "unsupport delete %s backups": 451, + "unsupport on host status %s": 863, + "unsupport type: %s": 1534, + "unsupported action %s": 1531, + "unsupported duration %s": 325, + "unsupported execution_error_state %s": 1540, + "unsupported no_data_state %s": 1539, + "unsupported notification type %s": 1566, + "unsupported resource type %s": 1564, + "update config version fail %s": 1478, + "url is empty": 1532, + "use yum requires valid repo_base_url": 912, + "user %s not found": 87, + "user contains external resources": 1482, + "user disabled": 1489, + "user must have system admin privileges": 923, + "user not found": 1493, + "user not found or not enabled": 1502, + "user not in project": 1495, + "valid vlan id": 1012, + "version mismatch": 1487, + "virtual resource already freezed": 91, + "virtual resource not freezed": 92, + "vpc %s and vpc %s have already connected": 1196, + "vpc %s has already in this dns zone": 420, + "vpc %s not in dns zone": 421, + "vpc %s(%s) is not a managed resouce": 1118, + "vpc lb is not allowed for now": 1323, + "vpc on different cloudprovider peering is not supported": 1192, + "vpc_id": 1191, + "weight %d not support, only support range 0 ~ 256": 927, + "wire contains hosts": 1215, + "wire contains networks": 1217, + "wire not found for zone %s and vpc %s": 1010, + "wire zone must match zone parameter, got %s, want %s(%s)": 954, + "zone %s not in cloudregion %s": 1128, + "zone %s(%s) has no lbcluster": 1324, + "zone and vpc info required when wire is absent": 1022, + "zone info missing": 1322, + "zone mismatch, elastic cache sku zone %s != %s": 1317, + "zone mismatch: instance zone %s, sku zone %s": 459, + "zone of wire must be %s, got %s": 955, } -var en_USIndex = []uint32{ // 1411 elements +var en_USIndex = []uint32{ // 1610 elements // Entry 0 - 1F 0x00000000, 0x00000016, 0x0000003a, 0x0000005a, 0x00000077, 0x00000097, 0x000000ba, 0x000000d2, @@ -1524,339 +1723,395 @@ var en_USIndex = []uint32{ // 1411 elements 0x00001f00, 0x00001f1a, 0x00001f35, 0x00001f49, 0x00001f63, 0x00001f7f, 0x00001f93, 0x00001fa3, 0x00001fc0, 0x00001fed, 0x00002001, 0x00002015, - 0x00002032, 0x0000204b, 0x0000205d, 0x0000206d, - 0x0000207e, 0x0000209a, 0x000020bb, 0x000020e1, - 0x00002113, 0x00002133, 0x00002158, 0x0000216b, - 0x0000217f, 0x0000218f, 0x000021ae, 0x000021c7, + 0x00002032, 0x0000204e, 0x00002071, 0x00002091, + 0x000020aa, 0x000020c6, 0x000020e4, 0x00002103, + 0x0000211d, 0x00002137, 0x00002150, 0x00002162, + 0x00002172, 0x00002183, 0x0000219f, 0x000021c0, // Entry 100 - 11F - 0x000021f7, 0x0000220f, 0x0000223a, 0x00002260, - 0x00002277, 0x00002297, 0x000022ab, 0x000022d0, - 0x000022f6, 0x00002318, 0x00002329, 0x00002343, - 0x00002354, 0x0000237f, 0x000023a4, 0x000023bd, - 0x000023d1, 0x00002405, 0x0000241a, 0x0000243f, - 0x00002464, 0x00002473, 0x00002496, 0x000024aa, - 0x000024be, 0x000024d3, 0x00002521, 0x00002535, - 0x00002547, 0x0000255e, 0x00002574, 0x0000259e, + 0x000021e6, 0x00002218, 0x00002238, 0x0000225d, + 0x0000227d, 0x00002290, 0x000022a4, 0x000022b4, + 0x000022d3, 0x000022ec, 0x0000231c, 0x00002334, + 0x0000235f, 0x00002378, 0x0000239e, 0x000023b5, + 0x000023d5, 0x000023e9, 0x0000240e, 0x00002434, + 0x00002456, 0x00002467, 0x00002481, 0x00002492, + 0x000024bd, 0x000024e2, 0x000024fb, 0x0000250f, + 0x00002543, 0x00002558, 0x0000257d, 0x000025a2, // Entry 120 - 13F - 0x000025df, 0x000025fe, 0x0000261c, 0x00002631, - 0x00002645, 0x0000265c, 0x00002685, 0x00002696, - 0x000026b6, 0x000026d4, 0x00002708, 0x00002735, - 0x00002767, 0x0000279d, 0x000027d9, 0x00002807, - 0x00002836, 0x0000285e, 0x00002897, 0x000028be, - 0x000028f6, 0x00002927, 0x00002937, 0x0000294b, - 0x0000296d, 0x00002993, 0x000029a7, 0x000029bf, - 0x000029e5, 0x00002a0e, 0x00002a2b, 0x00002a47, + 0x000025b1, 0x000025d4, 0x000025e8, 0x000025fc, + 0x00002611, 0x0000265f, 0x00002673, 0x00002685, + 0x0000269c, 0x000026b2, 0x000026dc, 0x0000271d, + 0x0000273c, 0x0000275a, 0x0000276f, 0x00002783, + 0x0000279a, 0x000027c3, 0x000027d4, 0x000027f4, + 0x00002812, 0x00002846, 0x00002873, 0x000028a5, + 0x000028db, 0x00002917, 0x00002945, 0x00002974, + 0x0000299c, 0x000029d5, 0x000029fc, 0x00002a34, // Entry 140 - 15F - 0x00002a6f, 0x00002aad, 0x00002ad3, 0x00002afc, - 0x00002b27, 0x00002b53, 0x00002b7d, 0x00002ba6, - 0x00002bd7, 0x00002c12, 0x00002c23, 0x00002c56, - 0x00002c7a, 0x00002c98, 0x00002cba, 0x00002cd4, - 0x00002cff, 0x00002d23, 0x00002d41, 0x00002d76, - 0x00002d89, 0x00002da9, 0x00002dc9, 0x00002dec, - 0x00002e1c, 0x00002e31, 0x00002e50, 0x00002e8b, - 0x00002eb8, 0x00002ee4, 0x00002f0a, 0x00002f2c, + 0x00002a65, 0x00002a75, 0x00002a89, 0x00002aab, + 0x00002ad1, 0x00002ae5, 0x00002afd, 0x00002b23, + 0x00002b4c, 0x00002b69, 0x00002b85, 0x00002bad, + 0x00002beb, 0x00002c11, 0x00002c3a, 0x00002c65, + 0x00002c91, 0x00002cbb, 0x00002ce4, 0x00002d15, + 0x00002d50, 0x00002d61, 0x00002d91, 0x00002dbb, + 0x00002dee, 0x00002e12, 0x00002e30, 0x00002e52, + 0x00002e6c, 0x00002e97, 0x00002ebb, 0x00002ed9, // Entry 160 - 17F - 0x00002f42, 0x00002f61, 0x00002f86, 0x00002fa0, - 0x00002fc0, 0x00002feb, 0x0000300a, 0x00003037, - 0x0000306e, 0x0000308d, 0x000030a3, 0x000030bd, - 0x000030d7, 0x000030f8, 0x0000310e, 0x00003126, - 0x00003143, 0x0000315f, 0x0000317d, 0x00003194, - 0x000031b7, 0x000031d9, 0x000031fa, 0x00003220, - 0x0000324c, 0x0000327d, 0x000032b1, 0x000032c7, - 0x000032fa, 0x00003315, 0x00003340, 0x00003359, + 0x00002f0e, 0x00002f21, 0x00002f41, 0x00002f61, + 0x00002f84, 0x00002fb4, 0x00002fc9, 0x00002fe8, + 0x00003023, 0x00003050, 0x0000307c, 0x000030a2, + 0x000030c4, 0x000030da, 0x000030f9, 0x0000311e, + 0x00003138, 0x00003158, 0x00003183, 0x000031a2, + 0x000031cf, 0x00003206, 0x00003225, 0x0000323b, + 0x00003255, 0x0000326f, 0x00003290, 0x000032a6, + 0x000032be, 0x000032db, 0x000032f7, 0x00003315, // Entry 180 - 19F - 0x00003392, 0x000033cc, 0x000033f7, 0x00003411, - 0x00003431, 0x00003454, 0x0000346c, 0x0000349d, - 0x000034ea, 0x00003522, 0x00003540, 0x00003562, - 0x0000356e, 0x00003585, 0x000035ad, 0x000035d9, - 0x00003605, 0x0000361a, 0x00003639, 0x0000365d, - 0x0000367f, 0x0000369a, 0x000036c0, 0x000036e4, - 0x000036fb, 0x00003717, 0x00003734, 0x00003753, - 0x00003780, 0x000037a1, 0x000037d0, 0x000037f0, + 0x0000332c, 0x0000334f, 0x00003371, 0x00003392, + 0x000033b8, 0x000033e4, 0x00003415, 0x00003449, + 0x0000345f, 0x00003492, 0x000034ad, 0x000034d8, + 0x000034f1, 0x0000352a, 0x00003564, 0x0000358f, + 0x000035a9, 0x000035c9, 0x000035ec, 0x00003604, + 0x0000361b, 0x0000364c, 0x00003699, 0x000036d1, + 0x000036ef, 0x00003711, 0x0000371d, 0x00003734, + 0x0000375c, 0x00003788, 0x000037b4, 0x000037c9, // Entry 1A0 - 1BF - 0x00003812, 0x00003832, 0x0000384e, 0x0000386f, - 0x00003890, 0x000038b2, 0x000038db, 0x00003907, - 0x00003926, 0x00003946, 0x0000395e, 0x0000396b, - 0x0000397b, 0x0000398b, 0x000039bb, 0x000039cd, - 0x000039e3, 0x00003a13, 0x00003a30, 0x00003a55, - 0x00003a65, 0x00003a8e, 0x00003aaa, 0x00003ac3, - 0x00003af0, 0x00003b25, 0x00003b4c, 0x00003b78, - 0x00003ba8, 0x00003bdb, 0x00003c08, 0x00003c45, + 0x000037e8, 0x0000380c, 0x0000382e, 0x00003849, + 0x0000386f, 0x00003893, 0x000038aa, 0x000038c6, + 0x000038e3, 0x00003902, 0x0000392f, 0x00003950, + 0x0000397f, 0x0000399f, 0x000039c1, 0x000039e1, + 0x000039fd, 0x00003a1e, 0x00003a3f, 0x00003a61, + 0x00003a8a, 0x00003ab6, 0x00003ad5, 0x00003af5, + 0x00003b0d, 0x00003b1a, 0x00003b2a, 0x00003b3a, + 0x00003b6a, 0x00003b7c, 0x00003b92, 0x00003bc2, // Entry 1C0 - 1DF - 0x00003c6f, 0x00003c8d, 0x00003ca9, 0x00003ccc, - 0x00003cf6, 0x00003d23, 0x00003d56, 0x00003d76, - 0x00003dad, 0x00003dd5, 0x00003ddc, 0x00003de9, - 0x00003e0b, 0x00003e3f, 0x00003e8b, 0x00003eb1, - 0x00003ec6, 0x00003eda, 0x00003f09, 0x00003f26, - 0x00003f4c, 0x00003f6e, 0x00003f8d, 0x00003f9c, - 0x00003fc3, 0x00003feb, 0x00004010, 0x00004037, - 0x00004055, 0x00004070, 0x0000409a, 0x000040c2, + 0x00003bdf, 0x00003c04, 0x00003c14, 0x00003c3d, + 0x00003c59, 0x00003c72, 0x00003c9f, 0x00003cd4, + 0x00003cfb, 0x00003d27, 0x00003d57, 0x00003d8a, + 0x00003db7, 0x00003df4, 0x00003e1e, 0x00003e3c, + 0x00003e68, 0x00003e84, 0x00003ea7, 0x00003ed1, + 0x00003efe, 0x00003f31, 0x00003f51, 0x00003f88, + 0x00003fb0, 0x00003fb7, 0x00003fc4, 0x00003fe6, + 0x0000401a, 0x00004066, 0x0000408c, 0x000040bf, // Entry 1E0 - 1FF - 0x000040de, 0x00004112, 0x00004135, 0x0000415c, - 0x000041bb, 0x0000421a, 0x00004237, 0x0000425c, - 0x0000426e, 0x000042a0, 0x000042c3, 0x000042f1, - 0x00004318, 0x00004341, 0x00004352, 0x00004389, - 0x00004396, 0x000043c8, 0x000043e9, 0x000043fc, - 0x0000441b, 0x00004429, 0x00004448, 0x00004461, - 0x00004482, 0x000044a6, 0x000044d1, 0x000044ee, - 0x00004507, 0x00004522, 0x00004553, 0x00004581, + 0x000040f6, 0x0000411c, 0x0000414a, 0x0000415f, + 0x00004173, 0x000041a2, 0x000041bf, 0x000041e5, + 0x00004207, 0x00004226, 0x00004235, 0x0000425c, + 0x00004284, 0x000042a9, 0x000042d0, 0x000042ee, + 0x00004309, 0x00004333, 0x0000435b, 0x00004377, + 0x000043ab, 0x000043ce, 0x000043f5, 0x00004454, + 0x000044b3, 0x000044d0, 0x000044f5, 0x00004507, + 0x00004539, 0x0000455c, 0x0000458a, 0x000045b1, // Entry 200 - 21F - 0x0000459c, 0x000045b6, 0x000045da, 0x000045f8, - 0x00004626, 0x00004638, 0x00004657, 0x00004675, - 0x00004689, 0x000046ad, 0x000046d2, 0x000046f6, - 0x00004706, 0x00004729, 0x00004756, 0x00004780, - 0x000047b8, 0x000047e3, 0x0000480e, 0x00004838, - 0x0000485f, 0x0000488b, 0x000048af, 0x000048c1, - 0x000048ce, 0x000048f0, 0x0000490f, 0x0000493e, - 0x0000496d, 0x000049a0, 0x000049bf, 0x000049d4, + 0x000045da, 0x000045eb, 0x00004622, 0x0000462f, + 0x00004661, 0x00004682, 0x00004695, 0x000046b4, + 0x000046c2, 0x000046e1, 0x000046fa, 0x0000471b, + 0x0000473f, 0x0000476a, 0x00004787, 0x000047a0, + 0x000047bb, 0x000047ec, 0x0000481a, 0x00004835, + 0x0000484f, 0x00004873, 0x00004891, 0x000048bf, + 0x000048d1, 0x000048f0, 0x0000490e, 0x00004922, + 0x00004946, 0x0000496b, 0x0000498f, 0x0000499f, // Entry 220 - 23F - 0x000049ea, 0x000049f8, 0x00004a19, 0x00004a41, - 0x00004a58, 0x00004a74, 0x00004a8b, 0x00004aa5, - 0x00004aca, 0x00004adf, 0x00004b1a, 0x00004b32, - 0x00004b4f, 0x00004b7d, 0x00004b9d, 0x00004bb1, - 0x00004bdd, 0x00004c00, 0x00004c23, 0x00004c33, - 0x00004c44, 0x00004c67, 0x00004c92, 0x00004cba, - 0x00004cd7, 0x00004cf5, 0x00004d18, 0x00004d3b, - 0x00004d60, 0x00004d7f, 0x00004da2, 0x00004dbd, + 0x000049c2, 0x000049ef, 0x00004a19, 0x00004a51, + 0x00004a7c, 0x00004aa7, 0x00004ad1, 0x00004af8, + 0x00004b24, 0x00004b48, 0x00004b5a, 0x00004b67, + 0x00004b89, 0x00004ba8, 0x00004bd7, 0x00004c06, + 0x00004c39, 0x00004c58, 0x00004c6d, 0x00004c83, + 0x00004c91, 0x00004cb2, 0x00004cda, 0x00004cf1, + 0x00004d0d, 0x00004d24, 0x00004d3e, 0x00004d63, + 0x00004d78, 0x00004db3, 0x00004dcb, 0x00004de8, // Entry 240 - 25F - 0x00004dea, 0x00004e05, 0x00004e39, 0x00004e57, - 0x00004e8f, 0x00004eac, 0x00004ed7, 0x00004efa, - 0x00004f14, 0x00004f30, 0x00004f48, 0x00004f60, - 0x00004f7c, 0x00004f9a, 0x00004fb7, 0x00004fe3, - 0x00005003, 0x00005029, 0x00005047, 0x00005069, - 0x00005084, 0x00005095, 0x000050ad, 0x000050e3, - 0x00005117, 0x0000514f, 0x00005164, 0x0000516f, - 0x00005187, 0x00005199, 0x000051ae, 0x000051dd, + 0x00004e16, 0x00004e36, 0x00004e4a, 0x00004e76, + 0x00004e99, 0x00004ebc, 0x00004ecc, 0x00004edd, + 0x00004f00, 0x00004f2b, 0x00004f53, 0x00004f70, + 0x00004f8e, 0x00004fb1, 0x00004fd4, 0x00004ff9, + 0x00005018, 0x0000503b, 0x00005056, 0x00005083, + 0x0000509e, 0x000050d2, 0x000050f0, 0x00005128, + 0x00005145, 0x00005170, 0x00005193, 0x000051ad, + 0x000051c9, 0x000051e1, 0x000051f9, 0x00005215, // Entry 260 - 27F - 0x000051f2, 0x00005225, 0x0000523e, 0x0000525c, - 0x0000526f, 0x00005284, 0x0000529f, 0x000052c8, - 0x000052eb, 0x00005316, 0x00005334, 0x00005357, - 0x0000536c, 0x00005388, 0x0000539f, 0x000053c3, - 0x000053f3, 0x0000540c, 0x0000542f, 0x00005440, - 0x00005453, 0x00005468, 0x00005481, 0x00005493, - 0x000054b7, 0x000054d2, 0x000054e5, 0x000054fd, - 0x00005524, 0x00005545, 0x00005558, 0x00005576, + 0x00005233, 0x00005250, 0x0000527c, 0x0000529c, + 0x000052c2, 0x000052e0, 0x00005302, 0x0000531d, + 0x0000532e, 0x00005346, 0x0000537c, 0x000053b0, + 0x000053e8, 0x000053fd, 0x00005408, 0x00005420, + 0x00005432, 0x00005447, 0x00005476, 0x0000548b, + 0x000054be, 0x000054d7, 0x000054f5, 0x00005508, + 0x0000551d, 0x00005538, 0x00005561, 0x00005584, + 0x000055af, 0x000055cd, 0x000055f0, 0x00005605, // Entry 280 - 29F - 0x00005593, 0x000055a5, 0x000055ce, 0x000055e6, - 0x00005600, 0x0000560f, 0x00005628, 0x00005640, - 0x0000565b, 0x0000567c, 0x0000568e, 0x0000569c, - 0x000056b2, 0x000056ce, 0x000056e3, 0x00005702, - 0x00005725, 0x00005748, 0x00005755, 0x00005763, - 0x00005788, 0x000057ab, 0x000057d4, 0x000057f5, - 0x0000582e, 0x00005848, 0x0000587c, 0x0000588d, - 0x000058a9, 0x000058dc, 0x00005909, 0x0000592e, + 0x00005621, 0x00005638, 0x0000565c, 0x0000568c, + 0x000056a5, 0x000056c8, 0x000056d9, 0x000056ec, + 0x00005701, 0x0000571a, 0x0000572c, 0x00005750, + 0x0000576b, 0x0000577e, 0x00005796, 0x000057bd, + 0x000057de, 0x000057f1, 0x0000580f, 0x0000582c, + 0x0000583e, 0x00005867, 0x0000587f, 0x00005899, + 0x000058a8, 0x000058c1, 0x000058d9, 0x000058f4, + 0x00005915, 0x00005927, 0x00005935, 0x0000594b, // Entry 2A0 - 2BF - 0x0000595f, 0x00005983, 0x000059a9, 0x000059c5, - 0x000059e4, 0x000059f3, 0x00005a14, 0x00005a2b, - 0x00005a39, 0x00005a51, 0x00005a62, 0x00005a90, - 0x00005abf, 0x00005ae8, 0x00005b19, 0x00005b4d, - 0x00005b72, 0x00005bad, 0x00005be7, 0x00005c19, - 0x00005c4e, 0x00005c8c, 0x00005ccd, 0x00005cfa, - 0x00005d24, 0x00005d48, 0x00005d68, 0x00005d7f, - 0x00005da5, 0x00005dc4, 0x00005dd6, 0x00005dec, + 0x00005967, 0x0000597c, 0x0000599b, 0x000059be, + 0x000059e1, 0x000059ee, 0x000059fc, 0x00005a21, + 0x00005a44, 0x00005a6d, 0x00005a8e, 0x00005ac7, + 0x00005ae1, 0x00005b15, 0x00005b26, 0x00005b42, + 0x00005b75, 0x00005ba2, 0x00005bc7, 0x00005bf8, + 0x00005c1c, 0x00005c42, 0x00005c5e, 0x00005c7d, + 0x00005c8c, 0x00005cad, 0x00005cc4, 0x00005cd2, + 0x00005cea, 0x00005cfb, 0x00005d29, 0x00005d58, // Entry 2C0 - 2DF - 0x00005dfe, 0x00005e1f, 0x00005e32, 0x00005e5a, - 0x00005e8d, 0x00005eb3, 0x00005ed8, 0x00005f0e, - 0x00005f2e, 0x00005f4c, 0x00005f76, 0x00005f99, - 0x00005fab, 0x00005fcb, 0x00005fec, 0x00006008, - 0x00006022, 0x00006048, 0x00006065, 0x0000607c, - 0x000060ca, 0x000060eb, 0x00006104, 0x00006124, - 0x0000615a, 0x00006190, 0x000061b3, 0x000061dd, - 0x00006208, 0x0000621d, 0x00006233, 0x0000625a, + 0x00005d81, 0x00005db2, 0x00005de6, 0x00005e0b, + 0x00005e46, 0x00005e80, 0x00005eb2, 0x00005ee7, + 0x00005f25, 0x00005f66, 0x00005f93, 0x00005fbd, + 0x00005fe1, 0x00006001, 0x00006018, 0x0000603e, + 0x0000605d, 0x0000606f, 0x00006085, 0x00006097, + 0x000060b8, 0x000060cb, 0x000060f3, 0x00006126, + 0x0000614c, 0x00006171, 0x000061a7, 0x000061c7, + 0x000061e5, 0x0000620f, 0x00006232, 0x00006244, // Entry 2E0 - 2FF - 0x0000626f, 0x0000629b, 0x000062ca, 0x000062fd, - 0x0000632b, 0x0000635d, 0x00006388, 0x000063b9, - 0x000063d8, 0x000063f9, 0x00006412, 0x0000642b, - 0x00006446, 0x0000647c, 0x000064ab, 0x000064c2, - 0x000064eb, 0x0000650d, 0x00006526, 0x0000655e, - 0x00006577, 0x00006596, 0x000065a8, 0x000065c4, - 0x000065e0, 0x00006601, 0x00006619, 0x0000663c, - 0x00006671, 0x000066a5, 0x000066db, 0x000066f6, + 0x00006264, 0x00006285, 0x000062a1, 0x000062bb, + 0x000062e1, 0x000062fe, 0x00006315, 0x00006363, + 0x00006384, 0x0000639d, 0x000063bd, 0x000063f3, + 0x00006429, 0x0000644c, 0x00006476, 0x000064a1, + 0x000064b6, 0x000064cc, 0x000064f3, 0x00006508, + 0x00006534, 0x00006563, 0x00006596, 0x000065c4, + 0x000065f6, 0x00006621, 0x00006652, 0x00006671, + 0x00006692, 0x000066ab, 0x000066c4, 0x000066df, // Entry 300 - 31F - 0x0000670e, 0x00006724, 0x00006740, 0x00006783, - 0x00006798, 0x000067ae, 0x000067c0, 0x000067d5, - 0x000067f8, 0x00006824, 0x0000684e, 0x0000686f, - 0x0000688c, 0x0000689c, 0x000068af, 0x000068d4, - 0x000068ec, 0x0000690b, 0x00006927, 0x0000695d, - 0x00006978, 0x00006996, 0x000069ce, 0x000069fb, - 0x00006a18, 0x00006a42, 0x00006a64, 0x00006a8d, - 0x00006aae, 0x00006ada, 0x00006b02, 0x00006b25, + 0x00006715, 0x00006744, 0x0000675b, 0x00006784, + 0x000067a6, 0x000067bf, 0x000067f7, 0x00006810, + 0x0000682f, 0x00006841, 0x0000685d, 0x00006879, + 0x0000689a, 0x000068b2, 0x000068d5, 0x0000690a, + 0x0000693e, 0x00006974, 0x0000698f, 0x000069a7, + 0x000069bd, 0x000069d9, 0x00006a1c, 0x00006a31, + 0x00006a47, 0x00006a59, 0x00006a6e, 0x00006a91, + 0x00006abd, 0x00006ae7, 0x00006b08, 0x00006b25, // Entry 320 - 33F - 0x00006b51, 0x00006b76, 0x00006b8c, 0x00006b9c, - 0x00006bc9, 0x00006bef, 0x00006c1c, 0x00006c3e, - 0x00006c64, 0x00006c83, 0x00006c9a, 0x00006cae, - 0x00006cc5, 0x00006cd7, 0x00006cee, 0x00006d0a, - 0x00006d27, 0x00006d49, 0x00006d6e, 0x00006d85, - 0x00006dab, 0x00006dc3, 0x00006dd7, 0x00006dec, - 0x00006e14, 0x00006e38, 0x00006e61, 0x00006e89, - 0x00006e9c, 0x00006ec0, 0x00006edc, 0x00006ef5, + 0x00006b35, 0x00006b48, 0x00006b6d, 0x00006b85, + 0x00006ba4, 0x00006bc0, 0x00006bf6, 0x00006c11, + 0x00006c2f, 0x00006c67, 0x00006c94, 0x00006cb1, + 0x00006cdb, 0x00006cfd, 0x00006d26, 0x00006d47, + 0x00006d73, 0x00006d9b, 0x00006dbe, 0x00006dea, + 0x00006e0f, 0x00006e25, 0x00006e35, 0x00006e62, + 0x00006e88, 0x00006eb5, 0x00006ed7, 0x00006efd, + 0x00006f1c, 0x00006f33, 0x00006f47, 0x00006f5e, // Entry 340 - 35F - 0x00006f17, 0x00006f3e, 0x00006f78, 0x00006f91, - 0x00006fab, 0x00006fbd, 0x00006fcd, 0x00006fed, - 0x00007005, 0x00007023, 0x0000703f, 0x00007065, - 0x0000708c, 0x000070a8, 0x000070d5, 0x000070f7, - 0x00007118, 0x00007140, 0x00007157, 0x00007171, - 0x0000718c, 0x000071ce, 0x00007214, 0x0000725b, - 0x0000727d, 0x0000728a, 0x000072b3, 0x000072dc, - 0x0000730a, 0x00007324, 0x0000733e, 0x00007370, + 0x00006f70, 0x00006f87, 0x00006fa3, 0x00006fc0, + 0x00006fe2, 0x00007007, 0x0000701e, 0x00007044, + 0x0000705c, 0x00007070, 0x00007085, 0x000070ad, + 0x000070d1, 0x000070fa, 0x00007122, 0x00007135, + 0x00007159, 0x00007175, 0x0000718e, 0x000071b0, + 0x000071d7, 0x00007211, 0x0000722a, 0x00007244, + 0x00007256, 0x00007266, 0x00007286, 0x0000729e, + 0x000072bc, 0x000072d8, 0x000072fe, 0x00007325, // Entry 360 - 37F - 0x00007392, 0x000073b1, 0x000073cd, 0x00007403, - 0x00007426, 0x0000743c, 0x00007455, 0x00007474, - 0x0000749a, 0x000074c0, 0x000074d0, 0x000074ea, - 0x00007512, 0x00007528, 0x0000753e, 0x00007563, - 0x0000757d, 0x000075b3, 0x000075da, 0x0000760a, - 0x00007633, 0x0000765d, 0x0000767f, 0x00007694, - 0x000076b9, 0x000076da, 0x0000770c, 0x0000771f, - 0x00007746, 0x00007777, 0x0000779c, 0x000077ac, + 0x00007341, 0x0000736e, 0x00007390, 0x000073b1, + 0x000073d9, 0x000073f0, 0x0000740a, 0x00007425, + 0x00007467, 0x000074ad, 0x000074f4, 0x00007516, + 0x00007523, 0x0000754c, 0x00007575, 0x000075a3, + 0x000075bd, 0x000075d7, 0x00007609, 0x0000763a, + 0x00007659, 0x00007675, 0x000076ab, 0x000076ce, + 0x000076e4, 0x000076fd, 0x0000771c, 0x00007742, + 0x00007768, 0x00007778, 0x00007792, 0x000077ba, // Entry 380 - 39F - 0x000077bd, 0x000077e8, 0x000077fa, 0x00007826, - 0x00007845, 0x00007859, 0x0000786f, 0x00007883, - 0x0000789a, 0x000078c1, 0x000078db, 0x00007901, - 0x0000792f, 0x00007961, 0x00007993, 0x000079ab, - 0x000079d5, 0x000079ec, 0x00007a07, 0x00007a38, - 0x00007a62, 0x00007a78, 0x00007aa2, 0x00007ab7, - 0x00007ae3, 0x00007b66, 0x00007b9e, 0x00007bb6, - 0x00007be5, 0x00007c18, 0x00007c4b, 0x00007c7b, + 0x000077d0, 0x000077e6, 0x0000780b, 0x00007825, + 0x0000785b, 0x00007882, 0x000078b2, 0x000078db, + 0x00007905, 0x00007927, 0x0000793c, 0x00007961, + 0x00007982, 0x000079b4, 0x000079c7, 0x000079ee, + 0x00007a1f, 0x00007a44, 0x00007a54, 0x00007a65, + 0x00007a90, 0x00007aa2, 0x00007ace, 0x00007aed, + 0x00007b01, 0x00007b17, 0x00007b2b, 0x00007b42, + 0x00007b69, 0x00007b83, 0x00007ba9, 0x00007bd7, // Entry 3A0 - 3BF - 0x00007ca5, 0x00007cc6, 0x00007cf3, 0x00007d24, - 0x00007d55, 0x00007d87, 0x00007db1, 0x00007dd6, - 0x00007e0f, 0x00007e2f, 0x00007e4e, 0x00007e7d, - 0x00007ec3, 0x00007eeb, 0x00007f19, 0x00007f48, - 0x00007f73, 0x00007fa6, 0x00007fde, 0x00008016, - 0x0000802e, 0x00008066, 0x0000809b, 0x000080d7, - 0x000080fd, 0x00008117, 0x00008128, 0x00008148, - 0x00008171, 0x00008194, 0x000081a0, 0x000081d2, + 0x00007c09, 0x00007c3b, 0x00007c53, 0x00007c7d, + 0x00007c94, 0x00007caf, 0x00007ce0, 0x00007d0a, + 0x00007d20, 0x00007d4a, 0x00007d5f, 0x00007d8b, + 0x00007e0e, 0x00007e46, 0x00007e5e, 0x00007e8d, + 0x00007ec0, 0x00007ef3, 0x00007f23, 0x00007f4d, + 0x00007f6e, 0x00007f9b, 0x00007fcc, 0x00007ffd, + 0x0000802f, 0x00008059, 0x0000807e, 0x000080b7, + 0x000080d7, 0x000080f6, 0x00008125, 0x0000816b, // Entry 3C0 - 3DF - 0x00008207, 0x00008226, 0x00008249, 0x00008259, - 0x00008276, 0x0000828e, 0x000082bf, 0x000082d5, - 0x000082f4, 0x00008310, 0x00008335, 0x0000834f, - 0x00008361, 0x0000837f, 0x00008398, 0x000083b0, - 0x000083dd, 0x000083f5, 0x0000840e, 0x00008433, - 0x00008447, 0x00008465, 0x0000847f, 0x00008495, - 0x000084bb, 0x000084e1, 0x000084f9, 0x00008507, - 0x0000852b, 0x0000853f, 0x00008552, 0x0000856a, + 0x00008193, 0x000081c1, 0x000081f0, 0x0000821b, + 0x0000824e, 0x00008286, 0x000082be, 0x000082d6, + 0x0000830e, 0x00008343, 0x0000837f, 0x000083a5, + 0x000083bf, 0x000083d0, 0x000083f0, 0x00008419, + 0x0000843c, 0x00008448, 0x0000847a, 0x000084af, + 0x000084ce, 0x000084f1, 0x00008501, 0x00008514, + 0x00008528, 0x00008550, 0x0000856e, 0x0000858d, + 0x000085b3, 0x000085c8, 0x000085e5, 0x000085fd, // Entry 3E0 - 3FF - 0x00008580, 0x000085a8, 0x000085c2, 0x000085d5, - 0x0000860c, 0x0000863b, 0x00008649, 0x0000867f, - 0x000086a8, 0x000086e5, 0x0000870e, 0x0000872d, - 0x00008765, 0x0000877d, 0x000087ab, 0x000087d2, - 0x000087fb, 0x00008829, 0x00008839, 0x00008868, - 0x0000887d, 0x000088a5, 0x000088c8, 0x000088ef, - 0x000088fd, 0x00008919, 0x00008932, 0x00008944, - 0x00008959, 0x00008978, 0x0000897b, 0x00008997, + 0x0000862e, 0x00008644, 0x00008663, 0x0000867f, + 0x000086a4, 0x000086be, 0x000086d0, 0x000086ee, + 0x00008707, 0x0000871f, 0x0000874c, 0x00008764, + 0x0000877d, 0x000087a2, 0x000087b6, 0x000087d4, + 0x000087ee, 0x00008804, 0x0000882a, 0x00008850, + 0x00008868, 0x00008876, 0x0000889a, 0x000088ae, + 0x000088c1, 0x000088d9, 0x000088ef, 0x00008917, + 0x00008931, 0x00008944, 0x0000897b, 0x000089aa, // Entry 400 - 41F - 0x000089b7, 0x000089dd, 0x00008a02, 0x00008a14, - 0x00008a45, 0x00008a58, 0x00008a76, 0x00008aa9, - 0x00008ac7, 0x00008af8, 0x00008b3a, 0x00008b8c, - 0x00008ba4, 0x00008bcb, 0x00008be3, 0x00008c00, - 0x00008c1a, 0x00008c5b, 0x00008c79, 0x00008c94, - 0x00008cb0, 0x00008cd8, 0x00008cff, 0x00008d28, - 0x00008d51, 0x00008d98, 0x00008db2, 0x00008ddf, - 0x00008e0e, 0x00008e27, 0x00008e3e, 0x00008e5e, + 0x000089b8, 0x000089ee, 0x00008a17, 0x00008a54, + 0x00008a7d, 0x00008a9c, 0x00008ad4, 0x00008aec, + 0x00008b1a, 0x00008b41, 0x00008b6a, 0x00008b98, + 0x00008ba8, 0x00008bd7, 0x00008bec, 0x00008c14, + 0x00008c37, 0x00008c5e, 0x00008c6c, 0x00008c88, + 0x00008ca1, 0x00008cb3, 0x00008cc8, 0x00008ce7, + 0x00008cea, 0x00008d06, 0x00008d26, 0x00008d4c, + 0x00008d71, 0x00008d83, 0x00008db4, 0x00008dc7, // Entry 420 - 43F - 0x00008e7c, 0x00008eb0, 0x00008ec4, 0x00008ee0, - 0x00008efd, 0x00008f18, 0x00008f3f, 0x00008f53, - 0x00008f6f, 0x00008f87, 0x00008fa4, 0x00008fb0, - 0x00008fdd, 0x00009008, 0x0000901c, 0x00009038, - 0x0000905a, 0x00009071, 0x0000908b, 0x000090ab, - 0x000090c1, 0x000090df, 0x00009104, 0x00009131, - 0x0000914c, 0x00009166, 0x00009185, 0x0000919c, - 0x000091d8, 0x000091ec, 0x00009201, 0x0000921f, + 0x00008de5, 0x00008df5, 0x00008e0a, 0x00008e26, + 0x00008e54, 0x00008e87, 0x00008ea5, 0x00008ed6, + 0x00008f18, 0x00008f6a, 0x00008f82, 0x00008fa9, + 0x00008fc1, 0x00008fde, 0x00008ff8, 0x00009039, + 0x00009057, 0x00009072, 0x0000908e, 0x000090b6, + 0x000090dd, 0x00009106, 0x0000912f, 0x00009176, + 0x00009190, 0x000091bd, 0x000091ec, 0x00009205, + 0x0000921c, 0x0000923c, 0x0000925a, 0x0000928e, // Entry 440 - 45F - 0x0000923b, 0x00009252, 0x00009276, 0x0000929f, - 0x000092b6, 0x000092dc, 0x000092f3, 0x00009310, - 0x0000933b, 0x00009352, 0x0000936e, 0x00009388, - 0x000093a7, 0x000093bf, 0x000093e7, 0x00009410, - 0x0000943a, 0x00009467, 0x00009478, 0x00009498, - 0x000094c9, 0x000094e7, 0x000094f6, 0x00009546, - 0x00009579, 0x000095a4, 0x000095be, 0x000095d5, - 0x000095eb, 0x00009614, 0x00009642, 0x00009660, + 0x000092a2, 0x000092be, 0x000092db, 0x000092f6, + 0x0000931d, 0x00009331, 0x0000934d, 0x00009365, + 0x00009382, 0x0000938e, 0x000093bb, 0x000093e6, + 0x000093fa, 0x00009416, 0x00009438, 0x0000944f, + 0x00009469, 0x00009489, 0x0000949f, 0x000094bd, + 0x000094e2, 0x0000950f, 0x0000952a, 0x00009544, + 0x00009563, 0x0000957a, 0x000095b6, 0x000095ca, + 0x000095e8, 0x00009604, 0x0000961b, 0x0000963f, // Entry 460 - 47F - 0x00009682, 0x000096b0, 0x000096dc, 0x000096f6, - 0x0000971b, 0x00009731, 0x00009762, 0x00009772, - 0x00009795, 0x000097c3, 0x000097d6, 0x000097ee, - 0x0000981f, 0x00009843, 0x00009864, 0x0000988d, - 0x000098b2, 0x000098e1, 0x0000990d, 0x0000992c, - 0x00009960, 0x00009990, 0x000099aa, 0x000099ba, - 0x000099de, 0x000099fb, 0x00009a17, 0x00009a2f, - 0x00009a45, 0x00009a6a, 0x00009a82, 0x00009aa0, + 0x00009668, 0x0000967f, 0x000096a5, 0x000096bc, + 0x000096d9, 0x00009704, 0x0000971b, 0x00009737, + 0x00009751, 0x0000976f, 0x00009797, 0x000097c0, + 0x000097ea, 0x00009817, 0x00009828, 0x00009848, + 0x00009879, 0x00009897, 0x000098a6, 0x000098f6, + 0x00009929, 0x00009954, 0x0000996e, 0x00009985, + 0x0000999b, 0x000099c4, 0x000099f2, 0x00009a10, + 0x00009a32, 0x00009a60, 0x00009a8c, 0x00009aa6, // Entry 480 - 49F - 0x00009ab9, 0x00009ad1, 0x00009ae8, 0x00009b06, - 0x00009b1b, 0x00009b37, 0x00009b49, 0x00009b5f, - 0x00009b78, 0x00009b8b, 0x00009bae, 0x00009bcb, - 0x00009bd2, 0x00009c0a, 0x00009c1d, 0x00009c5e, - 0x00009ca0, 0x00009cc9, 0x00009cf2, 0x00009d0a, - 0x00009d35, 0x00009d50, 0x00009d7f, 0x00009d9f, - 0x00009dc9, 0x00009e00, 0x00009e16, 0x00009e41, - 0x00009e63, 0x00009e99, 0x00009ebd, 0x00009ed1, + 0x00009acb, 0x00009ae1, 0x00009b12, 0x00009b22, + 0x00009b45, 0x00009b73, 0x00009b86, 0x00009b9e, + 0x00009bcf, 0x00009bf3, 0x00009c14, 0x00009c3d, + 0x00009c62, 0x00009c91, 0x00009cbd, 0x00009cdc, + 0x00009d10, 0x00009d40, 0x00009d5a, 0x00009d6a, + 0x00009d8e, 0x00009dab, 0x00009dc7, 0x00009ddf, + 0x00009df5, 0x00009e1a, 0x00009e32, 0x00009e50, + 0x00009e69, 0x00009e81, 0x00009e98, 0x00009eb6, // Entry 4A0 - 4BF - 0x00009ef2, 0x00009f11, 0x00009f44, 0x00009f56, - 0x00009f6a, 0x00009f7f, 0x00009f96, 0x00009fa5, - 0x00009fc0, 0x00009ff8, 0x0000a03a, 0x0000a061, - 0x0000a094, 0x0000a0b3, 0x0000a0c4, 0x0000a0fb, - 0x0000a126, 0x0000a151, 0x0000a18b, 0x0000a1b4, - 0x0000a1e7, 0x0000a205, 0x0000a22e, 0x0000a26c, - 0x0000a28e, 0x0000a2cb, 0x0000a30f, 0x0000a342, - 0x0000a362, 0x0000a391, 0x0000a3d6, 0x0000a409, + 0x00009ecb, 0x00009ee7, 0x00009ef9, 0x00009f0f, + 0x00009f28, 0x00009f3b, 0x00009f5e, 0x00009f7b, + 0x00009f82, 0x00009fba, 0x00009fcd, 0x0000a00e, + 0x0000a050, 0x0000a079, 0x0000a0a2, 0x0000a0ba, + 0x0000a0e5, 0x0000a100, 0x0000a12f, 0x0000a14f, + 0x0000a179, 0x0000a1b0, 0x0000a1c6, 0x0000a1f1, + 0x0000a213, 0x0000a249, 0x0000a26d, 0x0000a281, + 0x0000a2a2, 0x0000a2c1, 0x0000a2f4, 0x0000a306, // Entry 4C0 - 4DF - 0x0000a435, 0x0000a480, 0x0000a4b1, 0x0000a4dd, - 0x0000a4f5, 0x0000a50b, 0x0000a54e, 0x0000a58e, - 0x0000a5ad, 0x0000a5e1, 0x0000a657, 0x0000a689, - 0x0000a6c4, 0x0000a6fb, 0x0000a758, 0x0000a78c, - 0x0000a7cc, 0x0000a809, 0x0000a851, 0x0000a8aa, - 0x0000a900, 0x0000a947, 0x0000a97a, 0x0000a9b9, - 0x0000a9ea, 0x0000aa10, 0x0000aa39, 0x0000aa59, - 0x0000aa73, 0x0000aa88, 0x0000aaa9, 0x0000aadd, + 0x0000a31a, 0x0000a32f, 0x0000a346, 0x0000a355, + 0x0000a374, 0x0000a38f, 0x0000a3c7, 0x0000a409, + 0x0000a430, 0x0000a463, 0x0000a482, 0x0000a493, + 0x0000a4ca, 0x0000a4f5, 0x0000a520, 0x0000a55a, + 0x0000a583, 0x0000a5b6, 0x0000a5d4, 0x0000a5fd, + 0x0000a63b, 0x0000a65d, 0x0000a69a, 0x0000a6de, + 0x0000a711, 0x0000a731, 0x0000a760, 0x0000a7a5, + 0x0000a7d8, 0x0000a804, 0x0000a84f, 0x0000a880, // Entry 4E0 - 4FF - 0x0000aafa, 0x0000ab2f, 0x0000ab6b, 0x0000ab96, - 0x0000abc4, 0x0000abeb, 0x0000ac0d, 0x0000ac2a, - 0x0000ac62, 0x0000aca2, 0x0000acdd, 0x0000ad0d, - 0x0000ad41, 0x0000ad71, 0x0000ada2, 0x0000adcc, - 0x0000adfa, 0x0000ae30, 0x0000ae63, 0x0000ae8c, - 0x0000aec5, 0x0000aeeb, 0x0000af15, 0x0000af44, - 0x0000af7b, 0x0000afa5, 0x0000afd8, 0x0000b010, - 0x0000b057, 0x0000b090, 0x0000b0ce, 0x0000b0ff, + 0x0000a8ac, 0x0000a8c4, 0x0000a8da, 0x0000a91d, + 0x0000a95d, 0x0000a97c, 0x0000a9b0, 0x0000aa26, + 0x0000aa58, 0x0000aa93, 0x0000aaca, 0x0000ab27, + 0x0000ab5b, 0x0000ab9b, 0x0000abd8, 0x0000ac20, + 0x0000ac79, 0x0000accf, 0x0000ad16, 0x0000ad49, + 0x0000ad88, 0x0000adb9, 0x0000addf, 0x0000ae08, + 0x0000ae28, 0x0000ae42, 0x0000ae57, 0x0000ae78, + 0x0000aeac, 0x0000aec9, 0x0000aefe, 0x0000af3a, // Entry 500 - 51F - 0x0000b127, 0x0000b150, 0x0000b17d, 0x0000b1ba, - 0x0000b1f1, 0x0000b209, 0x0000b244, 0x0000b275, - 0x0000b29d, 0x0000b2cc, 0x0000b2de, 0x0000b2fc, - 0x0000b32d, 0x0000b33f, 0x0000b35d, 0x0000b37a, - 0x0000b38e, 0x0000b3a7, 0x0000b3c6, 0x0000b3ed, - 0x0000b426, 0x0000b464, 0x0000b486, 0x0000b4bf, - 0x0000b4f4, 0x0000b522, 0x0000b53b, 0x0000b558, - 0x0000b57f, 0x0000b5a4, 0x0000b5c4, 0x0000b5da, + 0x0000af65, 0x0000af93, 0x0000afba, 0x0000afdc, + 0x0000aff9, 0x0000b031, 0x0000b071, 0x0000b0ac, + 0x0000b0dc, 0x0000b110, 0x0000b140, 0x0000b171, + 0x0000b19b, 0x0000b1c9, 0x0000b1ff, 0x0000b232, + 0x0000b25b, 0x0000b294, 0x0000b2ba, 0x0000b2e4, + 0x0000b313, 0x0000b34a, 0x0000b374, 0x0000b3a7, + 0x0000b3df, 0x0000b426, 0x0000b45f, 0x0000b49d, + 0x0000b4ce, 0x0000b4f6, 0x0000b51f, 0x0000b54c, // Entry 520 - 53F - 0x0000b60c, 0x0000b63e, 0x0000b65f, 0x0000b68c, - 0x0000b6ce, 0x0000b704, 0x0000b741, 0x0000b757, - 0x0000b77e, 0x0000b7b6, 0x0000b7ec, 0x0000b810, - 0x0000b83d, 0x0000b852, 0x0000b86e, 0x0000b883, - 0x0000b89e, 0x0000b8ba, 0x0000b8f0, 0x0000b923, - 0x0000b942, 0x0000b978, 0x0000b9a0, 0x0000b9c2, - 0x0000b9ee, 0x0000ba18, 0x0000ba23, 0x0000ba37, - 0x0000ba4d, 0x0000ba5a, 0x0000ba6d, 0x0000ba83, + 0x0000b589, 0x0000b5c0, 0x0000b5d8, 0x0000b613, + 0x0000b644, 0x0000b66c, 0x0000b69b, 0x0000b6d5, + 0x0000b6e7, 0x0000b705, 0x0000b736, 0x0000b748, + 0x0000b766, 0x0000b783, 0x0000b797, 0x0000b7b0, + 0x0000b7cf, 0x0000b7f6, 0x0000b82f, 0x0000b86d, + 0x0000b88f, 0x0000b8c8, 0x0000b8fd, 0x0000b92b, + 0x0000b944, 0x0000b961, 0x0000b988, 0x0000b9ad, + 0x0000b9cd, 0x0000b9e3, 0x0000ba15, 0x0000ba47, // Entry 540 - 55F - 0x0000ba95, 0x0000baa9, 0x0000babd, 0x0000bacf, - 0x0000bae1, 0x0000baf4, 0x0000bb08, 0x0000bb1a, - 0x0000bb30, 0x0000bb4a, 0x0000bb58, 0x0000bb68, - 0x0000bb73, 0x0000bb7e, 0x0000bb9a, 0x0000bbb2, - 0x0000bbc0, 0x0000bbd0, 0x0000bbe2, 0x0000bbf4, - 0x0000bc0b, 0x0000bc1a, 0x0000bc2d, 0x0000bc40, - 0x0000bc57, 0x0000bc65, 0x0000bc77, 0x0000bc8b, - 0x0000bca2, 0x0000bcb1, 0x0000bcc0, 0x0000bcd6, + 0x0000ba6a, 0x0000ba8b, 0x0000bab8, 0x0000bafa, + 0x0000bb30, 0x0000bb6d, 0x0000bb83, 0x0000bbaa, + 0x0000bbe2, 0x0000bc18, 0x0000bc3c, 0x0000bc69, + 0x0000bc7e, 0x0000bc9a, 0x0000bcaf, 0x0000bcca, + 0x0000bce6, 0x0000bd1c, 0x0000bd4f, 0x0000bd6e, + 0x0000bda4, 0x0000bdcc, 0x0000bdee, 0x0000be1a, + 0x0000be44, 0x0000be4f, 0x0000be63, 0x0000be79, + 0x0000be86, 0x0000be99, 0x0000beaf, 0x0000bec1, // Entry 560 - 57F - 0x0000bce6, 0x0000bcfa, 0x0000bd10, 0x0000bd23, - 0x0000bd7b, 0x0000bd90, 0x0000bda5, 0x0000bdb8, - 0x0000bdc5, 0x0000bdd2, 0x0000bde4, 0x0000bdfa, - 0x0000be09, 0x0000be24, 0x0000be37, 0x0000be49, - 0x0000be5d, 0x0000be76, 0x0000be95, 0x0000bea1, - 0x0000beae, 0x0000bec2, 0x0000bed6, 0x0000bee2, - 0x0000bef7, 0x0000bf08, 0x0000bf24, 0x0000bf3f, - 0x0000bf55, 0x0000bf77, 0x0000bf9d, 0x0000bfbd, + 0x0000bed5, 0x0000bee9, 0x0000befb, 0x0000bf0d, + 0x0000bf20, 0x0000bf34, 0x0000bf46, 0x0000bf5c, + 0x0000bf76, 0x0000bf84, 0x0000bf94, 0x0000bf9f, + 0x0000bfaa, 0x0000bfc6, 0x0000bfde, 0x0000bfec, + 0x0000bffc, 0x0000c00e, 0x0000c020, 0x0000c037, + 0x0000c046, 0x0000c059, 0x0000c06c, 0x0000c083, + 0x0000c091, 0x0000c0a3, 0x0000c0b7, 0x0000c0ce, + 0x0000c0dd, 0x0000c0ec, 0x0000c102, 0x0000c112, // Entry 580 - 59F - 0x0000bfcf, 0x0000c03d, 0x0000c057, -} // Size: 5668 bytes + 0x0000c126, 0x0000c13c, 0x0000c14f, 0x0000c1a7, + 0x0000c1bc, 0x0000c1d1, 0x0000c1e4, 0x0000c1f3, + 0x0000c1fc, 0x0000c205, 0x0000c210, 0x0000c21e, + 0x0000c25b, 0x0000c271, 0x0000c2a1, 0x0000c2df, + 0x0000c2ee, 0x0000c307, 0x0000c320, 0x0000c331, + 0x0000c34e, 0x0000c360, 0x0000c379, 0x0000c392, + 0x0000c3ae, 0x0000c3c7, 0x0000c3e2, 0x0000c405, + 0x0000c40e, 0x0000c42a, 0x0000c43f, 0x0000c453, + // Entry 5A0 - 5BF + 0x0000c471, 0x0000c498, 0x0000c4aa, 0x0000c4d9, + 0x0000c509, 0x0000c52c, 0x0000c53f, 0x0000c550, + 0x0000c55f, 0x0000c577, 0x0000c590, 0x0000c5bc, + 0x0000c5d6, 0x0000c5f6, 0x0000c61a, 0x0000c637, + 0x0000c64a, 0x0000c65e, 0x0000c677, 0x0000c692, + 0x0000c6ae, 0x0000c6cb, 0x0000c6e8, 0x0000c70c, + 0x0000c722, 0x0000c739, 0x0000c75a, 0x0000c774, + 0x0000c78b, 0x0000c7a5, 0x0000c7bf, 0x0000c7de, + // Entry 5C0 - 5DF + 0x0000c7fe, 0x0000c81b, 0x0000c83b, 0x0000c865, + 0x0000c88f, 0x0000c8aa, 0x0000c8bd, 0x0000c8db, + 0x0000c8fb, 0x0000c910, 0x0000c92d, 0x0000c94e, + 0x0000c968, 0x0000c996, 0x0000c9b2, 0x0000c9cf, + 0x0000c9e0, 0x0000c9f1, 0x0000c9ff, 0x0000ca0d, + 0x0000ca22, 0x0000ca37, 0x0000ca46, 0x0000ca59, + 0x0000ca6d, 0x0000ca83, 0x0000ca96, 0x0000caac, + 0x0000cabc, 0x0000cad8, 0x0000caeb, 0x0000cb09, + // Entry 5E0 - 5FF + 0x0000cb16, 0x0000cb26, 0x0000cb3f, 0x0000cb4e, + 0x0000cb60, 0x0000cb6e, 0x0000cb7f, 0x0000cb96, + 0x0000cba3, 0x0000cbb0, 0x0000cbc2, 0x0000cbd8, + 0x0000cbe7, 0x0000cc02, 0x0000cc15, 0x0000cc27, + 0x0000cc3b, 0x0000cc54, 0x0000cc73, 0x0000cc7f, + 0x0000cc8c, 0x0000cca0, 0x0000ccb4, 0x0000ccc0, + 0x0000ccd5, 0x0000cce6, 0x0000cd02, 0x0000cd1d, + 0x0000cd33, 0x0000cd40, 0x0000cd50, 0x0000cd63, + // Entry 600 - 61F + 0x0000cd73, 0x0000cd87, 0x0000cd98, 0x0000cdae, + 0x0000cdcb, 0x0000cdf0, 0x0000ce29, 0x0000ce4c, + 0x0000ce73, 0x0000ce8e, 0x0000cea6, 0x0000cec1, + 0x0000ced3, 0x0000ceec, 0x0000cf02, 0x0000cf1c, + 0x0000cf3a, 0x0000cf53, 0x0000cf6d, 0x0000cf8d, + 0x0000cfb3, 0x0000cfcc, 0x0000cfe6, 0x0000d002, + 0x0000d01d, 0x0000d040, 0x0000d05e, 0x0000d073, + 0x0000d087, 0x0000d0a4, 0x0000d0cc, 0x0000d0ed, + // Entry 620 - 63F + 0x0000d111, 0x0000d125, 0x0000d139, 0x0000d149, + 0x0000d15d, 0x0000d16f, 0x0000d18b, 0x0000d1a1, + 0x0000d1be, 0x0000d1e1, 0x0000d1fc, 0x0000d217, + 0x0000d233, 0x0000d266, 0x0000d29d, 0x0000d2c1, + 0x0000d2db, 0x0000d2f7, 0x0000d315, 0x0000d32d, + 0x0000d345, 0x0000d367, 0x0000d394, 0x0000d3bc, + 0x0000d3dd, 0x0000d3f5, 0x0000d411, 0x0000d42a, + 0x0000d44a, 0x0000d45d, 0x0000d479, 0x0000d49d, + // Entry 640 - 65F + 0x0000d4b9, 0x0000d4df, 0x0000d4ff, 0x0000d511, + 0x0000d57f, 0x0000d599, 0x0000d5a0, 0x0000d5b8, + 0x0000d5d7, 0x0000d5ec, +} // Size: 6464 bytes -const en_USData string = "" + // Size: 49239 bytes +const en_USData string = "" + // Size: 54764 bytes "\x02invalid share_mode %s\x02mx_priority range limited to [1,50]\x02inva" + "lid domain %s for MX record\x02invalid ipv4 %s for A record\x02invalid i" + "pv6 %s for AAAA record\x02invalid domain %s for CNAME record\x02duplicat" + @@ -1981,167 +2236,177 @@ const en_USData string = "" + // Size: 49239 bytes "t error %s\x02object size limit exceeds\x02bucket.GetQuotaKeys fail %s" + "\x02put object error %s\x02setAcl error %s\x02syncWithCloudBucket error " + "%s\x02Bucket has %d task active, can't sync status\x02not an empty bucke" + - "t\x02object %s not found\x02iBucket.GetIObjects error %s\x02unmarshal li" + - "mit error %s\x02SetLimit error %s\x02Update error %s\x02missing manager?" + - "\x02iBucket.GetIObject error %s\x02ValidateDeleteCondition error %s\x02T" + - "he image has been cached on storages\x02the image reference session has " + - "not been expired!\x02failed to found storagecache %s\x02not allow to que" + - "ry system capability\x02account is enabled\x02account is not idle\x02pro" + - "vider %s: %v\x02Cannot enable deleting account\x02invalid proxy setting " + - "%s\x02not support for cloudaccount with provider '%s'\x02Unsupported pro" + - "vider %s\x02Project %s(%s) not belong to domain %s(%s)\x02Not support br" + - "and %s, only support %s\x02check uniqness fail %s\x02The account has bee" + - "n registered\x02no such provider %s\x02invalid cloud account info error:" + - " %s\x02check account_id duplication error %s\x02the account has been reg" + - "isterd %s\x02Account disabled\x02Account auto sync enabled\x02invalid in" + - "put %s\x02failed to found provider factory error: %v\x02failed to unmars" + - "hal input params: %v\x02check uniqueness fail %s\x02account %s conflict" + - "\x02inconsistent account_id, previous '%s' and now '%s'\x02project %s no" + - "t found\x02cannot enable auto sync in status %s\x02provider is shared ou" + - "tside of domain\x02%s not support\x02%s not support create subscription" + - "\x02not allow to create\x02provider is enabled\x02provider is not idle" + - "\x02Directly creating cloudprovider is not supported, create cloudaccoun" + - "t instead\x02Region %s not found\x02Zone %s not found\x02Cloudprovider d" + - "isabled\x02Cloudaccount disabled\x02not allow to change project across d" + - "omain\x02cannot change to a different domain from a private cloud accoun" + - "t\x02fail to get provider driver %s\x02storage classes not supported\x02" + - "GetZoneCount fail %s\x02GetVpcCount fail %s\x02not empty cloud region" + - "\x02not allow to delete default cloud region\x02VPC %s not found\x02Cann" + - "ot update external resource\x02failed to found dbinstance %s\x02DBInstan" + - "ce %s(%s) status is %s require status is %s\x02failed to found region fo" + - "r dbinstance %s(%s)\x02failed to found dbinstance %s(%s) database %s: %v" + - "\x02Failed to found database %s for dbinstance %s(%s): %v\x02The account" + - " %s(%s) has permission %s to the database %s(%s)\x02Account status is no" + - "t %s current status is %s\x02Instance status is not %s current status is" + - " %s\x02Database status is not %s current is %s\x02Account %s(%s) does no" + - "t have database %s(%s) permissions\x02DBinstance has not valid cloudprov" + - "ider\x02DBInstance backup has %d task active, can't sync status\x02faile" + - "d to found dbinstance %s(%s) account %s: %v\x02Not Implemented\x02invali" + - "d address: %s\x02Ip %s not in network %s(%s) range\x02cloudprovider %s(%" + - "s) is not available\x02invalid duration %s\x02unsupported duration %s" + - "\x02cloudregion %s not support create rds\x02cloudregion %s not support " + - "create %s rds\x02not match any dbinstance sku\x02%s rds not support secg" + - "roup\x02%s rds Support up to %d security groups\x02Cannot do recovery db" + - "instance in status %s required status %s\x02backup %s(%s) not contain da" + - "tabase %s\x02conflict database %s for instance %s(%s)\x02back and instan" + - "ce not in same cloudaccount\x02backup and instance not in same cloudregi" + - "on\x02can not recover data from diff rds engine\x02Cannot do reboot dbin" + - "stance in status %s\x02DBInstance has %d task active, can't sync status" + - "\x02Cannot do renew dbinstance in status %s required status %s\x02misson" + - "g duration\x02DBInstance has opened the outer network connection\x02The " + - "extranet connection is not open\x02%s not support this operation\x02Cann" + - "ot change config in status %s\x02Unmarshal input error: %v\x02failed to " + - "match any skus for change config\x02DBInstance is locked, cannot delete" + - "\x02dbinstance billing type is %s\x02dbinstance billing type %s not supp" + - "ort cancel expire\x02guest %q not found\x02snapshotpolicy %s not found: " + - "%s\x02not support update disk_type %s\x02failed to find storage for disk" + - " %s\x02failed to find host for storage %s with disk %s\x02Storage %s not" + - " found\x02cloudprovider %s not available\x02storage %s(%s) need online a" + - "nd attach host for create disk\x02Cannot create disk with disabled stora" + - "ge[%s]\x02Cannot create disk with offline storage[%s]\x02Storage type[%s" + - "] not match backend %s\x02Storage[%s] must attach to a host\x02Not enoug" + - "h free space\x02Fetch snapshot count failed %s\x02Disk %s don't need con" + - "vert snapshots\x02Can not get disk snapshot\x02Get convert snapshot fail" + - "ed: %s\x02Snapshot %s dose not have convert snapshot\x02Cannot reset dis" + - "k in status %s\x02Cannot reset disk with snapshot in status %s\x02Cannot" + - " reset disk %s(%s),Snapshot is belong to disk %s\x02Resize disk when dis" + - "k is READY\x02Disk cannot be thrink\x02disk has no valid storage\x02disk" + - ".GetQuotaKeys fail %s\x02fail to find storage for disk %s\x02No zone for" + - " this disk\x02Duplicate image name %s\x02Save disk when disk is READY" + - "\x02GetRuningGuestCount fail %s\x02Save disk when not being USED\x02Imag" + - "e name is required\x02cloud provider %s is not available\x02cloud accoun" + - "t %s is not available\x02storage of disk %s no valid host\x02GetGuestDis" + - "kCount for disk %s fail %s\x02Virtual disk %s(%s) used by virtual server" + - "s\x02not allow to delete prepaid disk in valid status\x02Diskinfo index " + - "%d: both imageID and size are absent\x02Snapshot %s not found\x02Snapsho" + - "t %s storage %s not found, is public cloud?\x02Image status is not activ" + - "e\x02Disk has %d task active, can't sync status\x02GetSnapshotCount fail" + - " %s\x02not allow to purge. Virtual disk must not have snapshots\x02not a" + - "llow to delete. Virtual disk must not have snapshots\x02not allow to del" + - "ete %s disk with snapshots\x02no such snapshotpolicy %s\x02%s %s not sup" + - "ported dns type %s\x02%s %s not supported policy type %s\x02%s %s %s not" + - " support %s\x02duplicated with CNAME dnsrecord name not support\x02dupli" + - "cated dnsrecord with existed dnsrecord can not distinguish by %s policy" + - "\x02duplicated dnsrecord with existed dnsrecord not support\x02%s not su" + - "pport policy type %s\x02%s %s not support policy value %s\x02Not support" + - "\x02invalid domain name %s\x02Not support %s for vpc %s, supported %s" + - "\x02Not support %s for account %s, supported %s\x02top level public doma" + - "in name %s not support\x02unknown zone type %s\x02can not sync record se" + - "ts in %s\x02dns zone can not cache in status %s\x02Only %s support cache" + - " for account\x02account %s has been cached\x02dns zone can not uncache i" + - "n status %s\x02vpc %s has already in this dns zone\x02vpc %s not in dns " + - "zone\x02SRV: insufficient param: %s\x02SRV: invalid port number: %s\x02S" + - "RV: invalid weight number: %s\x02SRV: weight number %d not in range [0,6" + - "5535]\x02SRV: invalid priority number: %s\x02SRV: priority number %d not" + - " in range [0,65535]\x02SRV cannot mix with other types\x02CNAME cannot m" + - "ix with other types\x02PTR cannot mix with other types\x02%s: invalid do" + - "main name: %s\x02SRV: invalid srv record name: %s\x02PTR: invalid ptr re" + - "cord name: %s\x02%s: name cannot be ip address: %s\x02A: record value mu" + - "st be ipv4 address: %s\x02AAAA: record value must be ipv6 address: %s" + - "\x02%s: %s must be domain name: %s\x02%s: %s cannot be ip address: %s" + - "\x02%s: unknown record type\x02Empty record\x02invalid ttl: %s\x02invali" + - "d ttl: %d\x02Cannot mix different types of records, %s != %s\x02invalid " + - "condition\x02schedtag %s not found\x02unmarshal StandaloneResourceCreate" + - "Input fail %s\x02Resource type %s not support\x02Virtual resource type %" + - "s not support\x02%s %s not found\x02can't restore elastic cache in statu" + - "s %s\x02unsupport delete %s backups\x02invalid billing_cycle %s\x02unmar" + - "shal VirtualResourceCreateInput fail %s\x02Cannot do restart elasticcach" + - "e instance in status %s\x02Elastic cache is locked, cannot delete\x02Ela" + - "stic cache is not expired, cannot delete\x02provider mismatch: %s instan" + - "ce can't use %s sku\x02region mismatch: instance region %s, sku region %" + - "s\x02zone mismatch: instance zone %s, sku zone %s\x02engine version mism" + - "atch: instance version %s, sku version %s\x02can not change specificatio" + - "n in status %s\x02auth mode aready in status %s\x02maintain time has no " + - "change\x02public connection aready allocated\x02release public connectio" + - "n aready released\x02invalid parameter format. json dict required\x02Ela" + - "sticcache has %d task active, can't sync status\x02elasticcache billing " + - "type is %s\x02elasticcache billing type %s not support cancel expire\x02" + - "Cannot add security groups in status %s\x02region\x02regiondriver\x02not" + - " supported bind security group\x02beyond security group quantity limit, " + - "max items %d.\x02The secgroup name %s does not meet the requirements, pl" + - "ease change the name\x02secgroups will be empty after update.\x02%s is n" + - "ot modifiable\x02server %s not found\x02Not support associate type %s, o" + - "nly support %s\x02charge type %s not supported\x02eip has been associate" + - "d with instance\x02eip cannot associate in status %s\x02fixed eip cannot" + - " be associated\x02Unsupported %s\x02cannot associate pending delete serv" + - "er\x02instance is already associated with eip\x02cannot associate server" + - " in status %s\x02cannot associate eip with same network\x02server region" + - " is not found???\x02eip region is not found???\x02eip and server are not" + - " in the same region\x02eip and server are not in the same zone\x02server" + - " host is not found???\x02server and eip are not managed by the same prov" + - "ider\x02eip cannot dissociate in status %s\x02fixed public eip cannot be" + - " dissociated\x02the associated natgateway has corresponding snat rules w" + - "ith eip %s, please delete them firstly\x02the associated natgateway has " + - "corresponding dnat rules with eip %s, please delete them firstly\x02fixe" + - "d eip cannot sync status\x02cannot change bandwidth in status %s\x02Inva" + - "lid bandwidth\x02Cannot purge elastic_ip on enabled cloud provider\x02ac" + - "count %s not share for domain %s\x02please retry after unbind all guests" + - " in group\x02can not bind guest from disabled guest\x02can not unbind gu" + - "est from disabled guest\x02no such model %s\x02guest and instance group " + - "should belong to same project\x02Host missing\x02host status %s and enab" + - "led %v, can't do server %s\x02Cannot send command in status %s\x02No hos" + - "t for server\x02Cannot save image in status %s\x02No root image\x02Suppo" + - "rt only by KVM Hypervisor\x02Cannot sync in status %s\x02Cannot live mig" + - "rate in status %s\x02Can't clone guest with backup guest\x02Guest hyperv" + - "isor %s does not support clone\x02Cannot clone VM in status %s\x02Unmars" + - "hal input error %s\x02Cannot deploy in status %s\x02Disk %s and guest no" + - "t belong to the same account\x02Disk %s and guest not belong to the same" + - " zone\x02isAttached check failed %s\x02Disk %s has been attached\x02Disk" + - " %s not belong the guest's host\x02Disk in %s not able to attach\x02Gues" + - "t %s not support attach disk in status %s\x02Disk %s not found\x02Cannot" + - " suspend VM in status %s\x02Cannot resume VM in status %s\x02Some disk n" + - "ot ready\x02Cannot do start server in status %s\x02CD-ROM not empty, ple" + - "ase eject first\x02Insert ISO not allowed in status %s\x02No ISO to ejec" + - "t\x02Eject ISO not allowed in status %s\x02Cannot add security groups fo" + - "r hypervisor %s\x02guest %s band to up to %d security groups\x02security" + - " group %s has already been assigned to guest %s\x02Cannot revoke securit" + - "y groups in status %s\x02security group %s not assigned to guest %s\x02C" + - "annot assign security rules in status %s\x02Cannot set security rules in" + - " status %s\x02Cannot set security group for this guest %s\x02Cannot purg" + - "e server on enabled host\x02failed to find %s\x02invlid image\x02image s" + - "ize exceeds root disk size\x02Cannot switch OS between %s-%s\x02Can not " + - "rebuild root with with diff uefi image\x02No template for root disk, can" + - "not rebuild root\x02%s not support rebuild root with a different image" + + "t\x02object %s not found\x02iBucket.GetIObjects error %s\x02iBucket.SetW" + + "ebsite error %s\x02iBucket.DeleteWebSiteConf error %s\x02iBucket.GetWebs" + + "iteConf error %s\x02iBucket.SetCORS error %s\x02iBucket.DeleteCORS error" + + " %s\x02iBucket.GetCORSRules error %s\x02iBucket.GetCdnDomains error %s" + + "\x02iBucket.SetRefer error %s\x02iBucket.GetRefer error %s\x02unmarshal " + + "limit error %s\x02SetLimit error %s\x02Update error %s\x02missing manage" + + "r?\x02iBucket.GetIObject error %s\x02ValidateDeleteCondition error %s" + + "\x02The image has been cached on storages\x02the image reference session" + + " has not been expired!\x02failed to found storagecache %s\x02not allow t" + + "o query system capability\x02account %s not enable saml auth\x02account " + + "is enabled\x02account is not idle\x02provider %s: %v\x02Cannot enable de" + + "leting account\x02invalid proxy setting %s\x02not support for cloudaccou" + + "nt with provider '%s'\x02Unsupported provider %s\x02Project %s(%s) not b" + + "elong to domain %s(%s)\x02%s not support saml auth\x02Not support brand " + + "%s, only support %s\x02check uniqness fail %s\x02The account has been re" + + "gistered\x02no such provider %s\x02invalid cloud account info error: %s" + + "\x02check account_id duplication error %s\x02the account has been regist" + + "erd %s\x02Account disabled\x02Account auto sync enabled\x02invalid input" + + " %s\x02failed to found provider factory error: %v\x02failed to unmarshal" + + " input params: %v\x02check uniqueness fail %s\x02account %s conflict\x02" + + "inconsistent account_id, previous '%s' and now '%s'\x02project %s not fo" + + "und\x02cannot enable auto sync in status %s\x02provider is shared outsid" + + "e of domain\x02%s not support\x02%s not support create subscription\x02n" + + "ot allow to create\x02provider is enabled\x02provider is not idle\x02Dir" + + "ectly creating cloudprovider is not supported, create cloudaccount inste" + + "ad\x02Region %s not found\x02Zone %s not found\x02Cloudprovider disabled" + + "\x02Cloudaccount disabled\x02not allow to change project across domain" + + "\x02cannot change to a different domain from a private cloud account\x02" + + "fail to get provider driver %s\x02storage classes not supported\x02GetZo" + + "neCount fail %s\x02GetVpcCount fail %s\x02not empty cloud region\x02not " + + "allow to delete default cloud region\x02VPC %s not found\x02Cannot updat" + + "e external resource\x02failed to found dbinstance %s\x02DBInstance %s(%s" + + ") status is %s require status is %s\x02failed to found region for dbinst" + + "ance %s(%s)\x02failed to found dbinstance %s(%s) database %s: %v\x02Fail" + + "ed to found database %s for dbinstance %s(%s): %v\x02The account %s(%s) " + + "has permission %s to the database %s(%s)\x02Account status is not %s cur" + + "rent status is %s\x02Instance status is not %s current status is %s\x02D" + + "atabase status is not %s current is %s\x02Account %s(%s) does not have d" + + "atabase %s(%s) permissions\x02DBinstance has not valid cloudprovider\x02" + + "DBInstance backup has %d task active, can't sync status\x02failed to fou" + + "nd dbinstance %s(%s) account %s: %v\x02Not Implemented\x02invalid addres" + + "s: %s\x02Ip %s not in network %s(%s) range\x02cloudprovider %s(%s) is no" + + "t available\x02invalid duration %s\x02unsupported duration %s\x02cloudre" + + "gion %s not support create rds\x02cloudregion %s not support create %s r" + + "ds\x02not match any dbinstance sku\x02%s rds not support secgroup\x02%s " + + "rds Support up to %d security groups\x02Cannot do recovery dbinstance in" + + " status %s required status %s\x02backup %s(%s) not contain database %s" + + "\x02conflict database %s for instance %s(%s)\x02back and instance not in" + + " same cloudaccount\x02backup and instance not in same cloudregion\x02can" + + " not recover data from diff rds engine\x02Cannot do reboot dbinstance in" + + " status %s\x02DBInstance has %d task active, can't sync status\x02Cannot" + + " do renew dbinstance in status %s required status %s\x02missong duration" + + "\x02The dbinstance status need be %s, current is %s\x02Only %s dbinstanc" + + "e support this operation\x02DBInstance has opened the outer network conn" + + "ection\x02The extranet connection is not open\x02%s not support this ope" + + "ration\x02Cannot change config in status %s\x02Unmarshal input error: %v" + + "\x02failed to match any skus for change config\x02DBInstance is locked, " + + "cannot delete\x02dbinstance billing type is %s\x02dbinstance billing typ" + + "e %s not support cancel expire\x02guest %q not found\x02snapshotpolicy %" + + "s not found: %s\x02not support update disk_type %s\x02failed to find sto" + + "rage for disk %s\x02failed to find host for storage %s with disk %s\x02S" + + "torage %s not found\x02cloudprovider %s not available\x02storage %s(%s) " + + "need online and attach host for create disk\x02Cannot create disk with d" + + "isabled storage[%s]\x02Cannot create disk with offline storage[%s]\x02St" + + "orage type[%s] not match backend %s\x02Storage[%s] must attach to a host" + + "\x02Not enough free space\x02Fetch snapshot count failed %s\x02Disk %s d" + + "on't need convert snapshots\x02Can not get disk snapshot\x02Get convert " + + "snapshot failed: %s\x02Snapshot %s dose not have convert snapshot\x02Can" + + "not reset disk in status %s\x02Cannot reset disk with snapshot in status" + + " %s\x02Cannot reset disk %s(%s),Snapshot is belong to disk %s\x02Resize " + + "disk when disk is READY\x02Disk cannot be thrink\x02disk has no valid st" + + "orage\x02disk.GetQuotaKeys fail %s\x02fail to find storage for disk %s" + + "\x02No zone for this disk\x02Duplicate image name %s\x02Save disk when d" + + "isk is READY\x02GetRuningGuestCount fail %s\x02Save disk when not being " + + "USED\x02Image name is required\x02cloud provider %s is not available\x02" + + "cloud account %s is not available\x02storage of disk %s no valid host" + + "\x02GetGuestDiskCount for disk %s fail %s\x02Virtual disk %s(%s) used by" + + " virtual servers\x02not allow to delete prepaid disk in valid status\x02" + + "Diskinfo index %d: both imageID and size are absent\x02Snapshot %s not f" + + "ound\x02Snapshot %s storage %s not found, is public cloud?\x02Image stat" + + "us is not active\x02Disk has %d task active, can't sync status\x02GetSna" + + "pshotCount fail %s\x02not allow to purge. Virtual disk must not have sna" + + "pshots\x02not allow to delete. Virtual disk must not have snapshots\x02n" + + "ot allow to delete %s disk with snapshots\x02no such snapshotpolicy %s" + + "\x02%s %s not supported dns type %s\x02%s %s not supported policy type %" + + "s\x02%s %s %s not support %s\x02invalid record name %s\x02duplicated wit" + + "h CNAME dnsrecord name not support\x02duplicated dnsrecord with existed " + + "dnsrecord can not distinguish by %s policy\x02duplicated dnsrecord with " + + "existed dnsrecord not support\x02%s not support policy type %s\x02%s %s " + + "not support policy value %s\x02Not support\x02invalid domain name %s\x02" + + "Not support %s for vpc %s, supported %s\x02Not support %s for account %s" + + ", supported %s\x02top level public domain name %s not support\x02unknown" + + " zone type %s\x02can not sync record sets in %s\x02dns zone can not cach" + + "e in status %s\x02Only %s support cache for account\x02account %s has be" + + "en cached\x02dns zone can not uncache in status %s\x02vpc %s has already" + + " in this dns zone\x02vpc %s not in dns zone\x02SRV: insufficient param: " + + "%s\x02SRV: invalid port number: %s\x02SRV: invalid weight number: %s\x02" + + "SRV: weight number %d not in range [0,65535]\x02SRV: invalid priority nu" + + "mber: %s\x02SRV: priority number %d not in range [0,65535]\x02SRV cannot" + + " mix with other types\x02CNAME cannot mix with other types\x02PTR cannot" + + " mix with other types\x02%s: invalid domain name: %s\x02SRV: invalid srv" + + " record name: %s\x02PTR: invalid ptr record name: %s\x02%s: name cannot " + + "be ip address: %s\x02A: record value must be ipv4 address: %s\x02AAAA: r" + + "ecord value must be ipv6 address: %s\x02%s: %s must be domain name: %s" + + "\x02%s: %s cannot be ip address: %s\x02%s: unknown record type\x02Empty " + + "record\x02invalid ttl: %s\x02invalid ttl: %d\x02Cannot mix different typ" + + "es of records, %s != %s\x02invalid condition\x02schedtag %s not found" + + "\x02unmarshal StandaloneResourceCreateInput fail %s\x02Resource type %s " + + "not support\x02Virtual resource type %s not support\x02%s %s not found" + + "\x02can't restore elastic cache in status %s\x02unsupport delete %s back" + + "ups\x02invalid billing_cycle %s\x02unmarshal VirtualResourceCreateInput " + + "fail %s\x02Cannot do restart elasticcache instance in status %s\x02Elast" + + "ic cache is locked, cannot delete\x02Elastic cache is not expired, canno" + + "t delete\x02provider mismatch: %s instance can't use %s sku\x02region mi" + + "smatch: instance region %s, sku region %s\x02zone mismatch: instance zon" + + "e %s, sku zone %s\x02engine version mismatch: instance version %s, sku v" + + "ersion %s\x02can not change specification in status %s\x02auth mode area" + + "dy in status %s\x02no admin account found for elastic cache %s\x02mainta" + + "in time has no change\x02public connection aready allocated\x02release p" + + "ublic connection aready released\x02invalid parameter format. json dict " + + "required\x02Elasticcache has %d task active, can't sync status\x02elasti" + + "ccache billing type is %s\x02elasticcache billing type %s not support ca" + + "ncel expire\x02Cannot add security groups in status %s\x02region\x02regi" + + "ondriver\x02not supported bind security group\x02beyond security group q" + + "uantity limit, max items %d.\x02The secgroup name %s does not meet the r" + + "equirements, please change the name\x02secgroups will be empty after upd" + + "ate.\x02The elastic cache status need be %s, current is %s\x02Only %s el" + + "astic cache support set auto renew operation\x02elastic cache no related" + + " region found\x02Only %s elastic cache support renew operation\x02%s is " + + "not modifiable\x02server %s not found\x02Not support associate type %s, " + + "only support %s\x02charge type %s not supported\x02eip has been associat" + + "ed with instance\x02eip cannot associate in status %s\x02fixed eip canno" + + "t be associated\x02Unsupported %s\x02cannot associate pending delete ser" + + "ver\x02instance is already associated with eip\x02cannot associate serve" + + "r in status %s\x02cannot associate eip with same network\x02server regio" + + "n is not found???\x02eip region is not found???\x02eip and server are no" + + "t in the same region\x02eip and server are not in the same zone\x02serve" + + "r host is not found???\x02server and eip are not managed by the same pro" + + "vider\x02eip cannot dissociate in status %s\x02fixed public eip cannot b" + + "e dissociated\x02the associated natgateway has corresponding snat rules " + + "with eip %s, please delete them firstly\x02the associated natgateway has" + + " corresponding dnat rules with eip %s, please delete them firstly\x02fix" + + "ed eip cannot sync status\x02cannot change bandwidth in status %s\x02Inv" + + "alid bandwidth\x02Cannot purge elastic_ip on enabled cloud provider\x02a" + + "ccount %s not share for domain %s\x02please retry after unbind all guest" + + "s in group\x02can not bind guest from disabled guest\x02can not unbind g" + + "uest from disabled guest\x02no such model %s\x02guest and instance group" + + " should belong to same project\x02Host missing\x02host status %s and ena" + + "bled %v, can't do server %s\x02Cannot send command in status %s\x02No ho" + + "st for server\x02Cannot save image in status %s\x02No root image\x02Supp" + + "ort only by KVM Hypervisor\x02Cannot sync in status %s\x02Cannot live mi" + + "grate in status %s\x02Can't clone guest with backup guest\x02Guest hyper" + + "visor %s does not support clone\x02Cannot clone VM in status %s\x02Unmar" + + "shal input error %s\x02Cannot deploy in status %s\x02Disk %s and guest n" + + "ot belong to the same account\x02Disk %s and guest not belong to the sam" + + "e zone\x02isAttached check failed %s\x02Disk %s has been attached\x02Dis" + + "k %s not belong the guest's host\x02Disk in %s not able to attach\x02Gue" + + "st %s not support attach disk in status %s\x02Disk %s not found\x02Canno" + + "t suspend VM in status %s\x02Cannot resume VM in status %s\x02Some disk " + + "not ready\x02Cannot do start server in status %s\x02CD-ROM not empty, pl" + + "ease eject first\x02Insert ISO not allowed in status %s\x02No ISO to eje" + + "ct\x02Eject ISO not allowed in status %s\x02Cannot add security groups f" + + "or hypervisor %s\x02guest %s band to up to %d security groups\x02securit" + + "y group %s has already been assigned to guest %s\x02Cannot revoke securi" + + "ty groups in status %s\x02security group %s not assigned to guest %s\x02" + + "Cannot assign security rules in status %s\x02Cannot set security rules i" + + "n status %s\x02Cannot set security group for this guest %s\x02Cannot pur" + + "ge server on enabled host\x02failed to find %s\x02invlid image\x02image " + + "size exceeds root disk size\x02Cannot switch OS between %s-%s\x02Can not" + + " rebuild root with with diff uefi image\x02No template for root disk, ca" + + "nnot rebuild root\x02%s not support rebuild root with a different image" + "\x02Cannot reset root in status %s\x02keypair %s not found\x02No Disk In" + "fo Provided\x02No valid host\x02No valid storage on current host\x02Not " + "eough storage space on current host\x02failed to find disk %s\x02check i" + @@ -2304,286 +2569,292 @@ const en_USData string = "" + // Size: 49239 bytes "e\x02failed to find storage %s to attach host\x02failed to find host %s " + "to attach storage\x02unmarshal JoinResourceBaseCreateInput fail %s\x02Ge" + "tGuestDiskCount fail %s\x02GetGuestnicsCount fail %s\x02guest on the hos" + - "t are using networks on this wire\x02can't delete snapshot in deleting" + - "\x02Isolated device used by server\x02IsolatedDevice %s not found\x02Iso" + - "lated device already attached to another guest: %s\x02Isolated device us" + - "ed by server: %s\x02Unsupported scheme %s\x02invalid public error: %v" + - "\x02GetLinkedGuestsCount failed %s\x02Cannot delete keypair used by serv" + - "ers\x02find listener of listener rule %s(%s)\x02invalid addr %s\x02comme" + - "nt too long (%d>=%d)\x02comment contains non-printable char: %v\x02acl c" + - "idr duplicate %s\x02get acl count fail %s\x02acl %s is still referred to" + - " by %d %s\x02invalid vrrp interface %q\x02invalid vrrp authentication pa" + - "ss size: %d, want [1,8]\x02invalid vrrp priority %d: want [1,255]\x02inv" + - "alid vrrp virtual_router_id %d: want [1,255]\x02invalid vrrp advert_int " + - "%d: want [1,255]\x02telegraf params: invalid influxdb url: %s\x02%s: bad" + - " base64 encoded string: %s\x02%s: bad template: %s\x02fetch lbagents of " + - "other clusters: %v\x02conflict with lbagent %s(%s): %v\x02lbcluster %s(%" + - "s) already has virtual_router_id %d\x02%s: time error: %s\x02%s: new tim" + - "e is in the future: %s > %s\x02peer lbagent %s(%s) already has vrrp prio" + - "rity %d\x02use yum requires valid repo_base_url\x02empty host name\x02fi" + - "nd host %s: %v\x02lbagent cannot be deployed on managed host\x02find gue" + - "st %s: %v\x02lbagent cannot be deployed on public guests\x02server is in" + - " %q state, want %q\x02unmarshal input: %v\x02host missing %s field\x02em" + - "pty host %s field\x02authenticate error: %v\x02user must have system adm" + - "in privileges\x02get %s service %s url: %v\x02No previous deployment inf" + - "o available\x02query backend group releated resource failed.\x02weight %" + - "d not support, only support range 0 ~ 256\x02port %d not support, only s" + - "upport range 1 ~ 65535\x02failed to find guest %s\x02only sysadmin can s" + - "pecify host as backend\x02failed to find host %s\x02unexpected backend t" + - "ype %s\x02region of backend %d does not match that of lb's\x02failed to " + - "find region for loadbalancer %s\x02get isDefault fail %s\x02backend grou" + - "p %s is default backend group\x02get refCount fail %s\x02backend group %" + - "s is still referred by %d %s\x02%s requires the virtual machine state to" + - " be %s before it can be added backendgroup, but current state of the vir" + - "tual machine is %s\x02guest %s(%s) vpc %s(%s) not same as loadbalancer v" + - "pc %s\x02failed getting guest %s\x02guest %s(%s) vpc %s(%s) not same as " + - "vpc %s(%s)\x02guest %s(%s) is already in the backendgroup %s(%s)\x02fail" + - "ed to found region for loadbalancer backend %s\x02failed to found backen" + - "dgroup for backend %s(%s)\x02the acl cache in region %s aready exists." + - "\x02get certificate refcount fail %s\x02certificate %s is still referred" + - " to by %d %s\x02invalid local certificate, private key is empty.\x02inva" + - "lid local certificate, certificate is empty.\x02the certificate cache in" + - " region %s aready exists.\x02not allowed update content of certificate" + - "\x02allow only internal zone, got %s(%s)\x02wire zone must match zone pa" + - "rameter, got %s, want %s(%s)\x02zone of wire must be %s, got %s\x02get l" + - "bcluster refcount fail %v\x02lbcluster %s(%s) is still referred to by %d" + - " %s\x04\x00\x01 A\x02lbclusters %s(%s) and %s(%s) has conflict virtual_r" + - "outer_id: %d \x02invalid conditions format,required json\x02invalid cond" + - "itions fromat,required json array\x02condition values limit (5 per rule)" + - ". %d given.\x02rule %s/%s already occupied by rule %s(%s)\x02failed to f" + - "ind region for loadbalancer listener %s\x02failed to find region for loa" + - "dbalancer listener rule %s\x02%s listener port %d is already taken by li" + - "stener %s(%s)\x02cannot find region info\x02backend group %s(%s) belongs" + - " to loadbalancer %s, not %s\x04\x00\x01 0\x02cluster zone %s does not ma" + - "tch network zone %s \x02cluster wire affiliation does not match network'" + - "s: %s != %s\x02loadbalancer is locked, cannot delete\x02Unmarshal input " + - "failed %s\x02Port value error\x02invalid internal ip address: %s\x02eip " + - "has been binding to another instance\x02eip has been binding to snat rul" + - "es\x02No such eip\x02Nat gateway has %d task active, can't sync status" + - "\x02Only one of that sourceCIDR and netword_id is needed\x02cidr %s is n" + - "ot in range vpc %s\x02eip has been binding to dnat rules\x02no such netw" + - "ork\x02GetAllocatedNicCount fail %s\x02not an empty network %s\x02addres" + - "s %s is not in the range of network %s(%s)\x02isAddressUsed fail %s\x02a" + - "ddress %s is already occupied\x02getFreeAddressCount fail %s\x02network " + - "%s(%s) has no free addresses\x02candidate %s out of range\x02Out of IP a" + - "ddress\x02no allow to access network %s\x02Network %s not found: %v\x02A" + - "ddress %s not in range\x02Only system admin allowed to use reserved ip" + - "\x02Address %s not reserved\x02Address %s has been used\x02Bandwidth lim" + - "it cannot exceed %dMbps\x02Duration %s invalid\x02not a valid ip address" + - " %s: %s\x02Address %s not in network\x02get reserved ip error\x02found %" + - "d wires for zone %s and vpc %s\x02wire not found for zone %s and vpc %s" + - "\x02Invalid server_type: %s\x02valid vlan id\x02cannot derive valid ifna" + - "me hint: %v\x02ip_prefix error: %s\x02Invalid masklen %d\x02Invalid star" + - "t ip: %s %s\x02invalid end ip: %s %s\x02start and end ip not in the same" + - " subnet\x02%s: Invalid IP address %s\x02bad gateway ip: %v\x02gateway ip" + - " must be in the same subnet as start, end ip\x02zone and vpc info requir" + - "ed when wire is absent\x02VPC not ready\x02eip network can only exist in" + - " default vpc, got %s(%s)\x02subnet masklen should be smaller than 30\x02" + - "start and end ip when masked are not in the same cidr subnet\x02Network " + - "not in range of VPC cidrblock %s\x02fail to GetNetworks of vpc: %v\x02Co" + - "nflict address space with existing networks in vpc %q\x02query all netwo" + - "rks fail\x02Conflict address space with existing networks\x02Address bee" + - "n assigned out of new range\x02start, end ip must be in the same subnet" + - "\x02network server_type %s not support auto alloc\x02Parse Ip Failed\x02" + - "Cannot purge network on enabled cloud provider\x02Network %s not found" + - "\x02Invalid Target Network %s: inconsist %s\x02Incontinuity Network for " + - "%s and %s\x02only on premise support this operation\x02Invalid IP %s\x02" + - "Split IP %s is the start ip\x02Split IP %s out of range\x02Duplicate nam" + - "e %s\x02GenerateName fail %s\x02Generate ifname hint failed %s\x02ip\x02" + - "Only support server type %s\x02Only support on premise network\x02on-pre" + - "mise network cannot sync status\x02managed network cannot change status" + - "\x02invalid status %s\x02BgpType attribute is only useful for eip networ" + - "k\x02not support create\x02not support create definition\x02Cannot purge" + - " route_table on enabled cloud provider\x02unmarshaling cidrs failed: %s" + - "\x02min_instance_number should not be smaller than 0\x02min_instance_num" + - "ber should not be bigger than max_instance_number\x02desire_instance_num" + - "ber should between min_instance_number and max_instance_number\x02no suc" + - "h cloud region %s\x02ScalingGroup should have some networks\x02some netw" + - "orks not exist\x02network '%s' not in vpc '%s'\x02no such guest template" + - " %s\x02the guest template %s is not valid in cloudregion %s, reason: %s" + - "\x02unkown expansion principle %s\x02unkown shrink principle %s\x02unkow" + - "n health check mode %s\x02no such loadbalancer backend group '%s'\x02inv" + - "alid loadbalancer backend port '%d'\x02invalid loadbalancer backend weig" + - "ht '%d'\x02Please disable this ScalingGroup firstly\x02There are some gu" + - "ests in this ScalingGroup, please delete them firstly\x02no such Scaling" + - "Group '%s'\x02Guest '%s' don't belong to ScalingGroup '%s'\x02every scal" + - "ing policy belong to a scaling group\x02no such scaling group %s\x02unko" + - "wn trigger type %s\x02unkown scaling policy action %s\x02unkown scaling " + - "policy unit %s\x02Can't trigger scaling policy without status 'ready'" + - "\x02mismatched alarm id\x02unkown operator in alarm %s\x02unkown indicat" + - "or in alarm %s\x02unkown wrapper in alarm %s\x02the min value of cycle i" + - "n alarm is 300\x02invalid strategy %s\x02ResourceType %q not support\x02" + - "schedtag_id not provide\x02Not support resource_type %s\x02Schedtag %s" + - "\x02Schedtag %s resource_type mismatch: %s != %s\x02unmarshal JointResou" + - "rceCreateInput fail %s\x02Invalid schedtag %s\x02Invalid default strageg" + - "y %s\x02Cannot set default strategy of %s\x02GetObjectCount fail %s\x02T" + - "ag is associated with %s\x02getDynamicSchedtagCount fail %s\x02tag has d" + - "ynamic rules\x02getSchedPoliciesCount fail %s\x02tag is associate with s" + - "ched policies\x02Schedtag %s ResourceType is %s, not match %s\x02unkown " + - "scheduled type '%s'\x02unkown resource type '%s'\x02unkown resource oper" + - "ation '%s'\x02unkown label type '%s'\x02This scheduled task is being exe" + - "cuted now, please try later\x02need scheduled task\x02not enough privile" + - "ge\x02Failed to unmarshal input: %v\x02Failed fetching secgroup %s\x02ru" + - "le %d is invalid: %s\x02vpc %s(%s) is not a managed resouce\x02Not suppo" + - "rt cache classic security group\x02invalid ip address: %s\x02secgroup %s" + - " rules not equals %s rules\x02GetGuestsCount fail %s\x02the security gro" + - "up is in use\x02not allow to delete default security group\x02no such gu" + - "est template\x02fail to parse icon url '%s'\x02no such guest_template %s" + - "\x02failed to found cloudregion %s\x02failed to found zone %s\x02cpu_cor" + - "e_count should be range of 1~256\x02memory_size_mb, shoud be range of 51" + - "2~%d\x02instance_type_category shoud be one of %s\x02checkout server sku" + - " name duplicate error: %v\x02Duplicate sku %s\x02instance specs list que" + - "ry error\x02can not update instance_type for public cloud %s\x02Cannot c" + - "hange server sku name\x02check instance\x02now allow to delete inuse ins" + - "tance_type.please remove related servers first: %s\x02not allow to delet" + - "e public cloud instance_type: %s\x02failed to find cloudregion for zone " + - "%s(%s)\x02duplicate instanceType %s\x02query sku list failed.\x02delete " + - "sku %s failed.\x02Only support cache sku for private cloud\x02failed to " + - "get cloudprovider for region %s(%s)\x02cloudprovider %s(%s) disabled\x02" + - "Retention days must in 1~%d or -1\x02repeat_weekdays only contains %d da" + - "ys at most\x02time_points only contains %d points at most\x02Unmarshel i" + - "nput failed %s\x02Retention days must in 1~65535 or -1\x02Do not need to" + - " update\x02Couldn't delete snapshot policy binding to disks\x02no such d" + - "isk %s\x02snapshotpolicy disk has been exist\x02disk %s has too many sna" + - "pshot policy attached\x02guest %s not found\x02failed to found disk %s" + - "\x02failed to found region for disk's storage %s(%s)\x02Cannot delete sn" + - "apshot in status %s\x02Fetch instance snapshot error %s\x02snapshot refe" + - "renced by instance snapshot\x02Cannot delete snapshot on disk reset\x02S" + - "napshot has %d task active, can't sync status\x02Cannot Delete disk %s s" + - "napshots, disk exist\x02Disk %s dose not have snapshot\x02Can not delete" + - " disk snapshots, have manual snapshot\x02Cannot purge snapshot on enable" + - "d cloud provider\x02getReferenceCount fail %s\x02Image is in use\x02Acti" + - "ve download session not expired\x02Cannot delete the last cache\x02Canno" + - "t uncache in status %s\x02storage cache not empty\x02referered by storag" + - "es\x02cannot uncache non-customized images\x02storage not cache image" + - "\x02Fail to mark cache status: %s\x02missing image id or name\x02Invalid" + - " storage type %s\x02Invalid medium type %s\x02Not support create %s stor" + - "age\x02GetHostCount fail %s\x02storage has associate hosts\x02storage ha" + - "s disks\x02storage has snapshots\x02storage cache is missing\x02storage " + - "is enabled\x02can't detach host in status online\x02host %s storage %s n" + - "ot found\x02vpc_id\x02vpc on different cloudprovider peering is not supp" + - "orted\x02ipv4 range overlap\x02cloudprovider %s %s %s %s %s not supporte" + - "d CrossCloud vpcpeering\x02cloudprovider %s %s %s %s %s not supported Cr" + - "ossRegion vpcpeering\x02vpc %s and vpc %s have already connected\x02inva" + - "lid external_access_mode %q, want %s\x02GetNetworkCount fail %s\x02VPC n" + - "ot empty, please delete network first\x02GetNatgatewayCount fail %v\x02V" + - "PC not empty, please delete nat gateway first\x02not allow to delete def" + - "ault vpc\x02GetRequesterVpcPeeringConnections fail %v\x02VPC peering not" + - " empty, please delete vpc peering first\x02invalid cidr_block %s\x02Cann" + - "ot purge vpc on enabled cloud provider\x02on-premise vpc cannot sync sta" + - "tus\x02For default vpc, only system level sharing can be set\x02Prohibit" + - " making default vpc private\x02mapped ip exhausted\x02bandwidth must be " + - "greater than 0\x02mtu must be range of 0~1000000\x02Currently only kvm p" + - "latform supports creating wire\x02HostCount fail %s\x02wire contains hos" + - "ts\x02NetworkCount fail %s\x02wire contains networks\x02not empty zone" + - "\x02not support create %s zone\x02intranet loadbalancer not support band" + - "width charge type\x02Loadbalancer's manager (%s(%s)) does not match vpc'" + - "s(%s(%s)) (%s)\x02Aliyun not allow to change certificate\x02master slave" + - " backendgorup must contain two backend\x02Unsupport backendgorup type %s" + - "\x02invalid guest %s\x02Aliyun instance weight must be in the range of 0" + - " ~ 100\x02internal error: unexpected backend type %s\x02backendgroup %s " + - "not support this operation\x02region of host %q (%s) != region of loadba" + - "lancer %q (%s))\x02%s backend group not support change port\x02%s backen" + - "d group not support change port or weight\x02Unknown backend group type " + - "%s\x02listener type must be http/https, got %s\x02backend group %s(%s) b" + - "elongs to loadbalancer %s instead of %s\x02backend group type must be no" + - "rmal\x02loadbalancerlistenerrule %s(%s): fetching listener %s failed\x02" + - "http or https listener only supportd default or normal backendgroup\x02h" + - "ealth_check_domain must be in the range of 1 ~ 80\x02%s length must less" + - " 500 letters\x02sticky_session_cookie length must within 1~200\x02sticky" + - "_session_cookie can only contain letters, Numbers, '_' and '-'\x02Unknow" + - "n sticky_session_type, only support %s or %s\x02failed to find loadbalan" + - "cer's %s(%s) region\x02The specified Scheduler %s is invalid for perform" + - "ance sharing loadbalancer\x02failed to found loadbalancer for listener %" + - "s(%s)\x02cloudregion %s(%s) not support %s scheduler\x02invalid %s,requi" + - "red int\x02%s cannot be set to 0\x02%s not support close tcp or udp load" + - "balancer listener health check\x02Snapshot for %s name can't start with " + - "auto, http:// or https://\x02Aliyun %s not support recovery\x02Aliyun %s" + - " only support recover from it self backups\x02Aliyun %s only 8.0 and 5.7" + - " high_availability local_ssd or 5.6 high_availability support recovery f" + - "rom it self backups\x02slave dbinstance not support prepaid billing type" + - "\x02failed to match any skus in the network %s(%s) zone %s(%s)\x02Not su" + - "pport create readonly dbinstance for MySQL %s %s\x02Not support create r" + - "eadonly dbinstance for MySQL %s %s with storage type %s, only support %s" + - "\x02Not support create readonly dbinstance for MySQL %s\x02SQL Server on" + - "ly support create readonly dbinstance for 2017_ent\x02SQL Server cannot " + - "have more than seven read-only dbinstances\x02Not support create readonl" + - "y dbinstance with master dbinstance engine %s\x02Master dbinstance memor" + - "y ≥64GB, up to 10 read-only instances are allowed to be created\x02Maste" + - "r dbinstance memory <64GB, up to 5 read-only instances are allowed to be" + - " created\x02At least two networks are required under vpc %s(%s) with ali" + - "yun %s(%s)\x02Description can not start with http:// or https://\x02Aliy" + - "un DBInstance account name length shoud be 2~16 characters\x02%s is rese" + - "rved for aliyun %s, please use another\x02invalid character %s for accou" + - "nt name\x02account name can not start or end with _\x02%s only support a" + - "liyun %s or %s\x02%s only support aliyun %s\x02Unknown privilege %s\x02n" + - "etwork %s related vpc not found\x02account_privilege %s only support red" + - "is version 4.0\x02required at least %d subnet.\x02required at least %d s" + - "ubnet with at least 8 free ip.\x02Loadbalancer's manager %s does not mat" + - "ch vpc's(%s(%s)) (%s)\x02all networks should in the same vpc. (%s).\x02a" + - "lready has one network in the zone %s. (%s).\x02invalid parameter loadba" + - "lancer_spec %s\x02invalid parameter backendgroup %s\x02invalid loadbalan" + - "cer_spec %s\x02%s does not currently support creating loadbalancer acl" + - "\x02%s does not currently support creating loadbalancer certificate\x02l" + - "oadbalancer listener %s related loadbalancer %s not found\x02The backend" + - " %s is already registered on port %d\x02%s does not currently support cr" + - "eating loadbalancer\x02disk and snapshotpolicy should have same domain" + - "\x02disk and snapshotpolicy should have same project\x02%s does not supp" + - "ort creating loadbalancer\x02%s does not support creating loadbalancer a" + - "cl\x02%s does not support creating loadbalancer certificate\x02Google db" + - "instance not support prepaid billing type\x02disk size gb must in range " + - "10 ~ 30720 Gb\x02eip's manager (%s(%s)) does not match vpc's(%s(%s)) (%s" + - ")\x02loadbalancer is using by %d listener.\x02loadbalancer is using by %" + - "d backendgroup.\x02Not support create read-only dbinstance for %s\x02Hua" + - "wei dbinstance name length shoud be 4~64 characters\x02%s require disk s" + - "ize must in 40 ~ 4000 GB\x02The disk_size_gb must be an integer multiple" + - " of 10\x02Not support create account for huawei cloud %s instance\x02Hua" + - "wei rds password cannot be in the same reverse order as the account\x02N" + - "ot support create database for huawei cloud %s instance\x02Huawei DBInst" + - "ance backup name length shoud be 4~64 characters\x02Huawei only supports" + - " specified databases with %s\x02Huawei DBInstance Disk cannot be thrink" + - "\x02Huawei DBInstance category cannot change\x02Huawei DBInstance storag" + - "e type cannot change\x02Huawei current not support reset dbinstance acco" + - "unt password\x02No need to grant or revoke privilege for admin account" + - "\x02%s not support recovery\x02Huawei %s rds not support recovery from i" + - "t self rds backup\x02Huawei only %s engine support databases recovery" + - "\x02New databases name can not be one of %s\x02zone mismatch, elastic ca" + - "che sku zone %s != %s\x02sku %s is soldout\x02%s not support create acco" + - "unt\x02huawei %s mode elastic not support create backup\x02zone info mis" + - "sing\x02vpc lb is not allowed for now\x02zone %s(%s) has no lbcluster" + - "\x02no viable lbcluster\x02host %s has no access ip\x02error getting hos" + - "t of guest %s\x02error loadbalancer of backend group %s\x02region of hos" + - "t %q (%s) != region of loadbalancer %q (%s)\x02redirect must have at lea" + - "st one of scheme, host, path changed\x02backend_group argument is missin" + - "g\x02non redirect lblistener rule must have backend_group set\x02redirec" + - "t can only be enabled for http/https listener\x02non http listener must " + - "have backend group set\x02no available eip network\x02bad network type %" + - "q, want %q\x02failed to found vpc for network %s(%s)\x02network %s(%s) d" + - "oes not belong to %s\x02Kvm snapshot missing storage ??\x02failed to fin" + - "d acl %s\x02cannot change loadbalancer listener listener_type\x02cannot " + - "change loadbalancer listener listener_port\x02can not make backup in sta" + - "tus %s\x02loadbalancer listener %s is already updating\x02loadbalancer b" + - "ackendgroup aready associate with other %s listener\x02%s request the ma" + - "sk range should be between 16 and 28\x02loadbalancer aready associated w" + - "ith fourth layer listener %s\x02path can not be emtpy\x02server %s with " + - "port %d already in used\x02server %s with port %d aready used by other %" + - "s listener\x02Qcloud Basic MySQL instance not support create backup\x02N" + - "ot support create Qcloud databases\x02redis version 2.8 not support crea" + - "te account\x02Empty spec query key\x02Parse spec key %s error: %v\x02Get" + - " object error: %v\x02empty project_id/tenant_id\x02tenant/project %s not" + - " found\x02Snapshot reference(by disk) count > 0, can not delete\x02disk " + - "need at least one of snapshot as backing file\x02Disk %s dosen't attach " + - "guest ?\x02Disk attached Guest has backup, Can't create snapshot\x02Cann" + - "ot do snapshot when VM in status %s\x02check disk snapshot count fail %s" + - "\x02Disk %s snapshot full, cannot take any more\x02This RBD Storage[%s/%" + - "s] has already exist\x02BadGateway\x02InternalServerError\x02ResourceNot" + - "ReadyError\x02PaymentError\x02ImageNotFoundError\x02ResourceNotFoundErro" + - "r\x02SpecNotFoundError\x02ActionNotFoundError\x02TenantNotFoundError\x02" + - "UserNotFoundError\x02ServerStatusError\x02InvalidFormatError\x02InputPar" + - "ameterError\x02WeakPasswordError\x02MissingParameterError\x02Insufficien" + - "tResourceError\x02OutOfResource\x02OutOfQuotaError\x02OutOfRange\x02OutO" + - "fLimit\x02NotSufficientPrivilegeError\x02UnsupportOperationError\x02NotE" + - "mptyError\x02BadRequestError\x02EmptyRequestError\x02UnauthorizedError" + + "t are using networks on this wire\x02can't delete instance snapshot with" + + " wrong status\x02Isolated device used by server\x02IsolatedDevice %s not" + + " found\x02Isolated device already attached to another guest: %s\x02Isola" + + "ted device used by server: %s\x02Unsupported scheme %s\x02invalid public" + + " error: %v\x02GetLinkedGuestsCount failed %s\x02Cannot delete keypair us" + + "ed by servers\x02find listener of listener rule %s(%s)\x02invalid addr %" + + "s\x02comment too long (%d>=%d)\x02comment contains non-printable char: %" + + "v\x02acl cidr duplicate %s\x02get acl count fail %s\x02acl %s is still r" + + "eferred to by %d %s\x02invalid vrrp interface %q\x02invalid vrrp authent" + + "ication pass size: %d, want [1,8]\x02invalid vrrp priority %d: want [1,2" + + "55]\x02invalid vrrp virtual_router_id %d: want [1,255]\x02invalid vrrp a" + + "dvert_int %d: want [1,255]\x02telegraf params: invalid influxdb url: %s" + + "\x02%s: bad base64 encoded string: %s\x02%s: bad template: %s\x02fetch l" + + "bagents of other clusters: %v\x02conflict with lbagent %s(%s): %v\x02lbc" + + "luster %s(%s) already has virtual_router_id %d\x02%s: time error: %s\x02" + + "%s: new time is in the future: %s > %s\x02peer lbagent %s(%s) already ha" + + "s vrrp priority %d\x02use yum requires valid repo_base_url\x02empty host" + + " name\x02find host %s: %v\x02lbagent cannot be deployed on managed host" + + "\x02find guest %s: %v\x02lbagent cannot be deployed on public guests\x02" + + "server is in %q state, want %q\x02unmarshal input: %v\x02host missing %s" + + " field\x02empty host %s field\x02authenticate error: %v\x02user must hav" + + "e system admin privileges\x02get %s service %s url: %v\x02No previous de" + + "ployment info available\x02query backend group releated resource failed." + + "\x02weight %d not support, only support range 0 ~ 256\x02port %d not sup" + + "port, only support range 1 ~ 65535\x02failed to find guest %s\x02only sy" + + "sadmin can specify host as backend\x02failed to find host %s\x02unexpect" + + "ed backend type %s\x02region of backend %d does not match that of lb's" + + "\x02failed to find region for loadbalancer %s\x02get isDefault fail %s" + + "\x02backend group %s is default backend group\x02get refCount fail %s" + + "\x02backend group %s is still referred by %d %s\x02%s requires the virtu" + + "al machine state to be %s before it can be added backendgroup, but curre" + + "nt state of the virtual machine is %s\x02guest %s(%s) vpc %s(%s) not sam" + + "e as loadbalancer vpc %s\x02failed getting guest %s\x02guest %s(%s) vpc " + + "%s(%s) not same as vpc %s(%s)\x02guest %s(%s) is already in the backendg" + + "roup %s(%s)\x02failed to found region for loadbalancer backend %s\x02fai" + + "led to found backendgroup for backend %s(%s)\x02the acl cache in region " + + "%s aready exists.\x02get certificate refcount fail %s\x02certificate %s " + + "is still referred to by %d %s\x02invalid local certificate, private key " + + "is empty.\x02invalid local certificate, certificate is empty.\x02the cer" + + "tificate cache in region %s aready exists.\x02not allowed update content" + + " of certificate\x02allow only internal zone, got %s(%s)\x02wire zone mus" + + "t match zone parameter, got %s, want %s(%s)\x02zone of wire must be %s, " + + "got %s\x02get lbcluster refcount fail %v\x02lbcluster %s(%s) is still re" + + "ferred to by %d %s\x04\x00\x01 A\x02lbclusters %s(%s) and %s(%s) has con" + + "flict virtual_router_id: %d \x02invalid conditions format,required json" + + "\x02invalid conditions fromat,required json array\x02condition values li" + + "mit (5 per rule). %d given.\x02rule %s/%s already occupied by rule %s(%s" + + ")\x02failed to find region for loadbalancer listener %s\x02failed to fin" + + "d region for loadbalancer listener rule %s\x02%s listener port %d is alr" + + "eady taken by listener %s(%s)\x02cannot find region info\x02backend grou" + + "p %s(%s) belongs to loadbalancer %s, not %s\x04\x00\x01 0\x02cluster zon" + + "e %s does not match network zone %s \x02cluster wire affiliation does no" + + "t match network's: %s != %s\x02loadbalancer is locked, cannot delete\x02" + + "Unmarshal input failed %s\x02Port value error\x02invalid internal ip add" + + "ress: %s\x02eip has been binding to another instance\x02eip has been bin" + + "ding to snat rules\x02No such eip\x02Nat gateway has %d task active, can" + + "'t sync status\x02Only one of that sourceCIDR and netword_id is needed" + + "\x02cidr %s is not in range vpc %s\x02eip has been binding to dnat rules" + + "\x02no such network\x02fetch guest %s: %v\x02fetch guest nic: %v\x02cann" + + "ot fetch network of guestnetwork %d\x02unknown parent object id spec\x02" + + "got unknown type %q, expect %s\x02got unknown parent type %q, expect %s" + + "\x02allocate ip addr: %v\x02GetAllocatedNicCount fail %s\x02not an empty" + + " network %s\x02address %s is not in the range of network %s(%s)\x02isAdd" + + "ressUsed fail %s\x02address %s is already occupied\x02getFreeAddressCoun" + + "t fail %s\x02network %s(%s) has no free addresses\x02candidate %s out of" + + " range\x02Out of IP address\x02no allow to access network %s\x02Network " + + "%s not found: %v\x02Address %s not in range\x02Only system admin allowed" + + " to use reserved ip\x02Address %s not reserved\x02Address %s has been us" + + "ed\x02Bandwidth limit cannot exceed %dMbps\x02Duration %s invalid\x02not" + + " a valid ip address %s: %s\x02Address %s not in network\x02get reserved " + + "ip error\x02found %d wires for zone %s and vpc %s\x02wire not found for " + + "zone %s and vpc %s\x02Invalid server_type: %s\x02valid vlan id\x02cannot" + + " derive valid ifname hint: %v\x02ip_prefix error: %s\x02Invalid masklen " + + "%d\x02Invalid start ip: %s %s\x02invalid end ip: %s %s\x02start and end " + + "ip not in the same subnet\x02%s: Invalid IP address %s\x02bad gateway ip" + + ": %v\x02gateway ip must be in the same subnet as start, end ip\x02zone a" + + "nd vpc info required when wire is absent\x02VPC not ready\x02eip network" + + " can only exist in default vpc, got %s(%s)\x02subnet masklen should be s" + + "maller than 30\x02start and end ip when masked are not in the same cidr " + + "subnet\x02Network not in range of VPC cidrblock %s\x02fail to GetNetwork" + + "s of vpc: %v\x02Conflict address space with existing networks in vpc %q" + + "\x02query all networks fail\x02Conflict address space with existing netw" + + "orks\x02Address been assigned out of new range\x02start, end ip must be " + + "in the same subnet\x02network server_type %s not support auto alloc\x02P" + + "arse Ip Failed\x02Cannot purge network on enabled cloud provider\x02Netw" + + "ork %s not found\x02Invalid Target Network %s: inconsist %s\x02Incontinu" + + "ity Network for %s and %s\x02only on premise support this operation\x02I" + + "nvalid IP %s\x02Split IP %s is the start ip\x02Split IP %s out of range" + + "\x02Duplicate name %s\x02GenerateName fail %s\x02Generate ifname hint fa" + + "iled %s\x02ip\x02Only support server type %s\x02Only support on premise " + + "network\x02on-premise network cannot sync status\x02managed network cann" + + "ot change status\x02invalid status %s\x02BgpType attribute is only usefu" + + "l for eip network\x02not support create\x02not support create definition" + + "\x02invalid cidr %s\x02not enough privilege\x02not supported next hop ty" + + "pe\x02Not support modify routetable for provider %s\x02Cannot purge rout" + + "e_table on enabled cloud provider\x02unmarshaling cidrs failed: %s\x02mi" + + "n_instance_number should not be smaller than 0\x02min_instance_number sh" + + "ould not be bigger than max_instance_number\x02desire_instance_number sh" + + "ould between min_instance_number and max_instance_number\x02no such clou" + + "d region %s\x02ScalingGroup should have some networks\x02some networks n" + + "ot exist\x02network '%s' not in vpc '%s'\x02no such guest template %s" + + "\x02the guest template %s is not valid in cloudregion %s, reason: %s\x02" + + "unkown expansion principle %s\x02unkown shrink principle %s\x02unkown he" + + "alth check mode %s\x02no such loadbalancer backend group '%s'\x02invalid" + + " loadbalancer backend port '%d'\x02invalid loadbalancer backend weight '" + + "%d'\x02Please disable this ScalingGroup firstly\x02There are some guests" + + " in this ScalingGroup, please delete them firstly\x02no such ScalingGrou" + + "p '%s'\x02Guest '%s' don't belong to ScalingGroup '%s'\x02every scaling " + + "policy belong to a scaling group\x02no such scaling group %s\x02unkown t" + + "rigger type %s\x02unkown scaling policy action %s\x02unkown scaling poli" + + "cy unit %s\x02Can't trigger scaling policy without status 'ready'\x02mis" + + "matched alarm id\x02unkown operator in alarm %s\x02unkown indicator in a" + + "larm %s\x02unkown wrapper in alarm %s\x02the min value of cycle in alarm" + + " is 300\x02invalid strategy %s\x02ResourceType %q not support\x02schedta" + + "g_id not provide\x02Not support resource_type %s\x02Schedtag %s\x02Sched" + + "tag %s resource_type mismatch: %s != %s\x02unmarshal JointResourceCreate" + + "Input fail %s\x02Invalid schedtag %s\x02Invalid default stragegy %s\x02C" + + "annot set default strategy of %s\x02GetObjectCount fail %s\x02Tag is ass" + + "ociated with %s\x02getDynamicSchedtagCount fail %s\x02tag has dynamic ru" + + "les\x02getSchedPoliciesCount fail %s\x02tag is associate with sched poli" + + "cies\x02Schedtag %s ResourceType is %s, not match %s\x02unkown scheduled" + + " type '%s'\x02unkown resource type '%s'\x02unkown resource operation '%s" + + "'\x02unkown label type '%s'\x02This scheduled task is being executed now" + + ", please try later\x02need scheduled task\x02Failed to unmarshal input: " + + "%v\x02Failed fetching secgroup %s\x02rule %d is invalid: %s\x02vpc %s(%s" + + ") is not a managed resouce\x02Not support cache classic security group" + + "\x02invalid ip address: %s\x02secgroup %s rules not equals %s rules\x02G" + + "etGuestsCount fail %s\x02the security group is in use\x02not allow to de" + + "lete default security group\x02no such guest template\x02fail to parse i" + + "con url '%s'\x02no such guest_template %s\x02zone %s not in cloudregion " + + "%s\x02cpu_core_count should be range of 1~256\x02memory_size_mb, shoud b" + + "e range of 512~%d\x02instance_type_category shoud be one of %s\x02checko" + + "ut server sku name duplicate error: %v\x02Duplicate sku %s\x02instance s" + + "pecs list query error\x02can not update instance_type for public cloud %" + + "s\x02Cannot change server sku name\x02check instance\x02now allow to del" + + "ete inuse instance_type.please remove related servers first: %s\x02not a" + + "llow to delete public cloud instance_type: %s\x02failed to find cloudreg" + + "ion for zone %s(%s)\x02duplicate instanceType %s\x02query sku list faile" + + "d.\x02delete sku %s failed.\x02Only support cache sku for private cloud" + + "\x02failed to get cloudprovider for region %s(%s)\x02cloudprovider %s(%s" + + ") disabled\x02Retention days must in 1~%d or -1\x02repeat_weekdays only " + + "contains %d days at most\x02time_points only contains %d points at most" + + "\x02Unmarshel input failed %s\x02Retention days must in 1~65535 or -1" + + "\x02Do not need to update\x02Couldn't delete snapshot policy binding to " + + "disks\x02no such disk %s\x02snapshotpolicy disk has been exist\x02disk %" + + "s has too many snapshot policy attached\x02guest %s not found\x02failed " + + "to found disk %s\x02failed to found region for disk's storage %s(%s)\x02" + + "Cannot delete snapshot in status %s\x02Fetch instance snapshot error %s" + + "\x02snapshot referenced by instance snapshot\x02Cannot delete snapshot o" + + "n disk reset\x02Snapshot has %d task active, can't sync status\x02Cannot" + + " Delete disk %s snapshots, disk exist\x02Disk %s dose not have snapshot" + + "\x02Can not delete disk snapshots, have manual snapshot\x02Cannot purge " + + "snapshot on enabled cloud provider\x02getReferenceCount fail %s\x02Image" + + " is in use\x02Active download session not expired\x02Cannot delete the l" + + "ast cache\x02Cannot uncache in status %s\x02storage cache not empty\x02r" + + "eferered by storages\x02cannot uncache non-customized images\x02storage " + + "not cache image\x02Fail to mark cache status: %s\x02missing image id or " + + "name\x02Invalid storage type %s\x02Invalid medium type %s\x02Not support" + + " create %s storage\x02GetHostCount fail %s\x02storage has associate host" + + "s\x02storage has disks\x02storage has snapshots\x02storage cache is miss" + + "ing\x02storage is enabled\x02can't detach host in status online\x02host " + + "%s storage %s not found\x02vpc_id\x02vpc on different cloudprovider peer" + + "ing is not supported\x02ipv4 range overlap\x02cloudprovider %s %s %s %s " + + "%s not supported CrossCloud vpcpeering\x02cloudprovider %s %s %s %s %s n" + + "ot supported CrossRegion vpcpeering\x02vpc %s and vpc %s have already co" + + "nnected\x02invalid external_access_mode %q, want %s\x02GetNetworkCount f" + + "ail %s\x02VPC not empty, please delete network first\x02GetNatgatewayCou" + + "nt fail %v\x02VPC not empty, please delete nat gateway first\x02not allo" + + "w to delete default vpc\x02GetRequesterVpcPeeringConnections fail %v\x02" + + "VPC peering not empty, please delete vpc peering first\x02invalid cidr_b" + + "lock %s\x02Cannot purge vpc on enabled cloud provider\x02on-premise vpc " + + "cannot sync status\x02For default vpc, only system level sharing can be " + + "set\x02Prohibit making default vpc private\x02mapped ip exhausted\x02ban" + + "dwidth must be greater than 0\x02mtu must be range of 0~1000000\x02Curre" + + "ntly only kvm platform supports creating wire\x02HostCount fail %s\x02wi" + + "re contains hosts\x02NetworkCount fail %s\x02wire contains networks\x02n" + + "ot empty zone\x02failed to found cloudregion %s\x02not support create %s" + + " zone\x02intranet loadbalancer not support bandwidth charge type\x02Load" + + "balancer's manager (%s(%s)) does not match vpc's(%s(%s)) (%s)\x02Aliyun " + + "not allow to change certificate\x02master slave backendgorup must contai" + + "n two backend\x02Unsupport backendgorup type %s\x02invalid guest %s\x02A" + + "liyun instance weight must be in the range of 0 ~ 100\x02internal error:" + + " unexpected backend type %s\x02backendgroup %s not support this operatio" + + "n\x02region of host %q (%s) != region of loadbalancer %q (%s))\x02%s bac" + + "kend group not support change port\x02%s backend group not support chang" + + "e port or weight\x02Unknown backend group type %s\x02listener type must " + + "be http/https, got %s\x02backend group %s(%s) belongs to loadbalancer %s" + + " instead of %s\x02backend group type must be normal\x02loadbalancerliste" + + "nerrule %s(%s): fetching listener %s failed\x02http or https listener on" + + "ly supportd default or normal backendgroup\x02health_check_domain must b" + + "e in the range of 1 ~ 80\x02%s length must less 500 letters\x02sticky_se" + + "ssion_cookie length must within 1~200\x02sticky_session_cookie can only " + + "contain letters, Numbers, '_' and '-'\x02Unknown sticky_session_type, on" + + "ly support %s or %s\x02failed to find loadbalancer's %s(%s) region\x02Th" + + "e specified Scheduler %s is invalid for performance sharing loadbalancer" + + "\x02failed to found loadbalancer for listener %s(%s)\x02cloudregion %s(%" + + "s) not support %s scheduler\x02invalid %s,required int\x02%s cannot be s" + + "et to 0\x02%s not support close tcp or udp loadbalancer listener health " + + "check\x02Snapshot for %s name can't start with auto, http:// or https://" + + "\x02Aliyun %s not support recovery\x02Aliyun %s only support recover fro" + + "m it self backups\x02Aliyun %s only 8.0 and 5.7 high_availability local_" + + "ssd or 5.6 high_availability support recovery from it self backups\x02sl" + + "ave dbinstance not support prepaid billing type\x02failed to match any s" + + "kus in the network %s(%s) zone %s(%s)\x02Not support create readonly dbi" + + "nstance for MySQL %s %s\x02Not support create readonly dbinstance for My" + + "SQL %s %s with storage type %s, only support %s\x02Not support create re" + + "adonly dbinstance for MySQL %s\x02SQL Server only support create readonl" + + "y dbinstance for 2017_ent\x02SQL Server cannot have more than seven read" + + "-only dbinstances\x02Not support create readonly dbinstance with master " + + "dbinstance engine %s\x02Master dbinstance memory ≥64GB, up to 10 read-on" + + "ly instances are allowed to be created\x02Master dbinstance memory <64GB" + + ", up to 5 read-only instances are allowed to be created\x02At least two " + + "networks are required under vpc %s(%s) with aliyun %s(%s)\x02Description" + + " can not start with http:// or https://\x02Aliyun DBInstance account nam" + + "e length shoud be 2~16 characters\x02%s is reserved for aliyun %s, pleas" + + "e use another\x02invalid character %s for account name\x02account name c" + + "an not start or end with _\x02%s only support aliyun %s or %s\x02%s only" + + " support aliyun %s\x02Unknown privilege %s\x02network %s related vpc not" + + " found\x02account_privilege %s only support redis version 4.0\x02require" + + "d at least %d subnet.\x02required at least %d subnet with at least 8 fre" + + "e ip.\x02Loadbalancer's manager %s does not match vpc's(%s(%s)) (%s)\x02" + + "all networks should in the same vpc. (%s).\x02already has one network in" + + " the zone %s. (%s).\x02invalid parameter loadbalancer_spec %s\x02invalid" + + " parameter backendgroup %s\x02invalid loadbalancer_spec %s\x02%s does no" + + "t currently support creating loadbalancer acl\x02%s does not currently s" + + "upport creating loadbalancer certificate\x02loadbalancer listener %s rel" + + "ated loadbalancer %s not found\x02The backend %s is already registered o" + + "n port %d\x02%s does not currently support creating loadbalancer\x02disk" + + " and snapshotpolicy should have same domain\x02disk and snapshotpolicy s" + + "hould have same project\x02%s does not support creating loadbalancer\x02" + + "%s does not support creating loadbalancer acl\x02%s does not support cre" + + "ating loadbalancer certificate\x02Google dbinstance not support prepaid " + + "billing type\x02disk size gb must in range 10 ~ 30720 Gb\x02eip's manage" + + "r (%s(%s)) does not match vpc's(%s(%s)) (%s)\x02loadbalancer is using by" + + " %d listener.\x02loadbalancer is using by %d backendgroup.\x02Not suppor" + + "t create read-only dbinstance for %s\x02Huawei dbinstance name length sh" + + "oud be 4~64 characters\x02%s require disk size must in 40 ~ 4000 GB\x02T" + + "he disk_size_gb must be an integer multiple of 10\x02Not support create " + + "account for huawei cloud %s instance\x02Huawei rds password cannot be in" + + " the same reverse order as the account\x02Not support create database fo" + + "r huawei cloud %s instance\x02Huawei DBInstance backup name length shoud" + + " be 4~64 characters\x02Huawei only supports specified databases with %s" + + "\x02Huawei DBInstance Disk cannot be thrink\x02Huawei DBInstance categor" + + "y cannot change\x02Huawei DBInstance storage type cannot change\x02Huawe" + + "i current not support reset dbinstance account password\x02No need to gr" + + "ant or revoke privilege for admin account\x02%s not support recovery\x02" + + "Huawei %s rds not support recovery from it self rds backup\x02Huawei onl" + + "y %s engine support databases recovery\x02New databases name can not be " + + "one of %s\x02zone mismatch, elastic cache sku zone %s != %s\x02elastic c" + + "ache sku zone (%s) and subnet zone (%s) mismatch\x02sku %s is soldout" + + "\x02%s not support create account\x02huawei %s mode elastic not support " + + "create backup\x02zone info missing\x02vpc lb is not allowed for now\x02z" + + "one %s(%s) has no lbcluster\x02no viable lbcluster\x02host %s has no acc" + + "ess ip\x02error getting host of guest %s\x02error loadbalancer of backen" + + "d group %s\x02region of host %q (%s) != region of loadbalancer %q (%s)" + + "\x02redirect must have at least one of scheme, host, path changed\x02bac" + + "kend_group argument is missing\x02non redirect lblistener rule must have" + + " backend_group set\x02redirect can only be enabled for http/https listen" + + "er\x02non http listener must have backend group set\x02no available eip " + + "network\x02bad network type %q, want %q\x02failed to found vpc for netwo" + + "rk %s(%s)\x02network %s(%s) does not belong to %s\x02Kvm snapshot missin" + + "g storage ??\x02failed to find acl %s\x02cannot change loadbalancer list" + + "ener listener_type\x02cannot change loadbalancer listener listener_port" + + "\x02account name 'root' is not allowed\x02can not make backup in status " + + "%s\x02loadbalancer listener %s is already updating\x02loadbalancer backe" + + "ndgroup aready associate with other %s listener\x02%s request the mask r" + + "ange should be between 16 and 28\x02loadbalancer aready associated with " + + "fourth layer listener %s\x02path can not be emtpy\x02server %s with port" + + " %d already in used\x02server %s with port %d aready used by other %s li" + + "stener\x02Qcloud Basic MySQL instance not support create backup\x02Not s" + + "upport create Qcloud databases\x02redis version 2.8 not support create a" + + "ccount\x02Empty spec query key\x02Parse spec key %s error: %v\x02Get obj" + + "ect error: %v\x02empty project_id/tenant_id\x02tenant/project %s not fou" + + "nd\x02Snapshot reference(by disk) count > 0, can not delete\x02disk need" + + " at least one of snapshot as backing file\x02Disk %s dosen't attach gues" + + "t ?\x02Disk attached Guest has backup, Can't create snapshot\x02Cannot d" + + "o snapshot when VM in status %s\x02check disk snapshot count fail %s\x02" + + "Disk %s snapshot full, cannot take any more\x02This RBD Storage[%s/%s] h" + + "as already exist\x02BadGateway\x02InternalServerError\x02ResourceNotRead" + + "yError\x02PaymentError\x02ImageNotFoundError\x02ResourceNotFoundError" + + "\x02SpecNotFoundError\x02ActionNotFoundError\x02TenantNotFoundError\x02U" + + "serNotFoundError\x02ServerStatusError\x02InvalidFormatError\x02InputPara" + + "meterError\x02WeakPasswordError\x02MissingParameterError\x02Insufficient" + + "ResourceError\x02OutOfResource\x02OutOfQuotaError\x02OutOfRange\x02OutOf" + + "Limit\x02NotSufficientPrivilegeError\x02UnsupportOperationError\x02NotEm" + + "ptyError\x02BadRequestError\x02EmptyRequestError\x02UnauthorizedError" + "\x02InvalidCredentialError\x02ForbiddenError\x02NotAcceptableError\x02Du" + "plicateNameError\x02DuplicateResourceError\x02ConflictError\x02ResourceB" + "usyError\x02RequireLicenseError\x02ProtectedResourceError\x02NoProjectEr" + @@ -2591,19 +2862,88 @@ const en_USData string = "" + // Size: 49239 bytes "upportedProtocol\x02PolicyDefinitionError\x02Image %s not found\x02passw" + "ord must be 12 chars of at least one digit, letter, uppercase letter and" + " punctuate\x02Missing parameter %s\x02Duplicate name %s %s\x02Duplicate " + - "ID %s %s\x02Unauthorized\x02InvalidToken\x02Name %s not found\x02No logi" + - "n secret found\x02no totp for %s\x02no recovery secrets for %s\x02totp s" + - "ecret exists\x02No password found\x02No ssh password: %s\x02invalid reso" + - "urces format\x02service %s not found error: %v\x02missing uid\x02missing" + - " pids\x02missing pid in pids\x02missing rid in pids\x02missing rid\x02pr" + - "oject is not found\x02No login key: %s\x02Not found kind in query: %v" + - "\x02Not found key in query: %v\x02unsupported action %s\x02Not find exec" + - "utor for data source\x02security group id should not be empty\x02failed " + - "to find SecurityGroup %s\x02no valid endpoint\x02require validated qclou" + - "d cross region vpcPeering bandwidth values:[10, 20, 50, 100, 200, 500, 1" + - "000],unit Mbps\x02failed parsing url %q: %v" + "ID %s %s\x02no such driver\x02empty DN\x02empty id\x02empty name\x02disa" + + "bled user\x02join user into project of default domain or identical domai" + + "n\x02sysadmin is protected\x02cannot remove current user from current pr" + + "oject\x02join group into project of default domain or identical domain" + + "\x02query error %s\x02missing input feild type\x02missing input field bl" + + "ob\x02encrypt error %s\x02cannot delete default domain\x02domain is enab" + + "led\x02domain is in use by user\x02group is in use by group\x02domain is" + + " in use by project\x02domain is in use by role\x02domain is in use by po" + + "licy\x02domain contains external resources\x02readonly\x02default domain" + + " is protected\x02field %s is readonly\x02endpoint is enabled\x02missing " + + "input field interface\x02missing input field service/service_id\x02not f" + + "ound cert %s\x02get sensitive config requires admin priviliges\x02cannot" + + " update config when enabled and connected\x02cannot update config when n" + + "ot idle\x02saveConfig fail %s\x02invalid template\x02missing driver\x02d" + + "river %s not supported\x02driver %s already exists\x02cannot delete defa" + + "ult SQL identity provider\x02cannot delete enabled idp\x02identity provi" + + "der with projects\x02enabled domain %s cannot be deleted\x02cannot updat" + + "e in sync status\x02domain is disabled\x02resource is enabled\x02missing" + + " input field type\x02fail to decode policy data\x02cannot delete system " + + "policy\x02cannot delete enabled policy\x02cannot delete system project" + + "\x02project contains external resources\x02project contains user\x02proj" + + "ect contains group\x02cannot alter system project name\x02region contain" + + "s endpoints\x02missing input field id\x02cannot alter name of role\x02ca" + + "nnot delete system role\x02role is being assigned to user\x02role is bei" + + "ng assigned to group\x02not supported update context\x02not supported up" + + "date context %s\x02inconsistent domain for project and roles\x02not supp" + + "orted secondary update context %s\x02service contains endpoints\x02servi" + + "ce is enabled\x02update config version fail %s\x02cannot alter sysadmin " + + "user name\x02invalid password: %s\x02cannot delete non-local user\x02use" + + "r contains external resources\x02cannot delete system user\x02cannot joi" + + "n user and group in differnt domain\x02cannot join read-only group\x02ca" + + "nnot leave read-only group\x02version mismatch\x02project disabled\x02us" + + "er disabled\x02expired token\x02invalid fernet token\x02invalid auth met" + + "hods\x02user not found\x02empty auth request\x02user not in project\x02i" + + "nvalid access key id\x02expired access key\x02unrecognized input %s\x02u" + + "nauthorized %s\x02fail to decode request body\x02duplicate username\x02u" + + "ser not found or not enabled\x02invalid user\x02invalid project\x02inter" + + "nal server error %s\x02invalid domain\x02not allow to auth\x02invalid to" + + "ken\x02invalid token %s\x02not allow to get usage\x02Unauthorized\x02Inv" + + "alidToken\x02Name %s not found\x02No login secret found\x02no totp for %" + + "s\x02no recovery secrets for %s\x02totp secret exists\x02No password fou" + + "nd\x02No ssh password: %s\x02invalid resources format\x02service %s not " + + "found error: %v\x02missing uid\x02missing pids\x02missing pid in pids" + + "\x02missing rid in pids\x02missing rid\x02project is not found\x02No log" + + "in key: %s\x02Not found kind in query: %v\x02Not found key in query: %v" + + "\x02unsupported action %s\x02url is empty\x02invalid url: %v\x02unsuppor" + + "t type: %s\x02app_id is empty\x02app_secret is empty\x02channel is empty" + + "\x02parameter %s is empty\x02unsupported no_data_state %s\x02unsupported" + + " execution_error_state %s\x02metric %s is invalid format, usage .\x02Cannot change state on pause alert\x02alert already att" + + "ached to notification\x02Alert is already un-paused\x02Alert is already " + + "paused\x02Invalid refresh format: %s\x02not find alert %s\x02not find no" + + "tification %s\x02dashboard_id is empty\x02can not find dashboard:%s\x02t" + + "he Comparator is illegal: %s\x02the reduce is illegal %s\x02the reduce i" + + "s illegal: %s\x02Alert resource driver not found\x02Alert resource drive" + + "r duplicate match\x02Invalid level format: %s\x02Invalid period format: " + + "%s\x02the AlertType is illegal:%s\x02Cannot delete system alert\x02thres" + + "hold:%s should be number type\x02Default data source not found\x02not su" + + "pport database\x02not support type %q\x02unsupported resource type %s" + + "\x02not found alert notification used by %s\x02unsupported notification " + + "type %s\x02Alert notification used by %d alert\x02input not json dict" + + "\x02not found signature\x02signature error\x02not found res_id %q\x02not" + + " found type %q\x02type %s rule already exists\x02no found rule setting" + + "\x02Invalid time_from format: %s\x02SuggestSysRuleConfig type is empty" + + "\x02project or domain is empty\x02not found resource_type %q\x02not foun" + + "d driver by type %q\x02resource type %q of driver does not match input %" + + "q\x02resource type must provided when resource_id specified\x02type or r" + + "esource_type must provided\x02not found rule by type %q\x02Invalid inter" + + "val format: %s\x02Unsupported notification type\x02the evalType is illeg" + + "al\x02Influxdb invalid status\x02Not find executor for data source\x02Co" + + "ndition is missing the threshold parameter\x02Condition is missing the t" + + "ype parameter\x02Invalid condition evaluator type\x02Unknown alert condi" + + "tion\x02Alert is missing conditions\x02input condition is empty\x02Unkow" + + "n alert condition type: %s\x02Unkown operator %s\x02select for nothing i" + + "n query\x02query duration err: from: %s, to:%s\x02query duration `to` er" + + "r: %s\x02security group id should not be empty\x02failed to find Securit" + + "yGroup %s\x02no valid endpoint\x02require validated qcloud cross region " + + "vpcPeering bandwidth values:[10, 20, 50, 100, 200, 500, 1000],unit Mbps" + + "\x02failed parsing url %q: %v\x02bad ip\x02unmarshal input fail %s\x02ch" + + "eck name duplication fail %s\x02policy is referenced" -var zh_CNIndex = []uint32{ // 1411 elements +var zh_CNIndex = []uint32{ // 1610 elements // Entry 0 - 1F 0x00000000, 0x00000018, 0x00000036, 0x0000005c, 0x00000085, 0x000000b1, 0x000000da, 0x000000f1, @@ -2635,376 +2975,432 @@ var zh_CNIndex = []uint32{ // 1411 elements 0x00000b71, 0x00000b81, 0x00000b97, 0x00000baf, 0x00000bc6, 0x00000bfd, 0x00000c2b, 0x00000c4d, 0x00000c6f, 0x00000c96, 0x00000caa, 0x00000cc1, - 0x00000cd8, 0x00000cff, 0x00000d31, 0x00000d68, - 0x00000d91, 0x00000da3, 0x00000db8, 0x00000dc2, - 0x00000dcb, 0x00000ddb, 0x00000de2, 0x00000dec, - 0x00000df6, 0x00000e08, 0x00000e24, 0x00000e37, - 0x00000e63, 0x00000e88, 0x00000ebb, 0x00000f24, + 0x00000cd8, 0x00000cff, 0x00000d27, 0x00000d54, + 0x00000d7d, 0x00000d8f, 0x00000da4, 0x00000dae, + 0x00000db7, 0x00000dc7, 0x00000dce, 0x00000dd8, + 0x00000de2, 0x00000df4, 0x00000e10, 0x00000e23, + 0x00000e4f, 0x00000e74, 0x00000ea7, 0x00000f10, // Entry 80 - 9F - 0x00000f4a, 0x00000f6c, 0x00000f8d, 0x00000fac, - 0x00000fd1, 0x00000fea, 0x00001014, 0x00001038, - 0x00001059, 0x00001081, 0x0000109c, 0x000010bc, - 0x000010e2, 0x000010f4, 0x00001109, 0x00001140, - 0x00001168, 0x00001199, 0x000011b5, 0x000011d0, - 0x000011e9, 0x00001212, 0x00001227, 0x0000123d, - 0x00001298, 0x000012cc, 0x000012f1, 0x00001312, - 0x0000133a, 0x00001360, 0x0000137d, 0x000013ad, + 0x00000f36, 0x00000f58, 0x00000f79, 0x00000f98, + 0x00000fbd, 0x00000fd6, 0x00001000, 0x00001024, + 0x00001045, 0x0000106d, 0x00001088, 0x000010a8, + 0x000010ce, 0x000010e0, 0x000010f5, 0x0000112c, + 0x00001154, 0x00001185, 0x000011a1, 0x000011bc, + 0x000011d5, 0x000011fe, 0x00001213, 0x00001229, + 0x00001284, 0x000012b8, 0x000012dd, 0x000012fe, + 0x00001326, 0x0000134c, 0x00001369, 0x00001399, // Entry A0 - BF - 0x000013d7, 0x000013f8, 0x00001427, 0x00001456, - 0x00001486, 0x00001486, 0x000014a4, 0x000014cd, - 0x000014e5, 0x0000150a, 0x0000153b, 0x00001554, - 0x00001570, 0x00001598, 0x000015ad, 0x000015cc, - 0x000015e2, 0x00001622, 0x00001662, 0x00001683, - 0x000016c2, 0x000016cc, 0x000016e7, 0x00001706, - 0x00001724, 0x00001742, 0x00001758, 0x0000179a, - 0x000017c0, 0x000017e5, 0x00001803, 0x0000182c, + 0x000013c3, 0x000013e4, 0x00001413, 0x00001442, + 0x00001472, 0x00001472, 0x00001490, 0x000014b9, + 0x000014d1, 0x000014f6, 0x00001527, 0x00001540, + 0x0000155c, 0x00001584, 0x00001599, 0x000015b8, + 0x000015ce, 0x0000160e, 0x0000164e, 0x0000166f, + 0x000016ae, 0x000016b8, 0x000016d3, 0x000016f2, + 0x00001710, 0x0000172e, 0x00001744, 0x00001786, + 0x000017ac, 0x000017d1, 0x000017ef, 0x00001818, // Entry C0 - DF - 0x00001851, 0x0000186e, 0x0000188a, 0x000018b5, - 0x000018ce, 0x0000190e, 0x00001951, 0x00001985, - 0x000019aa, 0x000019db, 0x000019fb, 0x00001a0b, - 0x00001a2c, 0x00001a52, 0x00001a72, 0x00001a89, - 0x00001aa0, 0x00001abe, 0x00001ae0, 0x00001af9, - 0x00001b09, 0x00001b20, 0x00001b37, 0x00001b47, - 0x00001b5f, 0x00001b6c, 0x00001b87, 0x00001b97, - 0x00001bac, 0x00001bc8, 0x00001be1, 0x00001c06, + 0x0000183d, 0x0000185a, 0x00001876, 0x000018a1, + 0x000018ba, 0x000018fa, 0x0000193d, 0x00001971, + 0x00001996, 0x000019c7, 0x000019e7, 0x000019f7, + 0x00001a18, 0x00001a3e, 0x00001a5e, 0x00001a75, + 0x00001a8c, 0x00001aaa, 0x00001acc, 0x00001ae5, + 0x00001af5, 0x00001b0c, 0x00001b23, 0x00001b33, + 0x00001b4b, 0x00001b58, 0x00001b73, 0x00001b83, + 0x00001b98, 0x00001bb4, 0x00001bcd, 0x00001bf2, // Entry E0 - FF - 0x00001c1e, 0x00001c2f, 0x00001c4e, 0x00001c66, - 0x00001c7e, 0x00001c99, 0x00001cb3, 0x00001ccf, - 0x00001ce8, 0x00001d0d, 0x00001d25, 0x00001d3d, - 0x00001d62, 0x00001daa, 0x00001db7, 0x00001dc9, - 0x00001dee, 0x00001e0f, 0x00001e29, 0x00001e3b, - 0x00001e4c, 0x00001e70, 0x00001e93, 0x00001eaf, - 0x00001ece, 0x00001ee6, 0x00001f02, 0x00001f18, - 0x00001f28, 0x00001f38, 0x00001f5d, 0x00001f75, + 0x00001c0a, 0x00001c1b, 0x00001c3a, 0x00001c52, + 0x00001c6a, 0x00001c85, 0x00001c9f, 0x00001cbb, + 0x00001cd4, 0x00001cf9, 0x00001d11, 0x00001d29, + 0x00001d4e, 0x00001d96, 0x00001da3, 0x00001db5, + 0x00001dda, 0x00001dda, 0x00001dda, 0x00001dda, + 0x00001dda, 0x00001dda, 0x00001dda, 0x00001dda, + 0x00001dda, 0x00001dda, 0x00001dfb, 0x00001e15, + 0x00001e27, 0x00001e38, 0x00001e5c, 0x00001e7f, // Entry 100 - 11F - 0x00001f98, 0x00001fa4, 0x00001fc3, 0x00001fe3, - 0x00001ff9, 0x0000200c, 0x00002021, 0x0000203c, - 0x00002059, 0x0000206e, 0x0000207e, 0x0000209a, - 0x000020af, 0x000020cf, 0x000020f0, 0x0000210b, - 0x00002120, 0x00002150, 0x00002162, 0x00002189, - 0x000021a1, 0x000021ad, 0x000021c5, 0x000021d5, - 0x000021f3, 0x00002214, 0x0000224f, 0x00002262, - 0x00002273, 0x00002293, 0x000022af, 0x000022d1, + 0x00001e9b, 0x00001eba, 0x00001ed2, 0x00001eee, + 0x00001eee, 0x00001f04, 0x00001f14, 0x00001f24, + 0x00001f49, 0x00001f61, 0x00001f84, 0x00001f90, + 0x00001faf, 0x00001faf, 0x00001fcf, 0x00001fe5, + 0x00001ff8, 0x0000200d, 0x00002028, 0x00002045, + 0x0000205a, 0x0000206a, 0x00002086, 0x0000209b, + 0x000020bb, 0x000020dc, 0x000020f7, 0x0000210c, + 0x0000213c, 0x0000214e, 0x00002175, 0x0000218d, // Entry 120 - 13F - 0x000022fc, 0x0000231c, 0x00002332, 0x0000234b, - 0x00002363, 0x00002378, 0x00002399, 0x000023a9, - 0x000023c5, 0x000023e6, 0x00002421, 0x0000244f, - 0x00002481, 0x000024bf, 0x000024ef, 0x00002518, - 0x00002541, 0x0000256d, 0x00002598, 0x000025c4, - 0x00002621, 0x00002650, 0x0000265a, 0x0000266c, - 0x0000268d, 0x000026ab, 0x000026c3, 0x000026de, - 0x000026fb, 0x00002722, 0x00002743, 0x0000275c, + 0x00002199, 0x000021b1, 0x000021c1, 0x000021df, + 0x00002200, 0x0000223b, 0x0000224e, 0x0000225f, + 0x0000227f, 0x0000229b, 0x000022bd, 0x000022e8, + 0x00002308, 0x0000231e, 0x00002337, 0x0000234f, + 0x00002364, 0x00002385, 0x00002395, 0x000023b1, + 0x000023d2, 0x0000240d, 0x0000243b, 0x0000246d, + 0x000024ab, 0x000024db, 0x00002504, 0x0000252d, + 0x00002559, 0x00002584, 0x000025b0, 0x0000260d, // Entry 140 - 15F - 0x00002785, 0x000027c0, 0x000027e4, 0x00002805, - 0x00002839, 0x0000286c, 0x00002897, 0x000028c1, - 0x00002906, 0x00002953, 0x00002968, 0x0000298d, - 0x000029a3, 0x000029b8, 0x000029e5, 0x00002a0c, - 0x00002a31, 0x00002a59, 0x00002a7a, 0x00002ab3, - 0x00002ac5, 0x00002ae2, 0x00002b03, 0x00002b24, - 0x00002b5c, 0x00002b6e, 0x00002b85, 0x00002bb9, - 0x00002be6, 0x00002c0d, 0x00002c30, 0x00002c57, + 0x0000263c, 0x00002646, 0x00002658, 0x00002679, + 0x00002697, 0x000026af, 0x000026ca, 0x000026e7, + 0x0000270e, 0x0000272f, 0x00002748, 0x00002771, + 0x000027ac, 0x000027d0, 0x000027f1, 0x00002825, + 0x00002858, 0x00002883, 0x000028ad, 0x000028f2, + 0x0000293f, 0x00002954, 0x00002954, 0x00002954, + 0x00002979, 0x0000298f, 0x000029a4, 0x000029d1, + 0x000029f8, 0x00002a1d, 0x00002a45, 0x00002a66, // Entry 160 - 17F - 0x00002c6a, 0x00002c85, 0x00002ca3, 0x00002cbc, - 0x00002ce0, 0x00002d01, 0x00002d22, 0x00002d49, - 0x00002d7c, 0x00002da9, 0x00002dbc, 0x00002de7, - 0x00002e04, 0x00002e28, 0x00002e42, 0x00002e57, - 0x00002e87, 0x00002eac, 0x00002edd, 0x00002ef9, - 0x00002f13, 0x00002f28, 0x00002f58, 0x00002f86, - 0x00002fa5, 0x00002fd0, 0x00003001, 0x00003013, - 0x00003051, 0x0000306a, 0x000030ac, 0x000030c8, + 0x00002a9f, 0x00002ab1, 0x00002ace, 0x00002aef, + 0x00002b10, 0x00002b48, 0x00002b5a, 0x00002b71, + 0x00002ba5, 0x00002bd2, 0x00002bf9, 0x00002c1c, + 0x00002c43, 0x00002c56, 0x00002c71, 0x00002c8f, + 0x00002ca8, 0x00002ccc, 0x00002ced, 0x00002d0e, + 0x00002d35, 0x00002d68, 0x00002d95, 0x00002da8, + 0x00002dd3, 0x00002df0, 0x00002e14, 0x00002e2e, + 0x00002e43, 0x00002e73, 0x00002e98, 0x00002ec9, // Entry 180 - 19F - 0x000030fe, 0x00003135, 0x00003168, 0x00003180, - 0x0000319a, 0x000031b7, 0x000031c8, 0x000031c8, - 0x000031c8, 0x000031c8, 0x000031e2, 0x000031fc, - 0x00003206, 0x00003218, 0x00003235, 0x00003254, - 0x0000327a, 0x00003293, 0x000032ba, 0x000032e1, - 0x000032fc, 0x0000330e, 0x0000333b, 0x00003359, - 0x00003371, 0x00003389, 0x000033a4, 0x000033bf, - 0x000033ec, 0x00003407, 0x0000342e, 0x0000345f, + 0x00002ee5, 0x00002eff, 0x00002f14, 0x00002f44, + 0x00002f72, 0x00002f91, 0x00002fbc, 0x00002fed, + 0x00002fff, 0x0000303d, 0x00003056, 0x00003098, + 0x000030b4, 0x000030ea, 0x00003121, 0x00003154, + 0x0000316c, 0x00003186, 0x000031a3, 0x000031b4, + 0x000031b4, 0x000031b4, 0x000031b4, 0x000031b4, + 0x000031ce, 0x000031e8, 0x000031f2, 0x00003204, + 0x00003221, 0x00003240, 0x00003266, 0x0000327f, // Entry 1A0 - 1BF - 0x00003492, 0x000034c3, 0x000034dd, 0x000034fe, - 0x0000351f, 0x00003544, 0x0000356a, 0x00003593, - 0x000035af, 0x000035d0, 0x000035eb, 0x000035f5, - 0x0000360a, 0x0000361f, 0x0000364f, 0x00003665, - 0x0000367d, 0x000036b0, 0x000036c8, 0x000036e6, - 0x000036fa, 0x00003721, 0x0000374b, 0x00003763, - 0x00003793, 0x000037c0, 0x000037e5, 0x0000380a, - 0x0000383f, 0x00003871, 0x0000389d, 0x000038d2, + 0x000032a6, 0x000032cd, 0x000032e8, 0x000032fa, + 0x00003327, 0x00003345, 0x0000335d, 0x00003375, + 0x00003390, 0x000033ab, 0x000033d8, 0x000033f3, + 0x0000341a, 0x0000344b, 0x0000347e, 0x000034af, + 0x000034c9, 0x000034ea, 0x0000350b, 0x00003530, + 0x00003556, 0x0000357f, 0x0000359b, 0x000035bc, + 0x000035d7, 0x000035e1, 0x000035f6, 0x0000360b, + 0x0000363b, 0x00003651, 0x00003669, 0x0000369c, // Entry 1C0 - 1DF - 0x00003902, 0x00003920, 0x0000393f, 0x00003955, - 0x0000396b, 0x00003995, 0x000039dd, 0x000039fe, - 0x00003a37, 0x00003a67, 0x00003a67, 0x00003a67, - 0x00003a80, 0x00003ab8, 0x00003ad9, 0x00003ad9, - 0x00003aee, 0x00003b00, 0x00003b26, 0x00003b3e, - 0x00003b5a, 0x00003b75, 0x00003b91, 0x00003b9d, - 0x00003bc8, 0x00003be4, 0x00003c0e, 0x00003c39, - 0x00003c55, 0x00003c6e, 0x00003c8d, 0x00003cad, + 0x000036b4, 0x000036d2, 0x000036e6, 0x0000370d, + 0x00003737, 0x0000374f, 0x0000377f, 0x000037ac, + 0x000037d1, 0x000037f6, 0x0000382b, 0x0000385d, + 0x00003889, 0x000038be, 0x000038ee, 0x0000390c, + 0x0000390c, 0x0000392b, 0x00003941, 0x00003957, + 0x00003981, 0x000039c9, 0x000039ea, 0x00003a23, + 0x00003a53, 0x00003a53, 0x00003a53, 0x00003a6c, + 0x00003aa4, 0x00003ac5, 0x00003ac5, 0x00003ac5, // Entry 1E0 - 1FF - 0x00003ccc, 0x00003cf2, 0x00003d19, 0x00003d37, - 0x00003d75, 0x00003db3, 0x00003de0, 0x00003e01, - 0x00003e11, 0x00003e51, 0x00003e51, 0x00003e76, - 0x00003e95, 0x00003eb4, 0x00003ec6, 0x00003eee, - 0x00003f01, 0x00003f4a, 0x00003f6b, 0x00003f8d, - 0x00003fae, 0x00003fc1, 0x00003fdd, 0x00004007, - 0x0000402b, 0x0000404a, 0x00004078, 0x000040a5, - 0x000040c6, 0x000040f3, 0x0000411d, 0x00004145, + 0x00003ac5, 0x00003ac5, 0x00003ac5, 0x00003ada, + 0x00003aec, 0x00003b12, 0x00003b2a, 0x00003b46, + 0x00003b61, 0x00003b7d, 0x00003b89, 0x00003bb4, + 0x00003bd0, 0x00003bfa, 0x00003c25, 0x00003c41, + 0x00003c5a, 0x00003c79, 0x00003c99, 0x00003cb8, + 0x00003cde, 0x00003d05, 0x00003d23, 0x00003d61, + 0x00003d9f, 0x00003dcc, 0x00003ded, 0x00003dfd, + 0x00003e3d, 0x00003e3d, 0x00003e62, 0x00003e81, // Entry 200 - 21F - 0x00004161, 0x00004176, 0x0000419d, 0x000041be, - 0x000041e7, 0x000041f9, 0x00004226, 0x00004253, - 0x0000426c, 0x0000428d, 0x000042be, 0x000042eb, - 0x000042fe, 0x00004325, 0x0000434b, 0x00004371, - 0x00004391, 0x000043b8, 0x000043d8, 0x000043ff, - 0x00004426, 0x00004447, 0x00004486, 0x00004492, - 0x000044a2, 0x000044c4, 0x000044e5, 0x0000450e, - 0x00004533, 0x00004561, 0x00004582, 0x00004597, + 0x00003ea0, 0x00003eb2, 0x00003eda, 0x00003eed, + 0x00003f36, 0x00003f57, 0x00003f79, 0x00003f9a, + 0x00003fad, 0x00003fc9, 0x00003ff3, 0x00004017, + 0x00004036, 0x00004064, 0x00004091, 0x000040b2, + 0x000040df, 0x00004109, 0x00004131, 0x0000414d, + 0x00004162, 0x00004189, 0x000041aa, 0x000041d3, + 0x000041e5, 0x00004212, 0x0000423f, 0x00004258, + 0x00004279, 0x000042aa, 0x000042d7, 0x000042ea, // Entry 220 - 23F - 0x000045aa, 0x000045bd, 0x000045dc, 0x000045fe, - 0x00004616, 0x0000462f, 0x00004645, 0x00004664, - 0x00004688, 0x0000469a, 0x000046d1, 0x000046e7, - 0x000046ff, 0x00004721, 0x00004738, 0x0000474d, - 0x00004779, 0x000047ad, 0x000047e1, 0x000047f0, - 0x00004800, 0x00004817, 0x00004837, 0x0000485c, - 0x0000487b, 0x00004893, 0x000048c0, 0x000048ed, - 0x00004914, 0x0000492a, 0x0000494b, 0x00004961, + 0x00004311, 0x00004337, 0x0000435d, 0x0000437d, + 0x000043a4, 0x000043c4, 0x000043eb, 0x00004412, + 0x00004433, 0x00004472, 0x0000447e, 0x0000448e, + 0x000044b0, 0x000044d1, 0x000044fa, 0x0000451f, + 0x0000454d, 0x0000456e, 0x00004583, 0x00004596, + 0x000045a9, 0x000045c8, 0x000045ea, 0x00004602, + 0x0000461b, 0x00004631, 0x00004650, 0x00004674, + 0x00004686, 0x000046bd, 0x000046d3, 0x000046eb, // Entry 240 - 25F - 0x00004980, 0x000049a4, 0x000049bd, 0x000049da, - 0x00004a0b, 0x00004a27, 0x00004a55, 0x00004a76, - 0x00004a94, 0x00004aaf, 0x00004ac8, 0x00004ae0, - 0x00004af6, 0x00004b1d, 0x00004b41, 0x00004b89, - 0x00004bb0, 0x00004bd7, 0x00004bfe, 0x00004c1c, - 0x00004c2f, 0x00004c3f, 0x00004c4f, 0x00004c7a, - 0x00004ca9, 0x00004cd9, 0x00004cf2, 0x00004d05, - 0x00004d1f, 0x00004d34, 0x00004d49, 0x00004d73, + 0x0000470d, 0x00004724, 0x00004739, 0x00004765, + 0x00004799, 0x000047cd, 0x000047dc, 0x000047ec, + 0x00004803, 0x00004823, 0x00004848, 0x00004867, + 0x0000487f, 0x000048ac, 0x000048d9, 0x00004900, + 0x00004916, 0x00004937, 0x0000494d, 0x0000496c, + 0x00004990, 0x000049a9, 0x000049c6, 0x000049f7, + 0x00004a13, 0x00004a41, 0x00004a62, 0x00004a80, + 0x00004a9b, 0x00004ab4, 0x00004acc, 0x00004ae2, // Entry 260 - 27F - 0x00004d8f, 0x00004dc6, 0x00004dd8, 0x00004df7, - 0x00004e09, 0x00004e21, 0x00004e37, 0x00004e5c, - 0x00004e7e, 0x00004ea3, 0x00004ec4, 0x00004ee0, - 0x00004ef0, 0x00004f0f, 0x00004f2b, 0x00004f4d, - 0x00004f80, 0x00004f9b, 0x00004fc2, 0x00004fd6, - 0x00004fe5, 0x00004ff5, 0x00005007, 0x00005020, - 0x0000503b, 0x00005059, 0x00005072, 0x0000508d, - 0x000050b6, 0x000050d8, 0x000050f0, 0x00005108, + 0x00004b09, 0x00004b2d, 0x00004b75, 0x00004b9c, + 0x00004bc3, 0x00004bea, 0x00004c08, 0x00004c1b, + 0x00004c2b, 0x00004c3b, 0x00004c66, 0x00004c95, + 0x00004cc5, 0x00004cde, 0x00004cf1, 0x00004d0b, + 0x00004d20, 0x00004d35, 0x00004d5f, 0x00004d7b, + 0x00004db2, 0x00004dc4, 0x00004de3, 0x00004df5, + 0x00004e0d, 0x00004e23, 0x00004e48, 0x00004e6a, + 0x00004e8f, 0x00004eb0, 0x00004ecc, 0x00004edc, // Entry 280 - 29F - 0x00005123, 0x0000513b, 0x00005160, 0x00005175, - 0x0000518c, 0x00005198, 0x000051ae, 0x000051c9, - 0x000051e5, 0x00005212, 0x00005225, 0x00005234, - 0x00005248, 0x00005266, 0x0000527c, 0x00005297, - 0x000052b9, 0x000052dc, 0x000052ed, 0x000052ff, - 0x00005325, 0x00005346, 0x00005376, 0x00005396, - 0x000053cb, 0x000053e6, 0x00005418, 0x0000542a, - 0x00005449, 0x00005475, 0x00005491, 0x000054b2, + 0x00004efb, 0x00004f17, 0x00004f39, 0x00004f6c, + 0x00004f87, 0x00004fae, 0x00004fc2, 0x00004fd1, + 0x00004fe1, 0x00004ff3, 0x0000500c, 0x00005027, + 0x00005045, 0x0000505e, 0x00005079, 0x000050a2, + 0x000050c4, 0x000050dc, 0x000050f4, 0x0000510f, + 0x00005127, 0x0000514c, 0x00005161, 0x00005178, + 0x00005184, 0x0000519a, 0x000051b5, 0x000051d1, + 0x000051fe, 0x00005211, 0x00005220, 0x00005234, // Entry 2A0 - 2BF - 0x000054eb, 0x00005503, 0x00005521, 0x00005534, - 0x0000554f, 0x00005562, 0x00005580, 0x00005599, - 0x000055ac, 0x000055c5, 0x000055da, 0x00005605, - 0x00005633, 0x00005648, 0x00005679, 0x0000569f, - 0x000056ba, 0x000056df, 0x00005709, 0x00005730, - 0x0000575a, 0x0000578f, 0x000057c7, 0x000057f0, - 0x00005816, 0x0000583d, 0x00005858, 0x0000586e, - 0x00005895, 0x000058b0, 0x000058c5, 0x000058da, + 0x00005252, 0x00005268, 0x00005283, 0x000052a5, + 0x000052c8, 0x000052d9, 0x000052eb, 0x00005311, + 0x00005332, 0x00005362, 0x00005382, 0x000053b7, + 0x000053d2, 0x00005404, 0x00005416, 0x00005435, + 0x00005461, 0x0000547d, 0x0000549e, 0x000054d7, + 0x000054ef, 0x0000550d, 0x00005520, 0x0000553b, + 0x0000554e, 0x0000556c, 0x00005585, 0x00005598, + 0x000055b1, 0x000055c6, 0x000055f1, 0x0000561f, // Entry 2C0 - 2DF - 0x000058ec, 0x00005910, 0x00005925, 0x00005944, - 0x00005972, 0x000059a0, 0x000059c8, 0x000059fc, - 0x00005a25, 0x00005a47, 0x00005a77, 0x00005a9c, - 0x00005aa9, 0x00005ac1, 0x00005adf, 0x00005af8, - 0x00005b18, 0x00005b3d, 0x00005b53, 0x00005b6d, - 0x00005ba0, 0x00005bc6, 0x00005be5, 0x00005c09, - 0x00005c3c, 0x00005c79, 0x00005c9d, 0x00005cc8, - 0x00005cf2, 0x00005d07, 0x00005d1c, 0x00005d3c, + 0x00005634, 0x00005665, 0x0000568b, 0x000056a6, + 0x000056cb, 0x000056f5, 0x0000571c, 0x00005746, + 0x0000577b, 0x000057b3, 0x000057dc, 0x00005802, + 0x00005829, 0x00005844, 0x0000585a, 0x00005881, + 0x0000589c, 0x000058b1, 0x000058c6, 0x000058d8, + 0x000058fc, 0x00005911, 0x00005930, 0x0000595e, + 0x0000598c, 0x000059b4, 0x000059e8, 0x00005a11, + 0x00005a33, 0x00005a63, 0x00005a88, 0x00005a95, // Entry 2E0 - 2FF - 0x00005d53, 0x00005d77, 0x00005da5, 0x00005dd6, - 0x00005e00, 0x00005e29, 0x00005e4f, 0x00005e76, - 0x00005e91, 0x00005eb2, 0x00005ec1, 0x00005ed6, - 0x00005ee9, 0x00005f18, 0x00005f43, 0x00005f5c, - 0x00005f7c, 0x00005fa4, 0x00005fb9, 0x00005fdf, - 0x00005ffe, 0x0000601e, 0x00006036, 0x0000604d, - 0x00006077, 0x000060a1, 0x000060c0, 0x000060e8, - 0x00006119, 0x00006151, 0x00006182, 0x000061a4, + 0x00005aad, 0x00005acb, 0x00005ae4, 0x00005b04, + 0x00005b29, 0x00005b3f, 0x00005b59, 0x00005b8c, + 0x00005bb2, 0x00005bd1, 0x00005bf5, 0x00005c28, + 0x00005c65, 0x00005c89, 0x00005cb4, 0x00005cde, + 0x00005cf3, 0x00005d08, 0x00005d28, 0x00005d3f, + 0x00005d63, 0x00005d91, 0x00005dc2, 0x00005dec, + 0x00005e15, 0x00005e3b, 0x00005e62, 0x00005e7d, + 0x00005e9e, 0x00005ead, 0x00005ec2, 0x00005ed5, // Entry 300 - 31F - 0x000061bb, 0x000061d1, 0x000061e8, 0x0000623d, - 0x00006256, 0x00006275, 0x00006285, 0x000062a3, - 0x000062c2, 0x000062e1, 0x00006303, 0x0000631d, - 0x00006337, 0x00006343, 0x00006354, 0x00006376, - 0x0000638d, 0x000063a2, 0x000063c0, 0x000063e1, - 0x000063fc, 0x00006425, 0x00006444, 0x00006470, - 0x0000648b, 0x000064af, 0x000064d7, 0x00006511, - 0x00006539, 0x00006576, 0x000065b0, 0x000065d4, + 0x00005f04, 0x00005f2f, 0x00005f48, 0x00005f68, + 0x00005f90, 0x00005fa5, 0x00005fcb, 0x00005fea, + 0x0000600a, 0x00006022, 0x00006039, 0x00006063, + 0x0000608d, 0x000060ac, 0x000060d4, 0x00006105, + 0x0000613d, 0x0000616e, 0x00006190, 0x000061a7, + 0x000061bd, 0x000061d4, 0x00006229, 0x00006242, + 0x00006261, 0x00006271, 0x0000628f, 0x000062ae, + 0x000062cd, 0x000062ef, 0x00006309, 0x00006323, // Entry 320 - 33F - 0x000065fe, 0x00006622, 0x0000663a, 0x00006659, - 0x00006692, 0x000066bf, 0x000066e9, 0x00006711, - 0x00006738, 0x0000674c, 0x0000676a, 0x0000677a, - 0x0000679a, 0x000067ab, 0x000067cf, 0x000067ec, - 0x0000680d, 0x00006836, 0x0000685f, 0x00006871, - 0x0000689b, 0x000068ad, 0x000068c0, 0x000068d8, - 0x0000690c, 0x00006942, 0x00006988, 0x000069b2, - 0x000069c4, 0x000069f2, 0x00006a0e, 0x00006a30, + 0x0000632f, 0x00006340, 0x00006362, 0x00006379, + 0x0000638e, 0x000063ac, 0x000063cd, 0x000063e8, + 0x00006411, 0x00006430, 0x0000645c, 0x00006477, + 0x0000649b, 0x000064c3, 0x000064fd, 0x00006525, + 0x00006562, 0x0000659c, 0x000065c0, 0x000065ea, + 0x0000660e, 0x00006626, 0x00006645, 0x0000667e, + 0x000066ab, 0x000066d5, 0x000066fd, 0x00006724, + 0x00006738, 0x00006756, 0x00006766, 0x00006786, // Entry 340 - 35F - 0x00006a4f, 0x00006a77, 0x00006aa5, 0x00006ab7, - 0x00006ada, 0x00006aec, 0x00006b0b, 0x00006b28, - 0x00006b44, 0x00006b68, 0x00006b8b, 0x00006bb5, - 0x00006bdf, 0x00006c06, 0x00006c3d, 0x00006c68, - 0x00006c92, 0x00006cc9, 0x00006ceb, 0x00006d13, - 0x00006d35, 0x00006d7c, 0x00006dc4, 0x00006e0c, - 0x00006e31, 0x00006e44, 0x00006e59, 0x00006e71, - 0x00006ea2, 0x00006ebf, 0x00006ee2, 0x00006f1f, + 0x00006797, 0x000067bb, 0x000067d8, 0x000067f9, + 0x00006822, 0x0000684b, 0x0000685d, 0x00006887, + 0x00006899, 0x000068ac, 0x000068c4, 0x000068f8, + 0x0000692e, 0x00006974, 0x0000699e, 0x000069b0, + 0x000069de, 0x000069fa, 0x00006a1c, 0x00006a3b, + 0x00006a63, 0x00006a91, 0x00006aa3, 0x00006ac6, + 0x00006ad8, 0x00006af7, 0x00006b14, 0x00006b30, + 0x00006b54, 0x00006b77, 0x00006ba1, 0x00006bcb, // Entry 360 - 37F - 0x00006f32, 0x00006f51, 0x00006f69, 0x00006f96, - 0x00006fb7, 0x00006fc9, 0x00006fde, 0x00006ffe, - 0x00007026, 0x00007051, 0x00007063, 0x00007082, - 0x000070ac, 0x000070cf, 0x000070f3, 0x00007115, - 0x00007134, 0x00007167, 0x00007190, 0x000071c3, - 0x000071ef, 0x0000721b, 0x0000723e, 0x00007258, - 0x00007285, 0x000072a6, 0x000072d6, 0x000072ed, - 0x00007315, 0x00007346, 0x0000737e, 0x0000738e, + 0x00006bf2, 0x00006c29, 0x00006c54, 0x00006c7e, + 0x00006cb5, 0x00006cd7, 0x00006cff, 0x00006d21, + 0x00006d68, 0x00006db0, 0x00006df8, 0x00006e1d, + 0x00006e30, 0x00006e45, 0x00006e5d, 0x00006e8e, + 0x00006eab, 0x00006ece, 0x00006f0b, 0x00006f0b, + 0x00006f2a, 0x00006f42, 0x00006f6f, 0x00006f90, + 0x00006fa2, 0x00006fb7, 0x00006fd7, 0x00006fff, + 0x0000702a, 0x0000703c, 0x0000705b, 0x00007085, // Entry 380 - 39F - 0x000073ab, 0x000073dc, 0x000073f6, 0x00007424, - 0x0000744a, 0x0000746a, 0x0000747f, 0x00007494, - 0x000074a6, 0x000074cb, 0x000074eb, 0x0000750a, - 0x0000752f, 0x0000755a, 0x00007585, 0x00007597, - 0x000075c8, 0x000075dd, 0x000075f8, 0x0000762d, - 0x00007657, 0x00007672, 0x00007693, 0x000076ad, - 0x000076cc, 0x00007718, 0x00007759, 0x0000776e, - 0x00007797, 0x000077bf, 0x000077ec, 0x0000781a, + 0x000070a8, 0x000070cc, 0x000070ee, 0x0000710d, + 0x00007140, 0x00007169, 0x0000719c, 0x000071c8, + 0x000071f4, 0x00007217, 0x00007231, 0x0000725e, + 0x0000727f, 0x000072af, 0x000072c6, 0x000072ee, + 0x0000731f, 0x00007357, 0x00007367, 0x00007384, + 0x000073b5, 0x000073cf, 0x000073fd, 0x00007423, + 0x00007443, 0x00007458, 0x0000746d, 0x0000747f, + 0x000074a4, 0x000074c4, 0x000074e3, 0x00007508, // Entry 3A0 - 3BF - 0x00007842, 0x00007863, 0x0000787f, 0x000078a4, - 0x000078cf, 0x000078f1, 0x0000790d, 0x0000793e, - 0x0000797b, 0x000079a3, 0x000079d3, 0x000079f9, - 0x00007a48, 0x00007a6e, 0x00007a9d, 0x00007ace, - 0x00007aec, 0x00007b1c, 0x00007b46, 0x00007b6c, - 0x00007b85, 0x00007bc6, 0x00007c04, 0x00007c48, - 0x00007c73, 0x00007c94, 0x00007ca4, 0x00007cc1, - 0x00007cdd, 0x00007cf7, 0x00007d04, 0x00007d4f, + 0x00007533, 0x0000755e, 0x00007570, 0x000075a1, + 0x000075b6, 0x000075d1, 0x00007606, 0x00007630, + 0x0000764b, 0x0000766c, 0x00007686, 0x000076a5, + 0x000076f1, 0x00007732, 0x00007747, 0x00007770, + 0x00007798, 0x000077c5, 0x000077f3, 0x0000781b, + 0x0000783c, 0x00007858, 0x0000787d, 0x000078a8, + 0x000078ca, 0x000078e6, 0x00007917, 0x00007954, + 0x0000797c, 0x000079ac, 0x000079d2, 0x00007a21, // Entry 3C0 - 3DF - 0x00007d82, 0x00007da4, 0x00007dbe, 0x00007dce, - 0x00007dee, 0x00007e15, 0x00007e39, 0x00007e58, - 0x00007e6d, 0x00007e92, 0x00007eb4, 0x00007ecf, - 0x00007ee7, 0x00007eff, 0x00007f19, 0x00007f31, - 0x00007f61, 0x00007f76, 0x00007f8b, 0x00007faa, - 0x00007fc2, 0x00007fdb, 0x00007ff3, 0x0000800c, - 0x0000803a, 0x0000805e, 0x0000807e, 0x0000808e, - 0x000080b8, 0x000080cd, 0x000080e1, 0x000080fb, + 0x00007a47, 0x00007a76, 0x00007aa7, 0x00007ac5, + 0x00007af5, 0x00007b1f, 0x00007b45, 0x00007b5e, + 0x00007b9f, 0x00007bdd, 0x00007c21, 0x00007c4c, + 0x00007c6d, 0x00007c7d, 0x00007c9a, 0x00007cb6, + 0x00007cd0, 0x00007cdd, 0x00007d28, 0x00007d5b, + 0x00007d7d, 0x00007d97, 0x00007da7, 0x00007da7, + 0x00007da7, 0x00007da7, 0x00007da7, 0x00007da7, + 0x00007da7, 0x00007da7, 0x00007dc7, 0x00007dee, // Entry 3E0 - 3FF - 0x00008115, 0x0000813c, 0x00008154, 0x0000816b, - 0x000081a0, 0x000081d0, 0x000081e0, 0x0000821d, - 0x0000823b, 0x00008278, 0x0000829e, 0x000082bf, - 0x000082e4, 0x00008300, 0x0000831c, 0x0000834a, - 0x00008371, 0x0000839b, 0x000083b0, 0x000083f3, - 0x00008405, 0x0000842f, 0x0000844c, 0x00008468, - 0x00008477, 0x00008494, 0x000084ac, 0x000084b9, - 0x000084d7, 0x000084f8, 0x000084fb, 0x00008513, + 0x00007e12, 0x00007e31, 0x00007e46, 0x00007e6b, + 0x00007e8d, 0x00007ea8, 0x00007ec0, 0x00007ed8, + 0x00007ef2, 0x00007f0a, 0x00007f3a, 0x00007f4f, + 0x00007f64, 0x00007f83, 0x00007f9b, 0x00007fb4, + 0x00007fcc, 0x00007fe5, 0x00008013, 0x00008037, + 0x00008057, 0x00008067, 0x00008091, 0x000080a6, + 0x000080ba, 0x000080d4, 0x000080ee, 0x00008115, + 0x0000812d, 0x00008144, 0x00008179, 0x000081a9, // Entry 400 - 41F - 0x0000852f, 0x0000855d, 0x0000857f, 0x00008591, - 0x000085c2, 0x000085d2, 0x000085ee, 0x00008634, - 0x00008654, 0x00008675, 0x000086a8, 0x000086f7, - 0x0000870f, 0x00008731, 0x00008747, 0x0000875f, - 0x00008777, 0x000087ae, 0x000087c9, 0x000087e4, - 0x00008802, 0x00008823, 0x00008847, 0x0000886b, - 0x00008884, 0x000088bb, 0x000088d3, 0x000088f0, - 0x00008915, 0x0000892a, 0x00008942, 0x00008960, + 0x000081b9, 0x000081f6, 0x00008214, 0x00008251, + 0x00008277, 0x00008298, 0x000082bd, 0x000082d9, + 0x000082f5, 0x00008323, 0x0000834a, 0x00008374, + 0x00008389, 0x000083cc, 0x000083de, 0x00008408, + 0x00008425, 0x00008441, 0x00008450, 0x0000846d, + 0x00008485, 0x00008492, 0x000084b0, 0x000084d1, + 0x000084d4, 0x000084ec, 0x00008508, 0x00008536, + 0x00008558, 0x0000856a, 0x0000859b, 0x000085ab, // Entry 420 - 43F - 0x00008984, 0x000089b7, 0x000089c9, 0x000089ed, - 0x00008a11, 0x00008a38, 0x00008a57, 0x00008a69, - 0x00008a81, 0x00008a99, 0x00008ab1, 0x00008ac0, - 0x00008af2, 0x00008b20, 0x00008b38, 0x00008b50, - 0x00008b71, 0x00008b91, 0x00008ba6, 0x00008bcf, - 0x00008be8, 0x00008c0f, 0x00008c2e, 0x00008c62, - 0x00008c7a, 0x00008c92, 0x00008ca4, 0x00008cbc, - 0x00008ce7, 0x00008d00, 0x00008d0d, 0x00008d2e, + 0x000085c7, 0x000085c7, 0x000085d4, 0x000085d4, + 0x000085d4, 0x0000861a, 0x0000863a, 0x0000865b, + 0x0000868e, 0x000086dd, 0x000086f5, 0x00008717, + 0x0000872d, 0x00008745, 0x0000875d, 0x00008794, + 0x000087af, 0x000087ca, 0x000087e8, 0x00008809, + 0x0000882d, 0x00008851, 0x0000886a, 0x000088a1, + 0x000088b9, 0x000088d6, 0x000088fb, 0x00008910, + 0x00008928, 0x00008946, 0x0000896a, 0x0000899d, // Entry 440 - 45F - 0x00008d46, 0x00008d60, 0x00008d83, 0x00008da2, - 0x00008db9, 0x00008ddc, 0x00008dfb, 0x00008e11, - 0x00008e33, 0x00008e49, 0x00008e64, 0x00008e7c, - 0x00008e94, 0x00008ea8, 0x00008ecd, 0x00008ef1, - 0x00008f1c, 0x00008f3a, 0x00008f4a, 0x00008f69, - 0x00008f94, 0x00008faa, 0x00008fbd, 0x0000900e, - 0x00009039, 0x00009065, 0x0000907a, 0x00009090, - 0x000090a3, 0x000090bf, 0x000090ec, 0x0000910d, + 0x000089af, 0x000089d3, 0x000089f7, 0x00008a1e, + 0x00008a3d, 0x00008a4f, 0x00008a67, 0x00008a7f, + 0x00008a97, 0x00008aa6, 0x00008ad8, 0x00008b06, + 0x00008b1e, 0x00008b36, 0x00008b57, 0x00008b77, + 0x00008b8c, 0x00008bb5, 0x00008bce, 0x00008bf5, + 0x00008c14, 0x00008c48, 0x00008c60, 0x00008c78, + 0x00008c8a, 0x00008ca2, 0x00008ccd, 0x00008ce6, + 0x00008d07, 0x00008d1f, 0x00008d39, 0x00008d5c, // Entry 460 - 47F - 0x00009138, 0x0000915f, 0x0000918b, 0x000091ac, - 0x000091e2, 0x000091ef, 0x0000921d, 0x0000922f, - 0x0000924b, 0x00009272, 0x00009284, 0x00009299, - 0x000092be, 0x000092df, 0x000092fd, 0x0000931c, - 0x0000933e, 0x00009380, 0x000093ad, 0x000093c2, - 0x000093f3, 0x00009436, 0x00009459, 0x0000946c, - 0x00009485, 0x000094a1, 0x000094bf, 0x000094d2, - 0x000094e5, 0x0000950a, 0x00009526, 0x00009544, + 0x00008d7b, 0x00008d92, 0x00008db5, 0x00008dd4, + 0x00008dea, 0x00008e0c, 0x00008e22, 0x00008e3d, + 0x00008e55, 0x00008e55, 0x00008e7a, 0x00008e9e, + 0x00008ec9, 0x00008ee7, 0x00008ef7, 0x00008f16, + 0x00008f41, 0x00008f57, 0x00008f6a, 0x00008fbb, + 0x00008fe6, 0x00009012, 0x00009027, 0x0000903d, + 0x00009050, 0x0000906c, 0x00009099, 0x000090ba, + 0x000090e5, 0x0000910c, 0x00009138, 0x00009159, // Entry 480 - 49F - 0x0000955f, 0x00009577, 0x0000958f, 0x000095a7, - 0x000095c5, 0x000095de, 0x000095f4, 0x0000960a, - 0x00009620, 0x00009639, 0x0000965b, 0x00009678, - 0x00009678, 0x00009678, 0x00009678, 0x00009678, - 0x00009678, 0x00009678, 0x000096a4, 0x000096c5, - 0x000096f0, 0x00009714, 0x00009742, 0x0000975b, - 0x0000975b, 0x0000975b, 0x00009772, 0x000097b2, - 0x000097db, 0x00009815, 0x00009837, 0x00009846, + 0x0000918f, 0x0000919c, 0x000091ca, 0x000091dc, + 0x000091f8, 0x0000921f, 0x00009231, 0x00009246, + 0x0000926b, 0x0000928c, 0x000092aa, 0x000092c9, + 0x000092eb, 0x0000932d, 0x0000935a, 0x0000936f, + 0x000093a0, 0x000093e3, 0x00009406, 0x00009419, + 0x00009432, 0x0000944e, 0x0000946c, 0x0000947f, + 0x00009492, 0x000094b7, 0x000094d3, 0x000094f1, + 0x0000950c, 0x00009524, 0x0000953c, 0x00009554, // Entry 4A0 - 4BF - 0x0000985d, 0x00009881, 0x000098ac, 0x000098c7, - 0x000098e6, 0x00009904, 0x0000991d, 0x0000992b, - 0x00009944, 0x00009972, 0x000099ae, 0x000099cd, - 0x000099f5, 0x00009a10, 0x00009a25, 0x00009a54, - 0x00009a7b, 0x00009a99, 0x00009ada, 0x00009afb, - 0x00009b25, 0x00009b40, 0x00009b6e, 0x00009ba0, - 0x00009bbf, 0x00009bef, 0x00009c23, 0x00009c55, - 0x00009c79, 0x00009cae, 0x00009cfa, 0x00009d2a, + 0x00009572, 0x0000958b, 0x000095a1, 0x000095b7, + 0x000095cd, 0x000095e6, 0x00009608, 0x00009625, + 0x00009625, 0x00009625, 0x00009625, 0x00009625, + 0x00009625, 0x00009625, 0x00009651, 0x00009672, + 0x0000969d, 0x000096c1, 0x000096ef, 0x00009708, + 0x00009708, 0x00009708, 0x0000971f, 0x0000975f, + 0x00009788, 0x000097c2, 0x000097e4, 0x000097f3, + 0x0000980a, 0x0000982e, 0x00009859, 0x00009874, // Entry 4C0 - 4DF - 0x00009d55, 0x00009d94, 0x00009dc5, 0x00009dec, - 0x00009e07, 0x00009e1a, 0x00009e4a, 0x00009e81, - 0x00009e9c, 0x00009ecd, 0x00009f2f, 0x00009f5a, - 0x00009f94, 0x00009fc3, 0x0000a012, 0x0000a03f, - 0x0000a079, 0x0000a0af, 0x0000a0e2, 0x0000a125, - 0x0000a161, 0x0000a19a, 0x0000a1c2, 0x0000a1fc, - 0x0000a21c, 0x0000a243, 0x0000a271, 0x0000a290, - 0x0000a2aa, 0x0000a2bc, 0x0000a2da, 0x0000a306, + 0x00009893, 0x000098b1, 0x000098ca, 0x000098d8, + 0x000098f0, 0x00009909, 0x00009937, 0x00009973, + 0x00009992, 0x000099ba, 0x000099d5, 0x000099ea, + 0x00009a19, 0x00009a40, 0x00009a5e, 0x00009a9f, + 0x00009ac0, 0x00009aea, 0x00009b05, 0x00009b33, + 0x00009b65, 0x00009b84, 0x00009bb4, 0x00009be8, + 0x00009c1a, 0x00009c3e, 0x00009c73, 0x00009cbf, + 0x00009cef, 0x00009d1a, 0x00009d59, 0x00009d8a, // Entry 4E0 - 4FF - 0x0000a31b, 0x0000a34b, 0x0000a389, 0x0000a3b0, - 0x0000a3d6, 0x0000a3fc, 0x0000a41d, 0x0000a443, - 0x0000a479, 0x0000a4a3, 0x0000a4d2, 0x0000a4f2, - 0x0000a51c, 0x0000a544, 0x0000a56f, 0x0000a593, - 0x0000a5c3, 0x0000a5e7, 0x0000a61b, 0x0000a646, - 0x0000a679, 0x0000a6a3, 0x0000a6d0, 0x0000a709, - 0x0000a746, 0x0000a778, 0x0000a79c, 0x0000a7c6, - 0x0000a7f7, 0x0000a827, 0x0000a867, 0x0000a894, + 0x00009db1, 0x00009dcc, 0x00009ddf, 0x00009e0f, + 0x00009e46, 0x00009e61, 0x00009e92, 0x00009ef4, + 0x00009f1f, 0x00009f59, 0x00009f88, 0x00009fd7, + 0x0000a004, 0x0000a03e, 0x0000a074, 0x0000a0a7, + 0x0000a0ea, 0x0000a126, 0x0000a15f, 0x0000a187, + 0x0000a1c1, 0x0000a1e1, 0x0000a208, 0x0000a236, + 0x0000a255, 0x0000a26f, 0x0000a281, 0x0000a29f, + 0x0000a2cb, 0x0000a2e0, 0x0000a310, 0x0000a34e, // Entry 500 - 51F - 0x0000a8c2, 0x0000a8f0, 0x0000a924, 0x0000a961, - 0x0000a98f, 0x0000a9a1, 0x0000a9d8, 0x0000aa02, - 0x0000aa23, 0x0000aa53, 0x0000aa63, 0x0000aa7b, - 0x0000aaae, 0x0000aac2, 0x0000aae4, 0x0000ab14, - 0x0000ab3c, 0x0000ab5c, 0x0000ab80, 0x0000abb3, - 0x0000abff, 0x0000ac33, 0x0000ac50, 0x0000ac85, - 0x0000acae, 0x0000acd1, 0x0000acd1, 0x0000acf4, - 0x0000ad1c, 0x0000ad34, 0x0000ad56, 0x0000ad66, + 0x0000a375, 0x0000a39b, 0x0000a3c1, 0x0000a3e2, + 0x0000a408, 0x0000a43e, 0x0000a468, 0x0000a497, + 0x0000a4b7, 0x0000a4e1, 0x0000a509, 0x0000a534, + 0x0000a558, 0x0000a588, 0x0000a5ac, 0x0000a5e0, + 0x0000a60b, 0x0000a63e, 0x0000a668, 0x0000a695, + 0x0000a6ce, 0x0000a70b, 0x0000a73d, 0x0000a761, + 0x0000a78b, 0x0000a7bc, 0x0000a7ec, 0x0000a82c, + 0x0000a859, 0x0000a887, 0x0000a8b5, 0x0000a8e9, // Entry 520 - 53F - 0x0000ad95, 0x0000adc4, 0x0000ade5, 0x0000ae09, - 0x0000ae33, 0x0000ae5b, 0x0000ae88, 0x0000ae9b, - 0x0000aeb8, 0x0000aee0, 0x0000af16, 0x0000af38, - 0x0000af66, 0x0000af82, 0x0000afa2, 0x0000afba, - 0x0000afd5, 0x0000afe7, 0x0000b00f, 0x0000b03e, - 0x0000b05c, 0x0000b093, 0x0000b0ba, 0x0000b0de, - 0x0000b105, 0x0000b11e, 0x0000b12b, 0x0000b141, - 0x0000b15a, 0x0000b16a, 0x0000b17a, 0x0000b18a, + 0x0000a926, 0x0000a954, 0x0000a966, 0x0000a99d, + 0x0000a9c7, 0x0000a9e8, 0x0000aa18, 0x0000aa18, + 0x0000aa28, 0x0000aa40, 0x0000aa73, 0x0000aa87, + 0x0000aaa9, 0x0000aad9, 0x0000ab01, 0x0000ab21, + 0x0000ab45, 0x0000ab78, 0x0000abc4, 0x0000abf8, + 0x0000ac15, 0x0000ac4a, 0x0000ac73, 0x0000ac96, + 0x0000ac96, 0x0000acb9, 0x0000ace1, 0x0000acf9, + 0x0000ad1b, 0x0000ad2b, 0x0000ad5a, 0x0000ad89, // Entry 540 - 55F - 0x0000b198, 0x0000b1a8, 0x0000b1b8, 0x0000b1c8, - 0x0000b1de, 0x0000b1ee, 0x0000b201, 0x0000b20b, - 0x0000b21b, 0x0000b228, 0x0000b235, 0x0000b242, - 0x0000b24f, 0x0000b25c, 0x0000b269, 0x0000b27c, - 0x0000b289, 0x0000b299, 0x0000b2a6, 0x0000b2b0, - 0x0000b2c0, 0x0000b2c7, 0x0000b2d4, 0x0000b2e1, - 0x0000b2ee, 0x0000b2f5, 0x0000b2ff, 0x0000b30d, - 0x0000b320, 0x0000b32d, 0x0000b33a, 0x0000b353, + 0x0000ad89, 0x0000adaa, 0x0000adce, 0x0000adf8, + 0x0000ae20, 0x0000ae4d, 0x0000ae60, 0x0000ae7d, + 0x0000aea5, 0x0000aedb, 0x0000aefd, 0x0000af2b, + 0x0000af47, 0x0000af67, 0x0000af7f, 0x0000af9a, + 0x0000afac, 0x0000afd4, 0x0000b003, 0x0000b021, + 0x0000b058, 0x0000b07f, 0x0000b0a3, 0x0000b0ca, + 0x0000b0e3, 0x0000b0f0, 0x0000b106, 0x0000b11f, + 0x0000b12f, 0x0000b13f, 0x0000b14f, 0x0000b15d, // Entry 560 - 57F - 0x0000b363, 0x0000b376, 0x0000b389, 0x0000b39b, - 0x0000b3f2, 0x0000b404, 0x0000b418, 0x0000b42e, - 0x0000b438, 0x0000b447, 0x0000b459, 0x0000b475, - 0x0000b48e, 0x0000b4af, 0x0000b4c3, 0x0000b4d3, - 0x0000b4eb, 0x0000b501, 0x0000b518, 0x0000b525, - 0x0000b533, 0x0000b547, 0x0000b55b, 0x0000b568, - 0x0000b578, 0x0000b591, 0x0000b5ad, 0x0000b5c8, - 0x0000b5dd, 0x0000b602, 0x0000b61a, 0x0000b62f, + 0x0000b16d, 0x0000b17d, 0x0000b18d, 0x0000b1a3, + 0x0000b1b3, 0x0000b1c6, 0x0000b1d0, 0x0000b1e0, + 0x0000b1ed, 0x0000b1fa, 0x0000b207, 0x0000b214, + 0x0000b221, 0x0000b22e, 0x0000b241, 0x0000b24e, + 0x0000b25e, 0x0000b26b, 0x0000b275, 0x0000b285, + 0x0000b28c, 0x0000b299, 0x0000b2a6, 0x0000b2b3, + 0x0000b2ba, 0x0000b2c4, 0x0000b2d2, 0x0000b2e5, + 0x0000b2f2, 0x0000b2ff, 0x0000b318, 0x0000b328, // Entry 580 - 59F - 0x0000b642, 0x0000b642, 0x0000b65a, -} // Size: 5668 bytes + 0x0000b33b, 0x0000b34e, 0x0000b360, 0x0000b3b7, + 0x0000b3c9, 0x0000b3dd, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + // Entry 5A0 - 5BF + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + // Entry 5C0 - 5DF + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + // Entry 5E0 - 5FF + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, 0x0000b3f3, + 0x0000b3fd, 0x0000b40c, 0x0000b41e, 0x0000b43a, + 0x0000b453, 0x0000b474, 0x0000b488, 0x0000b498, + 0x0000b4b0, 0x0000b4c6, 0x0000b4dd, 0x0000b4ea, + 0x0000b4f8, 0x0000b50c, 0x0000b520, 0x0000b52d, + 0x0000b53d, 0x0000b556, 0x0000b572, 0x0000b58d, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + // Entry 600 - 61F + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + // Entry 620 - 63F + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, 0x0000b5a2, + 0x0000b5a2, 0x0000b5c7, 0x0000b5c7, 0x0000b5c7, + 0x0000b5c7, 0x0000b5c7, 0x0000b5c7, 0x0000b5c7, + 0x0000b5c7, 0x0000b5c7, 0x0000b5c7, 0x0000b5c7, + // Entry 640 - 65F + 0x0000b5c7, 0x0000b5df, 0x0000b5f4, 0x0000b607, + 0x0000b607, 0x0000b61f, 0x0000b61f, 0x0000b61f, + 0x0000b61f, 0x0000b61f, +} // Size: 6464 bytes -const zh_CNData string = "" + // Size: 46682 bytes +const zh_CNData string = "" + // Size: 46623 bytes "\x02无效的shared_mode %s\x02mx_priority应在[1,50]范围\x02无效的MX记录:无效的域名%s\x02无效的" + "A记录:无效的IPv4地址%s\x02无效的AAAA记录:无效的IPv6地址%s\x02无效的CNAME记录:无效的域名%s\x02无效的路由C" + "IDR %s\x02服务器内部错误:%s\x02服务器内部错误\x02无效的handler:%s\x02未找到handler\x02找不到请求的" + @@ -3031,60 +3427,60 @@ const zh_CNData string = "" + // Size: 46682 bytes "\x02未指定项目\x02Informer后端未初始化\x02无效的格式\x02操作不允许\x02无效的输入格式\x02缺少输入参数:%q" + "\x02%q:常规错误:%s\x02类型错误:期望类型%s,%q的实际类型为%s\x02%q:错误的枚举值:期望%s,实际%s\x02%q:长度" + "%d太短,最小为%d\x02%q:长度%d太长,最长为%d\x02%q:%d不在范围内,应为[%d,%d]\x02%q:无效的值%s\x02%q" + - ":无效的值:%v\x04\x00\x01 \x12\x02%q:无效的值\x02无法找到%q对应的模型管理器\x02无法通过名称或Id找到%" + - "q所指定的资源\x02无法通过名称或Id找到%q所指定的资源:%s\x02无效的证书算法:%s,要求为%s\x02找不到provider\x02" + - "未实现GetProvider\x02未找到\x02Id重复\x02无效的状态\x02超时\x02未实现\x02不支持\x02无效的Provi" + - "der\x02没有查询余额的权限\x02不支持多网卡\x02%s磁盘大小必须在%dGB到%dGB范围内\x02%s要求EIP带宽必须小于100M" + - "bps\x02与路由表关联的网络%s没有因特网网关\x02无法找到镜像%s的subformats中找到vhd,请尝试为glance target" + - "_image_formats选项添加'vhd'\x02Azure Mv2系列SKU仅支持UEFI镜像\x02Azure UEFI镜像%s不支持此" + - "SKU\x02不支持变更Azure实例名称\x02不能变更裸金属的配置\x02不能变更裸金属的磁盘大小\x02无效的RAID配置:%v\x02宿" + - "主机%s不是一个裸金属服务器\x02裸金属服务器%s没有准备好\x02裸金属服务器%s已被占用\x02无法为裸金属服务器保存镜像\x02未实" + - "现ValidateCreateEip\x02hypervisor %s不允许此操作\x02不支持此操作,请使用kubectl\x02容器不支" + - "持%s\x02%s不支持创建EIP\x02无法为有主机快照的虚拟机调整磁盘大小\x02ESXi虚机迁移需要指定prefer_host\x02" + - "无法为有主机快照的虚拟机重装系统\x02未知的Google存储类型\x02系统盘不支持%s磁盘\x02%s磁盘数量超过8个\x02%s和%s" + - "特性创建实例时互不兼容\x02找不到宿主机%s\x02主备机不可迁移\x02无法为状态为%s的虚机执行迁移操作,尝试救援模式或server-" + - "live-migrate\x02救援模式要求所有磁盘都使用共享存储\x02使用透传设备时不支持迁移\x02使用cdrom时无法在线迁移\x02使" + - "用透传设备时无法在线迁移\x02QEMU版本太低,无法在线迁移\x02%s不支持指定CDROM参数\x02%s不支持创建EIP,仅支持绑定已" + - "有EIP\x02%s不支持创建虚机时同时创建EIP\x02数据盘不支持存储类型%s\x02%s盘的大小必须在10GB到16000GB范围内" + - "\x02%s盘的大小必须在50GB到16000GB范围内\x02%s盘的大小必须在100GB到16000GB范围内\x02%s存储不能作为数据盘" + - "\x02数据盘大小必须是10GB的整数倍\x02找不到系统盘:%v\x02找不到磁盘%s(%s)所属的存储\x02系统盘存储在本地,不支持变更配" + - "置\x02不支持创建本地盘\x02请指定新的磁盘类型\x02磁盘存储在本地,不支持解绑\x02宿主机%s不在线\x02GetGuestCou" + - "nt调用出错:%s\x02宿主机已被占用\x02阿里支重置磁盘要求虚机状态为运行中或已关机\x02AWS不支持重置磁盘,您可以使用快照来创建新盘" + - "\x02不支持变更Azure磁盘名称\x02Azure不支持重置磁盘,您可以使用快照创建新盘\x02未实现\x02ValidateResetDi" + - "sk未实现\x02ValidateAttachStorage未实现\x02RequestAttachStorage未实现\x02RequestD" + - "etachStorage未实现\x02磁盘必须已解绑\x02%s重置磁盘时要求虚机状态必须是运行中或已关机\x02不支持挂载%s存储到%s宿主机" + - "\x02挂载rbd存储要求宿主机在线\x02查询主机磁盘出错:%s\x02宿主机%s挂载点%s已有其它存储\x02挂载NFS存储要求宿主机在线" + - "\x02%s不是一个挂解点:%s\x02磁盘挂载到多个虚机\x02磁盘所在虚机必须是已关机状态\x02磁盘未挂载到虚机\x02OpenStack" + - "不支持重置磁盘,您可以从快照创建新盘\x02Qcloud重置磁盘要求虚机状态必须是运行中或已关机\x02Ucloud重置磁盘要求磁盘处于未挂" + - "载状态\x02Ucloud仅支持数据盘重置操作\x02ZStack重置磁盘要求虚机处于关机状态\x02解析远端IP地址出错:%s\x02子网" + - "未找到\x02裸金属服务器Agent未找到\x02裸金属服务器package未准备好\x02检查agent唯一性失败:%s\x02manag" + - "er_url冲突:%s\x02manager_url重复:%s\x02未指定duration/expire_time\x02新的过期时间先于当前" + - "值\x02无效的duration %s: %s\x02找不到名称\x02无效的桶名%s:%s\x02无效的桶名%s:%s\x02没有外部桶" + - "\x02获取对象出错:%s\x02未指定key\x02生成临时URL出错:%s\x02空的目录名\x02无效的key %s:%s\x02GetI" + - "Object调用出错:%s\x02对象数量超出限制\x02bucket.GetQuotaKeys调用出错:%s\x02创建目录失败:%s\x02" + - "keys列表为空\x02对象key不应以斜线结尾\x02无效的对象key:%s\x02找不到Content-Length\x02无效的Conte" + - "nt-Length %s\x02Content-Length为负值%d\x02GetIObject调用出错:%s\x02对象大小超出限制\x02" + - "bucket.GetQuotaKeys调用出错:%s\x02更新对象出错:%s\x02setAcl调用出错:%s\x02syncWithClou" + - "dBucket调用出错:%s\x02桶当前有%d个活跃任务在执行,无法执行同步状态操作\x02桶不为空\x02找不到对象%s\x02iBucke" + - "t.GetIObjects调用出错:%s\x02unmarshal limit参数出错:%s\x02SetLimit调用出错:%s\x02更新出" + - "错:%s\x02找不到manager\x02iBucket.GetIObject调用出错:%s\x02ValidateDeleteCondi" + - "tion出错:%s\x02镜像已被缓存到磁盘\x02镜像引用会话还未过期\x02找不到存储缓存%s\x02不允许查询系统能力\x02账号为启用状" + - "态\x02账号不空闲\x02%s provider: %v\x02无法启用正在删除中的账号\x02无效的代理设备%s\x02不支持provi" + - "der为%s的云账号\x02不支持%s\x02项目%s(%s)不属于域%s(%s)\x02不支持品牌%s,仅支持%s\x02唯一性检查失败" + - "\x02账户已被注册\x02找不到provider %s\x02无效的账号信息:%s\x02检查重复account_id出错\x02账号%s已被" + - "注册\x02账号已禁用\x02账号自动同步已启用\x02无效的输入:%s\x02无法找到provider工厂:%v\x02unmarshal" + - "输入参数出错:%v\x02检查唯一性出错:%s\x02账号%s出现冲突\x02account_id不一致,之前为%q,现在为%q\x02找不" + - "到项目%s\x02状态为%s时无法启用自动同步\x02provider在域间共享\x02不支持%s\x02%s不支持创建订阅\x02不允许创" + - "建\x02provider当前是启用状态\x02provider当前不是空闲状态\x02不支持直接创建cloudprovider,请先创建云" + - "账号\x02找不到region %s\x02找不到zone %s\x02cloudprovider是已禁用状态\x02云账号是已禁用状态" + - "\x02不允许跨域变更项目属性\x02不允许变更为私有云另外一个域\x02获取provider驱动失败:%s\x02不支持存储类型\x02Get" + - "ZoneCount调用失败\x02GetVpcCount调用失败\x02cloudregion不为空\x02不允许删除默认cloudregion" + - "\x02找不到VPC %s\x02不允许更新外部资源\x02查找数据库实例出错:%s\x02数据库实例%s(%s)当前状态为%s,要求状态为%s" + - "\x02找不到数据库实例%s(%s)所属的region\x02在数据库实例%s(%s)中查找%s库失败:%v\x02查找数据库%s失败,在实例%" + - "s(%s)中查找出错:%v\x02账号%s(%s)已有权限%s访问数据库%s(%s)\x02账号状态不是%s,当前状态为%s\x02实例状态不是" + - "%s,当前状态为%s\x02数据库状态不是%s,当前状态为%s\x02账号%s(%s)没有数据库%s(%s)的权限\x02数据库实例没有有效的c" + - "loudprovider\x02数据库实例的备份当前有%d个活跃任务执行中,无法执行同步状态操作\x02找不到数据库实例%s(%s)的账号%s:" + - "%v\x02未实现\x02无效地址:%s\x02%s不在子网%s(%s)的范围中\x02云订阅 %s(%s) 无法使用\x02无效的时间长度%s" + + ":无效的值:%v\x04\x00\x01 \x12\x02%q:无效的值\x02无法找到%q对应的模型管理器\x02无法通过名称或ID找到%" + + "q(%q)\x02无法通过名称或ID找到%q(%q):%s\x02无效的证书算法:%s,要求为%s\x02找不到provider\x02未实现G" + + "etProvider\x02未找到\x02Id重复\x02无效的状态\x02超时\x02未实现\x02不支持\x02无效的Provider" + + "\x02没有查询余额的权限\x02不支持多网卡\x02%s磁盘大小必须在%dGB到%dGB范围内\x02%s要求EIP带宽必须小于100Mbps" + + "\x02与路由表关联的网络%s没有因特网网关\x02无法找到镜像%s的subformats中找到vhd,请尝试为glance target_im" + + "age_formats选项添加'vhd'\x02Azure Mv2系列SKU仅支持UEFI镜像\x02Azure UEFI镜像%s不支持此SKU" + + "\x02不支持变更Azure实例名称\x02不能变更裸金属的配置\x02不能变更裸金属的磁盘大小\x02无效的RAID配置:%v\x02宿主机%" + + "s不是一个裸金属服务器\x02裸金属服务器%s没有准备好\x02裸金属服务器%s已被占用\x02无法为裸金属服务器保存镜像\x02未实现Vali" + + "dateCreateEip\x02hypervisor %s不允许此操作\x02不支持此操作,请使用kubectl\x02容器不支持%s\x02" + + "%s不支持创建EIP\x02无法为有主机快照的虚拟机调整磁盘大小\x02ESXi虚机迁移需要指定prefer_host\x02无法为有主机快照的" + + "虚拟机重装系统\x02未知的Google存储类型\x02系统盘不支持%s磁盘\x02%s磁盘数量超过8个\x02%s和%s特性创建实例时互不" + + "兼容\x02找不到宿主机%s\x02主备机不可迁移\x02无法为状态为%s的虚机执行迁移操作,尝试救援模式或server-live-migr" + + "ate\x02救援模式要求所有磁盘都使用共享存储\x02使用透传设备时不支持迁移\x02使用cdrom时无法在线迁移\x02使用透传设备时无法在" + + "线迁移\x02QEMU版本太低,无法在线迁移\x02%s不支持指定CDROM参数\x02%s不支持创建EIP,仅支持绑定已有EIP\x02%" + + "s不支持创建虚机时同时创建EIP\x02数据盘不支持存储类型%s\x02%s盘的大小必须在10GB到16000GB范围内\x02%s盘的大小必须" + + "在50GB到16000GB范围内\x02%s盘的大小必须在100GB到16000GB范围内\x02%s存储不能作为数据盘\x02数据盘大小必" + + "须是10GB的整数倍\x02找不到系统盘:%v\x02找不到磁盘%s(%s)所属的存储\x02系统盘存储在本地,不支持变更配置\x02不支持" + + "创建本地盘\x02请指定新的磁盘类型\x02磁盘存储在本地,不支持解绑\x02宿主机%s不在线\x02GetGuestCount调用出错:%" + + "s\x02宿主机已被占用\x02阿里支重置磁盘要求虚机状态为运行中或已关机\x02AWS不支持重置磁盘,您可以使用快照来创建新盘\x02不支持变" + + "更Azure磁盘名称\x02Azure不支持重置磁盘,您可以使用快照创建新盘\x02未实现\x02ValidateResetDisk未实现" + + "\x02ValidateAttachStorage未实现\x02RequestAttachStorage未实现\x02RequestDetach" + + "Storage未实现\x02磁盘必须已解绑\x02%s重置磁盘时要求虚机状态必须是运行中或已关机\x02不支持挂载%s存储到%s宿主机\x02挂" + + "载rbd存储要求宿主机在线\x02查询主机磁盘出错:%s\x02宿主机%s挂载点%s已有其它存储\x02挂载NFS存储要求宿主机在线\x02" + + "%s不是一个挂解点:%s\x02磁盘挂载到多个虚机\x02磁盘所在虚机必须是已关机状态\x02磁盘未挂载到虚机\x02OpenStack不支持重" + + "置磁盘,您可以从快照创建新盘\x02Qcloud重置磁盘要求虚机状态必须是运行中或已关机\x02Ucloud重置磁盘要求磁盘处于未挂载状态" + + "\x02Ucloud仅支持数据盘重置操作\x02ZStack重置磁盘要求虚机处于关机状态\x02解析远端IP地址出错:%s\x02子网未找到" + + "\x02裸金属服务器Agent未找到\x02裸金属服务器package未准备好\x02检查agent唯一性失败:%s\x02manager_ur" + + "l冲突:%s\x02manager_url重复:%s\x02未指定duration/expire_time\x02新的过期时间先于当前值\x02" + + "无效的duration %s: %s\x02找不到名称\x02无效的桶名%s:%s\x02无效的桶名%s:%s\x02没有外部桶\x02获取" + + "对象出错:%s\x02未指定key\x02生成临时URL出错:%s\x02空的目录名\x02无效的key %s:%s\x02GetIObje" + + "ct调用出错:%s\x02对象数量超出限制\x02bucket.GetQuotaKeys调用出错:%s\x02创建目录失败:%s\x02keys" + + "列表为空\x02对象key不应以斜线结尾\x02无效的对象key:%s\x02找不到Content-Length\x02无效的Content" + + "-Length %s\x02Content-Length为负值%d\x02GetIObject调用出错:%s\x02对象大小超出限制\x02bu" + + "cket.GetQuotaKeys调用出错:%s\x02更新对象出错:%s\x02setAcl调用出错:%s\x02syncWithCloudB" + + "ucket调用出错:%s\x02桶当前有%d个活跃任务在执行,无法执行同步状态操作\x02桶不为空\x02找不到对象%s\x02iBucket." + + "GetIObjects调用出错:%s\x02unmarshal limit参数出错:%s\x02SetLimit调用出错:%s\x02更新出错:" + + "%s\x02找不到manager\x02iBucket.GetIObject调用出错:%s\x02ValidateDeleteCondition" + + "出错:%s\x02镜像已被缓存到磁盘\x02镜像引用会话还未过期\x02找不到存储缓存%s\x02不允许查询系统能力\x02账号为启用状态" + + "\x02账号不空闲\x02%s provider: %v\x02无法启用正在删除中的账号\x02无效的代理设备%s\x02不支持provider" + + "为%s的云账号\x02不支持%s\x02项目%s(%s)不属于域%s(%s)\x02不支持品牌%s,仅支持%s\x02唯一性检查失败\x02" + + "账户已被注册\x02找不到provider %s\x02无效的账号信息:%s\x02检查重复account_id出错\x02账号%s已被注册" + + "\x02账号已禁用\x02账号自动同步已启用\x02无效的输入:%s\x02无法找到provider工厂:%v\x02unmarshal输入参数" + + "出错:%v\x02检查唯一性出错:%s\x02账号%s出现冲突\x02account_id不一致,之前为%q,现在为%q\x02找不到项目%" + + "s\x02状态为%s时无法启用自动同步\x02provider在域间共享\x02不支持%s\x02%s不支持创建订阅\x02不允许创建\x02p" + + "rovider当前是启用状态\x02provider当前不是空闲状态\x02不支持直接创建cloudprovider,请先创建云账号\x02找不" + + "到region %s\x02找不到zone %s\x02cloudprovider是已禁用状态\x02云账号是已禁用状态\x02不允许跨域变" + + "更项目属性\x02不允许变更为私有云另外一个域\x02获取provider驱动失败:%s\x02不支持存储类型\x02GetZoneCoun" + + "t调用失败\x02GetVpcCount调用失败\x02cloudregion不为空\x02不允许删除默认cloudregion\x02找不到V" + + "PC %s\x02不允许更新外部资源\x02查找数据库实例出错:%s\x02数据库实例%s(%s)当前状态为%s,要求状态为%s\x02找不到数" + + "据库实例%s(%s)所属的region\x02在数据库实例%s(%s)中查找%s库失败:%v\x02查找数据库%s失败,在实例%s(%s)中" + + "查找出错:%v\x02账号%s(%s)已有权限%s访问数据库%s(%s)\x02账号状态不是%s,当前状态为%s\x02实例状态不是%s,当" + + "前状态为%s\x02数据库状态不是%s,当前状态为%s\x02账号%s(%s)没有数据库%s(%s)的权限\x02数据库实例没有有效的clo" + + "udprovider\x02数据库实例的备份当前有%d个活跃任务执行中,无法执行同步状态操作\x02找不到数据库实例%s(%s)的账号%s:%v" + + "\x02未实现\x02无效地址:%s\x02%s不在子网%s(%s)的范围中\x02云订阅 %s(%s) 无法使用\x02无效的时间长度%s" + "\x02不支持的时间长度%s\x02区域 %s 不支持创建RDS\x02区域 %s 不支持创建 %s 类型RDS\x02找不到匹配的dbinst" + "ance sku\x02%s RDS不支持安全组\x02%s RDS支持绑定最多 %d 个安全组\x02不可在%s状态做恢复操作,要求状态必须为" + "%s\x02备份%s(%s)中不包含数据库%s\x02数据库%s与实例%s(%s)冲突\x02备份与数据库实例不属于同一个云账号\x02备份与数" + @@ -3223,38 +3619,38 @@ const zh_CNData string = "" + // Size: 46682 bytes "留%dM内存,因资源不够\x02宿主机%s无法为每个透传设备预留%dM存储,因资源不够\x02仅系统管理员可指定宿主机\x02存储正使用中" + "\x02查找存储%s失败\x02查找宿主机%s失败\x02unmarshal JoinResourceBaseCreateInput出错:%s" + "\x02GetGuestDiskCount失败:%s\x02GetGuestnicsCount调用出错:%s\x02宿主机上的虚机正在使用此二层" + - "网络中的子网\x02快照正删除中\x02透传设备正被虚机使用\x02找不到透传设备%s\x02透传设备已被另外一台虚机%s使用\x02透传设" + - "备已被虚机%s使用\x02不支持格式%s\x02无效的公钥:%v\x02GetLinkedGuestsCount失败:%s\x02密钥对正被" + - "虚机使用无法删除\x02找不到转发策略%s(%s)所属的监听\x02无效的地址%s\x02描述文本过长(%d>=%d)\x02描述文本包含不" + - "可打印字符:%v\x02访问控制包含重复的CIDR %s\x02获取访问控制数量失败:%s\x02访问控制%s仍被%d个%s使用\x02无效" + - "的VRRP网络接口名%q\x02无效的VRRP认证密钥长度:%d,要求[1,8]\x02无效的VRRP优先级%d,要求[1,255]\x02" + - "无效的VRRP virtual_router_id %d:要求[1,255]\x02无效的VRRP advert_int %d:要求[1,2" + - "55]\x02telegraf参数:无效的influxdb URL:%s\x02%s:无效的base64编码串:%s\x02%s:无效的模板:%" + - "s\x02获取其它集群的转发节点失败:%v\x02与转发节点%s(%s)冲突:%v\x02转发集群%s(%s)已占用virtual_router" + - "_id %d\x02%s:时间错误:%s\x02%s:新指定时间在未来:%s > %s\x02集群中已有节点%s(%s)使用VRRP优先级%d" + - "\x02使用yum模式需要提供有效的repo_base_url参数\x02主机名为空\x02查找宿主机%s出错:%v\x02转发节点不能部署到纳" + - "管的宿主机上\x02查找虚机%s出错:%v\x02转发节点不能部署到公有云虚机上\x02虚机当前状态为%q,要求为%q\x02unmarsh" + - "al输入参数出错: %v\x02主机缺少%s字段\x02主机缺少%s字段\x02认证出错:%v\x02用户必须有系统管理员权限\x02获取%s " + - "%s服务URL出错:%v\x02上次部署的信息不可用\x02查找后端组相关的资源出错\x02无效的权重%d,要求在0~256范围\x02无效的端" + - "口%d,要求在1~65535范围\x02找不到虚机%s\x02仅系统管理员可指定宿主机作为后端\x02找不到宿主机%s\x02未识别的后端类" + - "型%s\x02第%d个后端所属的region与lb的region不匹配\x02查找负载均衡所属region失败:%s\x02调用isDefa" + - "ult出错:%s\x02后端服务器组%s是默认组\x02调用refCount出错:%s\x02后端组%s仍被%d个%s使用\x02%s要求添加到" + - "后端组中的虚机状态为%s,当前虚机状态为%s\x02虚机%s(%s)所处VPC %s(%s)和负载均衡所处VPC %s不一致\x02获取虚机" + - "%s出错\x02虚机%s(%s)所属VPC %s(%s)不是%s(%s)\x02虚机%s(%s)已存在于后端组%s(%s)\x02查找负载均衡后" + - "端%s的region时出错\x02查找后端%s(%s)所属的后端组时出错\x02访问控制缓存已在region %s存在\x02获取证书使用数" + - "失败:%s\x02证书%s仍被%d个%s使用\x02无效的本地证书:私钥为空\x02无效的本地证书:证书内容为空\x02证书缓存已在regi" + - "on %s存在\x02不允许变更证书内容\x02zone %s(%s)不合法:仅允许本地IDC的zone\x02二层网络所属zone %s与参数" + - "中的zone %s(%s)冲突,\x02wire所属zone必须为%s,实际为%s\x02获取负载均衡集群引用次数失败:%v\x02负载均衡" + - "集群仍被%d个%s使用\x04\x00\x01 J\x02负载均衡集群%s(%s)和%s(%s)两者virtual_router_id参数冲" + - "突:%d\x02无效的条件格式,要求为JSON\x02无效的表达式格式,要求为JSON数组\x02触及条件数目上限(5),已指定%d个" + - "\x02规则%s/%s已被%s(%s)使用\x02查找负载均衡监听所属region失败:%s\x02查找负载均衡转发策略失败:%s\x02%s监" + - "听的端口%d已被%s(%s)占用\x02无法找到region信息\x02后端服务器组%s(%s)属于负载均衡实例%s,而不是%s\x04" + - "\x00\x01 9\x02负载均衡集群所属zone %s与子网zone %s不匹配\x02负载均衡集群所属wire与子网所属wire不匹配:%" + - "s != %s\x02负载均衡已经被锁定,无法删除\x02Unmarshal输入参数失败:%s\x02端口值错误\x02无效的内网IP地址:%s" + - "\x02EIP已绑定到其它实例\x02EIP已绑定到SNAT规则\x02找不到EIP\x02NAT网关已有%d个活跃任务在执行,无法执行同步状态" + - "操作\x02sourceCIDR和network_id仅允许指定其中一个\x02CIDR %s不在VPC允许范围%s内\x02EIP已绑定到" + - "DNAT规则\x02找不到子网\x02GetAllocatedNicCount失败:%s\x02IP子网 %s 包含已分配的IP地址\x02地址" + - "%s不在子网%s(%s)范围内\x02isAddressUsed调用失败:%s\x02地址%s已被占用\x02getFreeAddressCou" + + "网络中的子网\x02透传设备正被虚机使用\x02找不到透传设备%s\x02透传设备已被另外一台虚机%s使用\x02透传设备已被虚机%s使用" + + "\x02不支持格式%s\x02无效的公钥:%v\x02GetLinkedGuestsCount失败:%s\x02密钥对正被虚机使用无法删除" + + "\x02找不到转发策略%s(%s)所属的监听\x02无效的地址%s\x02描述文本过长(%d>=%d)\x02描述文本包含不可打印字符:%v" + + "\x02访问控制包含重复的CIDR %s\x02获取访问控制数量失败:%s\x02访问控制%s仍被%d个%s使用\x02无效的VRRP网络接口名" + + "%q\x02无效的VRRP认证密钥长度:%d,要求[1,8]\x02无效的VRRP优先级%d,要求[1,255]\x02无效的VRRP virt" + + "ual_router_id %d:要求[1,255]\x02无效的VRRP advert_int %d:要求[1,255]\x02telegra" + + "f参数:无效的influxdb URL:%s\x02%s:无效的base64编码串:%s\x02%s:无效的模板:%s\x02获取其它集群的转发" + + "节点失败:%v\x02与转发节点%s(%s)冲突:%v\x02转发集群%s(%s)已占用virtual_router_id %d\x02%s" + + ":时间错误:%s\x02%s:新指定时间在未来:%s > %s\x02集群中已有节点%s(%s)使用VRRP优先级%d\x02使用yum模式" + + "需要提供有效的repo_base_url参数\x02主机名为空\x02查找宿主机%s出错:%v\x02转发节点不能部署到纳管的宿主机上" + + "\x02查找虚机%s出错:%v\x02转发节点不能部署到公有云虚机上\x02虚机当前状态为%q,要求为%q\x02unmarshal输入参数出错" + + ": %v\x02主机缺少%s字段\x02主机缺少%s字段\x02认证出错:%v\x02用户必须有系统管理员权限\x02获取%s %s服务URL出" + + "错:%v\x02上次部署的信息不可用\x02查找后端组相关的资源出错\x02无效的权重%d,要求在0~256范围\x02无效的端口%d,要求" + + "在1~65535范围\x02找不到虚机%s\x02仅系统管理员可指定宿主机作为后端\x02找不到宿主机%s\x02未识别的后端类型%s" + + "\x02第%d个后端所属的region与lb的region不匹配\x02查找负载均衡所属region失败:%s\x02调用isDefault出错" + + ":%s\x02后端服务器组%s是默认组\x02调用refCount出错:%s\x02后端组%s仍被%d个%s使用\x02%s要求添加到后端组" + + "中的虚机状态为%s,当前虚机状态为%s\x02虚机%s(%s)所处VPC %s(%s)和负载均衡所处VPC %s不一致\x02获取虚机%s出" + + "错\x02虚机%s(%s)所属VPC %s(%s)不是%s(%s)\x02虚机%s(%s)已存在于后端组%s(%s)\x02查找负载均衡后端" + + "%s的region时出错\x02查找后端%s(%s)所属的后端组时出错\x02访问控制缓存已在region %s存在\x02获取证书使用数失败:" + + "%s\x02证书%s仍被%d个%s使用\x02无效的本地证书:私钥为空\x02无效的本地证书:证书内容为空\x02证书缓存已在region %s" + + "存在\x02不允许变更证书内容\x02zone %s(%s)不合法:仅允许本地IDC的zone\x02二层网络所属zone %s与参数中的z" + + "one %s(%s)冲突,\x02wire所属zone必须为%s,实际为%s\x02获取负载均衡集群引用次数失败:%v\x02负载均衡集群仍被%" + + "d个%s使用\x04\x00\x01 J\x02负载均衡集群%s(%s)和%s(%s)两者virtual_router_id参数冲突:%d" + + "\x02无效的条件格式,要求为JSON\x02无效的表达式格式,要求为JSON数组\x02触及条件数目上限(5),已指定%d个\x02规则%s/" + + "%s已被%s(%s)使用\x02查找负载均衡监听所属region失败:%s\x02查找负载均衡转发策略失败:%s\x02%s监听的端口%d已被%" + + "s(%s)占用\x02无法找到region信息\x02后端服务器组%s(%s)属于负载均衡实例%s,而不是%s\x04\x00\x01 9" + + "\x02负载均衡集群所属zone %s与子网zone %s不匹配\x02负载均衡集群所属wire与子网所属wire不匹配:%s != %s" + + "\x02负载均衡已经被锁定,无法删除\x02Unmarshal输入参数失败:%s\x02端口值错误\x02无效的内网IP地址:%s\x02EIP" + + "已绑定到其它实例\x02EIP已绑定到SNAT规则\x02找不到EIP\x02NAT网关已有%d个活跃任务在执行,无法执行同步状态操作" + + "\x02sourceCIDR和network_id仅允许指定其中一个\x02CIDR %s不在VPC允许范围%s内\x02EIP已绑定到DNAT" + + "规则\x02找不到子网\x02GetAllocatedNicCount失败:%s\x02IP子网 %s 包含已分配的IP地址\x02地址%s" + + "不在子网%s(%s)范围内\x02isAddressUsed调用失败:%s\x02地址%s已被占用\x02getFreeAddressCou" + "nt调用失败:%s\x02子网%s(%s)已没有可用地址\x02候选IP %s不在范围内\x02找不到可用IP地址\x02不允许使用网络%s" + "\x02查找网络%s失败:%v\x02地址%s不在范围内\x02仅系统管理员允许使用预留的IP地址\x02地址%s未被预留\x02地址%s已被使" + "用\x02带宽限速不可超过%dMbps\x02无效的时间长度%s\x02无效的IP地址%s:%s\x02地址%s不在子网中\x02获取预留地" + @@ -3270,103 +3666,103 @@ const zh_CNData string = "" + // Size: 46682 bytes "仅本地IDC支持此操作\x02无效的IP %s\x02分割IP %s是子网起始IP\x02分割IP %s不在范围\x02名称重复\x02Ge" + "nerateName调用失败:%s\x02生成网卡名指引失败:%s\x02ip\x02仅支持服务类型%s\x02仅支持本地IDC的子网\x02本" + "地IDC子网无法执行同步状态操作\x02无法变更纳管子网的状态\x02无效的状态%s\x02BgpType字段只对EIP类型的IP子网有意义" + - "\x02不支持创建\x02不支持创建策略定义\x02路由表所属cloudprovider为启用状态,无法执行purge操作\x02unmarsh" + - "al CIDR列表失败:%s\x02min_instance_number不应小于0\x02min_instance_number不应大于max" + - "_instance_number\x02desire_instance_number应在min_instance_number和max_inst" + - "ance_number范围内\x02找不到cloudregion %s\x02伸缩组需要指定网络参数\x02部分子网不存在\x02子网%s不在v" + - "pc %s中\x02找不到虚机模板%s\x02虚机模板%s在cloudregion %s中无效,原因:%s\x02未知的扩容原则:%s\x02未" + - "知的缩容原则:%s\x02未知的健康检查模式%s\x02找不到负载均衡后端组%s\x02找不到负载均衡后端端口%d\x02无效的负载均衡后端" + - "权重%d\x02请先禁用此伸缩组\x02此伸缩组中还存在虚机,请先将它们删除\x02找不到此伸缩组%s\x02虚机%s不属于伸缩组%s" + - "\x02伸缩策略必须属于某伸缩组\x02找不到伸缩组%s\x02未知的触发类型%s\x02未知的伸缩策略动作%s\x02未知的伸缩策略计量单位%" + - "s\x02除非状态ready,否则无法触发伸缩策略\x02报警id不匹配\x02告警%s中包含未知的操作符\x02告警%s中存在未知的指示器" + - "\x02告警%s中存在未知的聚合函数\x02告警周期的最小值为300\x02无效的策略%s\x02不支持资源类型%q\x02无效schedtag" + - "_id参数\x02不支持资源类型%s\x02调度标签%s\x02调度标签%s的资源类型不匹配:%s != %s\x02unmarshal Joi" + - "ntResourceCreateInput出错:%s\x02无效的调度标签%s\x02无效的默认策略%s\x02无法设定%s作为默认策略\x02" + - "GetObjectCount调用出错:%s\x02标签已关联至%s\x02getDynamicSchedtagCount调用出错:%s\x02标" + - "签已有动态规则\x02getSchedPoliciesCount调用出错:%s\x02标签已与调度策略绑定\x02调度标签%s的资源类型为%" + - "s,与%s不匹配\x02未知的调度类型%q\x02未知的资源类型%q\x02未知的操作%q\x02未知的标签类型%q\x02调度任务正执行中,请" + - "稍后尝试\x02需要指定调度任务\x02权限不足\x02unmarshal输入参数出错:%v\x02获取安全组%s失败\x02第%d条规则无" + - "效:%s\x02vpc %s(%s)不是一个纳管资源\x02不支持缓存经典安全组\x02无效的IP地址:%s\x02安全组%s的规则与%s不" + - "相等\x02GetGuestCount调用出错:%s\x02安全组正使用中\x02不允许删除默认的安全组\x02找不到虚机模板\x02解析图" + - "标URL出错:%s\x02找不到虚机模板%s\x02找不到cloudregion %s\x02查找zone %s失败\x02cpu_core" + - "_count应在1~256范围内\x02memory_size_mb应在512~%d范围内\x02instance_type_category应" + - "是%s其中之一\x02检查SKU重名时出错:%v\x02重复的SKU %s\x02实例规格列表查询出错\x02无法变更公有云%s SKU的实" + - "例类型\x02无法变更SKU名称\x02检查实例出错\x02不允许删除正在使用中的实例类型,请先移除相关的虚机:%s\x02不允许删除公有云" + - "instance_type:%s\x02查找zone %s(%s)所属的cloudregion失败\x02实例类型%s重复\x02查询SKU列表" + - "出错\x02删除SKU %s失败\x02仅支持缓存私有云SKU\x02获取region %s(%s)所属cloudprovider失败" + - "\x02cloudprovider %s(%s)已被禁用\x02保留天数须在范围1~%d内,或为-1\x02repeat_weekdays最多只" + - "能包含%d天\x02time_points最多只能包含%d个时间点\x02Unmarshal输入参数出错:%s\x02保持日期数必须在1~6" + - "5535范围内,或为-1\x02无需更新\x02无法删除已绑定到磁盘的快照策略\x02找不到磁盘%s\x02磁盘快照策略已存在\x02磁盘%s绑" + - "定了过多的快照策略\x02找不到虚机%s\x02删除磁盘%s失败\x02找不到存储%s(%s)所属的region\x02无法删除状态为%s的" + - "快照\x02获取实例快照出错:%s\x02快照仍被实例快照引用\x02磁盘重置时无法删除快照\x02快照有%d个任务自执行中,无法执行同步状" + - "态操作\x02因磁盘%s仍存在,无法删除其快照\x02磁盘%s没有快照\x02无法删除磁盘快照,因存在手工快照\x02快照所属cloudpr" + - "ovider为启用状态,无法执行purge操作\x02getReferenceCount调用出错:%s\x02镜像正使用中\x02下载会话还未过" + - "期\x02无法删除最后的缓存\x02状态为%s无法取消缓存\x02存储缓存非空\x02仍被存储引用\x02无法取消非定义镜像的缓存\x02镜" + - "像不在缓存存储中\x02标记缓存状态出错:%s\x02镜像Id或名称未指定\x02无效的存储类型%s\x02无效的介质类型%s\x02不支持" + - "创建%s存储\x02GetHostCount调用失败:%s\x02存储已关联宿主机\x02存储上还有磁盘\x02存储上还有快照\x02找不到" + - "存储缓存\x02存储是已启用状态\x02宿主机在线状态无法解绑\x02宿主机%s找不到存储%s\x02无效的external_access_" + - "mode %q,要求%s\x02GetNetworkCount调用出错:%s\x02VPC不为空,请先删除其中的网络\x02GetNatgate" + - "wayCount调用出错:%v\x02VPC不为空,请先删除其中的NAT网关\x02不允许删除默认VPC\x02无效的cidr_block %s" + - "\x02VPC所属cloudprovider为启用状态,无法执行purge操作\x02本地IDC VPC不支持同步状态操作\x02对于经典网络," + - "仅可设置为系统级别的共享\x02禁止将经典网络设为私有\x02映射IP枯竭\x02带宽值必须大于0\x02MTU值必须在0~1000000范" + - "围内\x02当前仅KVM平台支持创建二层网络\x02HostCount调用出错:%s\x02二层网络中包含宿主机\x02NetworkCou" + - "nt调用出错:%s\x02二层网络包含子网\x02zone不为空\x02不支持为%s创建zone\x02内网负载均衡实例不支持带宽计费\x02负" + - "载均衡实例的manager %s(%s)与VPC的%s(%s)不匹配\x02阿里云不支持变更证书\x02主备后端组必须包含两个后端\x02不" + - "支持后端组类型%s\x02无效的虚机机%s\x02阿里云实例权限必须在0~100范围内\x02内部错误:未知的后端类型%s\x02后端组%s" + - "不支持此操作\x02宿主机的region %q(%s)与负载均衡实例的%q(%s)不相符\x02%s后端组不支持变更端口\x02%s后端组不" + - "支持变更端口和权限\x02未知的后端组类型%s\x02监听类型必须为http/https,输入为%s\x02后端组%s(%s)属于负载均衡%" + - "s,而不是%s\x02后端组必须为普通类型\x02转发规则%s(%s):获取归属监听%s失败\x02HTTP、HTTPS监听仅支持默认和普通后端" + - "组\x02health_check_domain长度必须在1~80范围内\x02%s长度必须在500个字母以内\x02sticky_sess" + - "ion_cookie长度必须在1~200范围内\x02sticky_session_cookie仅允许包含字母、数字、下划线和破折号\x02未知" + - "的sticky_session_type,仅支持%s或%s\x02找不到负载均衡实例%s(%s)的region\x02调度算法%s对于性能共" + - "享性负载均衡实例不可用\x02找不到监听%s(%s)所属的负载均衡实例\x02cloudregion %s(%s)不支持%s调度器\x02无" + - "效%s,要求为整数\x02%s不能被置为0\x02%s不支持关闭tcp或udp监听的健康检查\x02%s快照名称不可以auto,http:/" + - "/,https://开头\x02阿里云%s不支持恢复\x02阿里云仅支持从它自己的备份中恢复\x02阿里云%s 5.7/8.0支持local_s" + - "sd+high_availability,5.6仅支持从它自己的备份中恢复\x02备份实例不支持预付费计费类型\x02无法在子网%s(%s)和区" + - "域%s(%s)中找到匹配的SKU\x02%s %s不支持创建只读MySQL数据库实例\x02不支持创建只读MySQL %s %s实例:不支持" + - "%s存储类型,仅支持%s\x02不支持创建只读MySQL %s数据库实例\x02SQL Server只支持创建2017_ent型只读数据库实例" + - "\x02不可创建超过7个只读SQL Server数据库实例\x02不支持创建引擎为%s的只读数据库实例\x02主实例内在大于等于64GB,最多可" + - "创建10个只读实例\x02主实例内在小于64GB,最多可创建5个只读实例\x02VPC %s(%s)至少需要2个子网来使用阿里云%s(%s)" + - "\x02描述不可以http://、https://开头\x02阿里云数据库实例账号名称长度应在2~16内\x02%s是阿里云%s的保留名称" + - "\x02账号名称中包含无效字符:%s\x02账号名称不可以下划线开头或结尾\x02%s仅对阿里云%s或%s有效\x02%s仅对阿里云%s有效" + - "\x02未知的权限%s\x02子网%s所属的VPC未找到\x02account_privilege %s仅支持Redis版本4.0\x02要求至" + - "少%d子网\x02要求至少有%d个包含8个可用IP的子网\x02负载均衡实例的manager %s与VPC %s(%s)的不匹配:%s" + - "\x02所有子网应属于同一个VPC:%s\x02已经有一个子网在区域%s:%s\x02无效的loadbalancer_spec参数:%s\x02" + - "无效的backendgroup参数:%s\x02无效的loadbalancer_spec参数:%s\x02%s当前不支持创建负载均衡访问控制" + - "规则\x02%s当前不支持创建负载均衡证书\x02监听%s所属的负载均衡实例%s找不到\x02后端%s已经以端口%d注册\x02%s当前不支" + - "持创建负载均衡实例\x02磁盘和快照策略应在相同的域\x02磁盘和快照策略应在相同的项目\x02%s不支持创建负载均衡实例\x02%s不支持" + - "创建负载均衡访问控制规则\x02%s不支持创建负载均衡证书\x02Google数据库实例不支持预付费计费类型\x02磁盘尺寸必须在10~30" + - "720GB范围内\x02EIP %s(%s)与vpc %s(%s)的manager id (%s)不匹配\x02负载均衡实例正被%d个监听使用" + - "\x02负载均衡实例正被%d个后端组使用\x02不支持创建引擎类型为%s的只读数据库实例\x02华为云数据库实例名称长度必须在4~64范围内" + - "\x02%s要求磁盘大小必须在40~4000GB范围内\x02disk_size_gb必须是10的整数倍\x02不支持为华为云%s实例创建账号" + - "\x02华为云RDS密码不允许是账号名的反转\x02不支持创建华为云%s实例创建数据库\x02华为云数据库实例备份名称长度应在4~64范围内" + - "\x02华为云仅支持使用%s时指定数据库\x02华为云数据库实例的磁盘不可缩容\x02华为云数据库实例的种类不可变更\x02华为云数据库实例的存" + - "储类型不可变更\x02华为云当前不支持重置数据库实例的账号密码\x02无需为管理员账号授予或收回权限\x02%s不支持恢复\x02华为云%s" + - " RDS不支持从它自己的备份中恢复\x02华为云仅%s引擎支持数据库恢复\x02新数据库名称不允许是%s\x02zone不匹配,弹性缓存SKU的" + - "zone %s != %s\x02SKU %s已售罄\x02%s不支持创建账号\x02华为云%s类型弹性缓存不支持创建备份\x02zone信息未" + - "指定\x02VPC中的负载均衡暂不支持\x02zone %s(%s)没有可用负载均衡转发集群\x02没有可用的负载均衡转发集群\x02宿主机" + - "%s没有可访问的IP\x02查找虚机%s所在宿主机出错\x02查找负载均衡后端组%s所属的实例出错\x02宿主机%q(%s)所属的region与" + - "负载均衡实例所属的%q(%s)不一致\x02跳转应至少变更scheme, host, path中的一项\x02未指定backend_grou" + - "p参数\x02非跳转类型监听必须指定backend_group参数\x02仅http/https监听可启用跳转功能\x02非http监听必须指定" + - "后端组\x02无效的子网类型%q,期望%q\x02查找子网%s(%s)所属的VPC时出错\x02子网%s(%s)不属于%s\x02KVM快照" + - "找不到所属的存储\x02找不到acl %s\x02不可变更负载均衡监听的listener_type\x02不可变更负载均衡监听的listen" + - "er_port\x02无法在状态%s时创建备份\x02负载均衡监听%s正在变更中\x02负载均衡后端组已与监听%s绑定\x02%s要求掩码大小在" + - "16到28范围内\x02负载均衡实例已与四层监听%s关联\x02路径不可为空\x02虚机%s、端口%d已注册\x02虚机%s、端口%d正被监听%" + - "s使用\x02腾讯云基础类型MySQL实例不支持创建备份\x02腾讯云不支持创建数据库\x02腾讯云Redis 2.8版本不支持创建账号\x02" + - "未指定规格查询参数\x02解析规格参数%s出错:%v\x02获取对象出错:%v\x02空的project_id/tenant_id\x02找" + - "不到项目%s\x02快照正被磁盘使用,无法删除\x02磁盘需要至少1个快照作为后备文件\x02磁盘%s没有挂载到虚机\x02磁盘所挂载的虚机" + - "有备机,无法创建快照\x02虚机状态为%s时无法创建快照\x02检查磁盘快照数时出错:%s\x02磁盘%s快照数满,不可再创建\x02RBD" + - "存储%s(%s)已存在\x02无效网关\x02服务器内部错误\x02资源不在可用状态\x02支付类错误\x02镜像未找到\x02找不到资源" + - "\x02找不到Spec\x02找不到动作\x02找不到租户\x02找不到用户\x02服务器状态错误\x02无效的格式\x02输入参数错误\x02" + - "弱密码\x02参数不存在\x02资源不足\x02资源不足\x02配置不足\x02超出范围\x02超出上限\x02权限不足\x02不支持的操作" + - "\x02内容非空\x02无效的请求\x02空的请求\x02未授权\x02无效的凭证\x02禁止\x02不可接受\x02名称重复\x02资源重复" + - "\x02冲突\x02资源忙\x02需要License\x02被保护的资源\x02没有项目\x02实体太大\x02尝试失败次数过多\x02请求数过" + - "多\x02不支持的协议\x02策略定义错误\x02找不到镜像%s\x02密码长度至少12个字符,且内容包含数字、大小写字母和标点符号\x02" + - "找不到参数%s\x02%s资源重名:%s\x02%s资源id重复:%s\x02未授权\x02无效的Token\x02找不到名字%s\x02找" + - "不到登录密文信息\x02找不到%s的TOTP信息\x02找不到%s的密文恢复信息\x02TOTP密文已存在\x02找不到密码\x02找不到S" + - "SH密码:%s\x02无效的资源格式\x02服务%s找不到:%v\x02未指定uid\x02未指定pids\x02pids中未指定pid\x02" + - "pids中未指定rid\x02未指定rid\x02找不到项目\x02找不到登录login_key\x02查询中找不到kind:%v\x02查询中" + - "找不到key:%v\x02不支持的动作%s\x02找不到数据源对应的执行器\x02安全组id不应为空\x02找不到安全组%s\x02无效的服" + - "务点\x02解析URL %q出错:%v" + "\x02不支持创建\x02不支持创建策略定义\x02权限不足\x02路由表所属cloudprovider为启用状态,无法执行purge操作" + + "\x02unmarshal CIDR列表失败:%s\x02min_instance_number不应小于0\x02min_instance_nu" + + "mber不应大于max_instance_number\x02desire_instance_number应在min_instance_numb" + + "er和max_instance_number范围内\x02找不到cloudregion %s\x02伸缩组需要指定网络参数\x02部分子网不存在" + + "\x02子网%s不在vpc %s中\x02找不到虚机模板%s\x02虚机模板%s在cloudregion %s中无效,原因:%s\x02未知的扩" + + "容原则:%s\x02未知的缩容原则:%s\x02未知的健康检查模式%s\x02找不到负载均衡后端组%s\x02找不到负载均衡后端端口%d" + + "\x02无效的负载均衡后端权重%d\x02请先禁用此伸缩组\x02此伸缩组中还存在虚机,请先将它们删除\x02找不到此伸缩组%s\x02虚机%s" + + "不属于伸缩组%s\x02伸缩策略必须属于某伸缩组\x02找不到伸缩组%s\x02未知的触发类型%s\x02未知的伸缩策略动作%s\x02未知" + + "的伸缩策略计量单位%s\x02除非状态ready,否则无法触发伸缩策略\x02报警id不匹配\x02告警%s中包含未知的操作符\x02告警%" + + "s中存在未知的指示器\x02告警%s中存在未知的聚合函数\x02告警周期的最小值为300\x02无效的策略%s\x02不支持资源类型%q\x02" + + "无效schedtag_id参数\x02不支持资源类型%s\x02调度标签%s\x02调度标签%s的资源类型不匹配:%s != %s\x02u" + + "nmarshal JointResourceCreateInput出错:%s\x02无效的调度标签%s\x02无效的默认策略%s\x02无法设定" + + "%s作为默认策略\x02GetObjectCount调用出错:%s\x02标签已关联至%s\x02getDynamicSchedtagCount" + + "调用出错:%s\x02标签已有动态规则\x02getSchedPoliciesCount调用出错:%s\x02标签已与调度策略绑定\x02调" + + "度标签%s的资源类型为%s,与%s不匹配\x02未知的调度类型%q\x02未知的资源类型%q\x02未知的操作%q\x02未知的标签类型%q" + + "\x02调度任务正执行中,请稍后尝试\x02需要指定调度任务\x02unmarshal输入参数出错:%v\x02获取安全组%s失败\x02第%d" + + "条规则无效:%s\x02vpc %s(%s)不是一个纳管资源\x02不支持缓存经典安全组\x02无效的IP地址:%s\x02安全组%s的规则" + + "与%s不相等\x02GetGuestCount调用出错:%s\x02安全组正使用中\x02不允许删除默认的安全组\x02找不到虚机模板" + + "\x02解析图标URL出错:%s\x02找不到虚机模板%s\x02cpu_core_count应在1~256范围内\x02memory_size" + + "_mb应在512~%d范围内\x02instance_type_category应是%s其中之一\x02检查SKU重名时出错:%v\x02重复的" + + "SKU %s\x02实例规格列表查询出错\x02无法变更公有云%s SKU的实例类型\x02无法变更SKU名称\x02检查实例出错\x02不允许" + + "删除正在使用中的实例类型,请先移除相关的虚机:%s\x02不允许删除公有云instance_type:%s\x02查找zone %s(%s)" + + "所属的cloudregion失败\x02实例类型%s重复\x02查询SKU列表出错\x02删除SKU %s失败\x02仅支持缓存私有云SKU" + + "\x02获取region %s(%s)所属cloudprovider失败\x02cloudprovider %s(%s)已被禁用\x02保留天数" + + "须在范围1~%d内,或为-1\x02repeat_weekdays最多只能包含%d天\x02time_points最多只能包含%d个时间点" + + "\x02Unmarshal输入参数出错:%s\x02保持日期数必须在1~65535范围内,或为-1\x02无需更新\x02无法删除已绑定到磁盘的" + + "快照策略\x02找不到磁盘%s\x02磁盘快照策略已存在\x02磁盘%s绑定了过多的快照策略\x02找不到虚机%s\x02删除磁盘%s失败" + + "\x02找不到存储%s(%s)所属的region\x02无法删除状态为%s的快照\x02获取实例快照出错:%s\x02快照仍被实例快照引用" + + "\x02磁盘重置时无法删除快照\x02快照有%d个任务自执行中,无法执行同步状态操作\x02因磁盘%s仍存在,无法删除其快照\x02磁盘%s没有" + + "快照\x02无法删除磁盘快照,因存在手工快照\x02快照所属cloudprovider为启用状态,无法执行purge操作\x02getRef" + + "erenceCount调用出错:%s\x02镜像正使用中\x02下载会话还未过期\x02无法删除最后的缓存\x02状态为%s无法取消缓存\x02" + + "存储缓存非空\x02仍被存储引用\x02无法取消非定义镜像的缓存\x02镜像不在缓存存储中\x02标记缓存状态出错:%s\x02镜像Id或名" + + "称未指定\x02无效的存储类型%s\x02无效的介质类型%s\x02不支持创建%s存储\x02GetHostCount调用失败:%s\x02" + + "存储已关联宿主机\x02存储上还有磁盘\x02存储上还有快照\x02找不到存储缓存\x02存储是已启用状态\x02宿主机在线状态无法解绑" + + "\x02宿主机%s找不到存储%s\x02无效的external_access_mode %q,要求%s\x02GetNetworkCount调用" + + "出错:%s\x02VPC不为空,请先删除其中的网络\x02GetNatgatewayCount调用出错:%v\x02VPC不为空,请先删除其" + + "中的NAT网关\x02不允许删除默认VPC\x02无效的cidr_block %s\x02VPC所属cloudprovider为启用状态,无" + + "法执行purge操作\x02本地IDC VPC不支持同步状态操作\x02对于经典网络,仅可设置为系统级别的共享\x02禁止将经典网络设为私有" + + "\x02映射IP枯竭\x02带宽值必须大于0\x02MTU值必须在0~1000000范围内\x02当前仅KVM平台支持创建二层网络\x02Hos" + + "tCount调用出错:%s\x02二层网络中包含宿主机\x02NetworkCount调用出错:%s\x02二层网络包含子网\x02zone不为" + + "空\x02找不到cloudregion %s\x02不支持为%s创建zone\x02内网负载均衡实例不支持带宽计费\x02负载均衡实例的ma" + + "nager %s(%s)与VPC的%s(%s)不匹配\x02阿里云不支持变更证书\x02主备后端组必须包含两个后端\x02不支持后端组类型%s" + + "\x02无效的虚机机%s\x02阿里云实例权限必须在0~100范围内\x02内部错误:未知的后端类型%s\x02后端组%s不支持此操作\x02宿" + + "主机的region %q(%s)与负载均衡实例的%q(%s)不相符\x02%s后端组不支持变更端口\x02%s后端组不支持变更端口和权限" + + "\x02未知的后端组类型%s\x02监听类型必须为http/https,输入为%s\x02后端组%s(%s)属于负载均衡%s,而不是%s\x02" + + "后端组必须为普通类型\x02转发规则%s(%s):获取归属监听%s失败\x02HTTP、HTTPS监听仅支持默认和普通后端组\x02heal" + + "th_check_domain长度必须在1~80范围内\x02%s长度必须在500个字母以内\x02sticky_session_cookie长" + + "度必须在1~200范围内\x02sticky_session_cookie仅允许包含字母、数字、下划线和破折号\x02未知的sticky_s" + + "ession_type,仅支持%s或%s\x02找不到负载均衡实例%s(%s)的region\x02调度算法%s对于性能共享性负载均衡实例不可用" + + "\x02找不到监听%s(%s)所属的负载均衡实例\x02cloudregion %s(%s)不支持%s调度器\x02无效%s,要求为整数\x02" + + "%s不能被置为0\x02%s不支持关闭tcp或udp监听的健康检查\x02%s快照名称不可以auto,http://,https://开头" + + "\x02阿里云%s不支持恢复\x02阿里云仅支持从它自己的备份中恢复\x02阿里云%s 5.7/8.0支持local_ssd+high_avai" + + "lability,5.6仅支持从它自己的备份中恢复\x02备份实例不支持预付费计费类型\x02无法在子网%s(%s)和区域%s(%s)中找到匹配" + + "的SKU\x02%s %s不支持创建只读MySQL数据库实例\x02不支持创建只读MySQL %s %s实例:不支持%s存储类型,仅支持%s" + + "\x02不支持创建只读MySQL %s数据库实例\x02SQL Server只支持创建2017_ent型只读数据库实例\x02不可创建超过7个只" + + "读SQL Server数据库实例\x02不支持创建引擎为%s的只读数据库实例\x02主实例内在大于等于64GB,最多可创建10个只读实例" + + "\x02主实例内在小于64GB,最多可创建5个只读实例\x02VPC %s(%s)至少需要2个子网来使用阿里云%s(%s)\x02描述不可以ht" + + "tp://、https://开头\x02阿里云数据库实例账号名称长度应在2~16内\x02%s是阿里云%s的保留名称\x02账号名称中包含无效字" + + "符:%s\x02账号名称不可以下划线开头或结尾\x02%s仅对阿里云%s或%s有效\x02%s仅对阿里云%s有效\x02未知的权限%s" + + "\x02子网%s所属的VPC未找到\x02account_privilege %s仅支持Redis版本4.0\x02要求至少%d子网\x02要求" + + "至少有%d个包含8个可用IP的子网\x02负载均衡实例的manager %s与VPC %s(%s)的不匹配:%s\x02所有子网应属于同一个" + + "VPC:%s\x02已经有一个子网在区域%s:%s\x02无效的loadbalancer_spec参数:%s\x02无效的backendgrou" + + "p参数:%s\x02无效的loadbalancer_spec参数:%s\x02%s当前不支持创建负载均衡访问控制规则\x02%s当前不支持创建负" + + "载均衡证书\x02监听%s所属的负载均衡实例%s找不到\x02后端%s已经以端口%d注册\x02%s当前不支持创建负载均衡实例\x02磁盘和" + + "快照策略应在相同的域\x02磁盘和快照策略应在相同的项目\x02%s不支持创建负载均衡实例\x02%s不支持创建负载均衡访问控制规则\x02" + + "%s不支持创建负载均衡证书\x02Google数据库实例不支持预付费计费类型\x02磁盘尺寸必须在10~30720GB范围内\x02EIP %s" + + "(%s)与vpc %s(%s)的manager id (%s)不匹配\x02负载均衡实例正被%d个监听使用\x02负载均衡实例正被%d个后端组使" + + "用\x02不支持创建引擎类型为%s的只读数据库实例\x02华为云数据库实例名称长度必须在4~64范围内\x02%s要求磁盘大小必须在40~4" + + "000GB范围内\x02disk_size_gb必须是10的整数倍\x02不支持为华为云%s实例创建账号\x02华为云RDS密码不允许是账号名的" + + "反转\x02不支持创建华为云%s实例创建数据库\x02华为云数据库实例备份名称长度应在4~64范围内\x02华为云仅支持使用%s时指定数据库" + + "\x02华为云数据库实例的磁盘不可缩容\x02华为云数据库实例的种类不可变更\x02华为云数据库实例的存储类型不可变更\x02华为云当前不支持重" + + "置数据库实例的账号密码\x02无需为管理员账号授予或收回权限\x02%s不支持恢复\x02华为云%s RDS不支持从它自己的备份中恢复" + + "\x02华为云仅%s引擎支持数据库恢复\x02新数据库名称不允许是%s\x02zone不匹配,弹性缓存SKU的zone %s != %s\x02" + + "SKU %s已售罄\x02%s不支持创建账号\x02华为云%s类型弹性缓存不支持创建备份\x02zone信息未指定\x02VPC中的负载均衡暂不" + + "支持\x02zone %s(%s)没有可用负载均衡转发集群\x02没有可用的负载均衡转发集群\x02宿主机%s没有可访问的IP\x02查找虚" + + "机%s所在宿主机出错\x02查找负载均衡后端组%s所属的实例出错\x02宿主机%q(%s)所属的region与负载均衡实例所属的%q(%s)" + + "不一致\x02跳转应至少变更scheme, host, path中的一项\x02未指定backend_group参数\x02非跳转类型监听必" + + "须指定backend_group参数\x02仅http/https监听可启用跳转功能\x02非http监听必须指定后端组\x02无效的子网类" + + "型%q,期望%q\x02查找子网%s(%s)所属的VPC时出错\x02子网%s(%s)不属于%s\x02KVM快照找不到所属的存储\x02找" + + "不到acl %s\x02不可变更负载均衡监听的listener_type\x02不可变更负载均衡监听的listener_port\x02无法" + + "在状态%s时创建备份\x02负载均衡监听%s正在变更中\x02负载均衡后端组已与监听%s绑定\x02%s要求掩码大小在16到28范围内" + + "\x02负载均衡实例已与四层监听%s关联\x02路径不可为空\x02虚机%s、端口%d已注册\x02虚机%s、端口%d正被监听%s使用\x02腾" + + "讯云基础类型MySQL实例不支持创建备份\x02腾讯云不支持创建数据库\x02腾讯云Redis 2.8版本不支持创建账号\x02未指定规格查" + + "询参数\x02解析规格参数%s出错:%v\x02获取对象出错:%v\x02空的project_id/tenant_id\x02找不到项目%s" + + "\x02快照正被磁盘使用,无法删除\x02磁盘需要至少1个快照作为后备文件\x02磁盘%s没有挂载到虚机\x02磁盘所挂载的虚机有备机,无法创建" + + "快照\x02虚机状态为%s时无法创建快照\x02检查磁盘快照数时出错:%s\x02磁盘%s快照数满,不可再创建\x02RBD存储%s(%s)" + + "已存在\x02无效网关\x02服务器内部错误\x02资源不在可用状态\x02支付类错误\x02镜像未找到\x02找不到资源\x02找不到Sp" + + "ec\x02找不到动作\x02找不到租户\x02找不到用户\x02服务器状态错误\x02无效的格式\x02输入参数错误\x02弱密码\x02参数" + + "不存在\x02资源不足\x02资源不足\x02配置不足\x02超出范围\x02超出上限\x02权限不足\x02不支持的操作\x02内容非空" + + "\x02无效的请求\x02空的请求\x02未授权\x02无效的凭证\x02禁止\x02不可接受\x02名称重复\x02资源重复\x02冲突" + + "\x02资源忙\x02需要License\x02被保护的资源\x02没有项目\x02实体太大\x02尝试失败次数过多\x02请求数过多\x02不" + + "支持的协议\x02策略定义错误\x02找不到镜像%s\x02密码长度至少12个字符,且内容包含数字、大小写字母和标点符号\x02找不到参数%" + + "s\x02%s资源重名:%s\x02%s资源id重复:%s\x02未授权\x02无效的Token\x02找不到名字%s\x02找不到登录密文信息" + + "\x02找不到%s的TOTP信息\x02找不到%s的密文恢复信息\x02TOTP密文已存在\x02找不到密码\x02找不到SSH密码:%s" + + "\x02无效的资源格式\x02服务%s找不到:%v\x02未指定uid\x02未指定pids\x02pids中未指定pid\x02pids中未指" + + "定rid\x02未指定rid\x02找不到项目\x02找不到登录login_key\x02查询中找不到kind:%v\x02查询中找不到ke" + + "y:%v\x02不支持的动作%s\x02找不到数据源对应的执行器\x02安全组id不应为空\x02找不到安全组%s\x02无效的服务点\x02解" + + "析URL %q出错:%v" - // Total table size 107257 bytes (104KiB); checksum: 5933B471 + // Total table size 114315 bytes (111KiB); checksum: 31D43FB2 diff --git a/locales/zh-CN/messages.gotext.json b/locales/zh-CN/messages.gotext.json index 0ec76290d1..fa94ba8fb1 100644 --- a/locales/zh-CN/messages.gotext.json +++ b/locales/zh-CN/messages.gotext.json @@ -658,13 +658,13 @@ { "id": "Model not found error: cannot find %q with id/name %q", "message": "Model not found error: cannot find %q with id/name %q", - "translation": "无法通过名称或Id找到%q所指定的资源", + "translation": "无法通过名称或ID找到%q(%q)", "position": "pkg/cloudcommon/validators/errors.go:157:17" }, { "id": "Model not found error: cannot find %q with id/name %q: %s", "message": "Model not found error: cannot find %q with id/name %q: %s", - "translation": "无法通过名称或Id找到%q所指定的资源:%s", + "translation": "无法通过名称或ID找到%q(%q):%s", "position": "pkg/cloudcommon/validators/errors.go:157:17" }, { diff --git a/pkg/apis/cloudevent/cloudevent.go b/pkg/apis/cloudevent/cloudevent.go index a53ea50402..fc388f627a 100644 --- a/pkg/apis/cloudevent/cloudevent.go +++ b/pkg/apis/cloudevent/cloudevent.go @@ -23,6 +23,7 @@ import ( type CloudeventListInput struct { apis.ModelBaseListInput + apis.ProjectizedResourceListInput compute.CloudenvResourceListInput @@ -43,3 +44,9 @@ type CloudeventListInput struct { // 操作日志截止时间 Until time.Time `json:"until"` } + +type CloudeventDetails struct { + apis.ModelBaseDetails + apis.ProjectizedResourceInfo + SCloudevent +} diff --git a/pkg/apis/cloudevent/zz_generated.model.go b/pkg/apis/cloudevent/zz_generated.model.go new file mode 100644 index 0000000000..9b3e1e3d72 --- /dev/null +++ b/pkg/apis/cloudevent/zz_generated.model.go @@ -0,0 +1,49 @@ +// 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. + +// Code generated by model-api-gen. DO NOT EDIT. + +package cloudevent + +import ( + time "time" + + "yunion.io/x/onecloud/pkg/apis" +) + +// SCloudevent is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudevent/models.SCloudevent. +type SCloudevent struct { + apis.SProjectizedResourceBase + EventId int64 `json:"event_id"` + Name string `json:"name"` + Service string `json:"service"` + ResourceType string `json:"resource_type"` + Action string `json:"action"` + RequestId string `json:"request_id"` + Request interface{} `json:"request"` + Account string `json:"account"` + Success bool `json:"success"` + CreatedAt time.Time `json:"created_at"` + CloudproviderId string `json:"cloudprovider_id"` + Manager string `json:"manager"` + Provider string `json:"provider"` + Brand string `json:"brand"` +} + +// SCloudprovider is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudevent/models.SCloudprovider. +type SCloudprovider struct { + apis.SEnabledStatusStandaloneResourceBase + apis.SProjectizedResourceBase + SyncStatus string `json:"sync_status"` + Provider string `json:"provider"` + Brand string `json:"brand"` +} diff --git a/pkg/apis/compute/bucket.go b/pkg/apis/compute/bucket.go index b016aafeef..1556b29e8c 100644 --- a/pkg/apis/compute/bucket.go +++ b/pkg/apis/compute/bucket.go @@ -225,18 +225,24 @@ type BucketCORSRule struct { AllowedHeaders []string MaxAgeSeconds int ExposeHeaders []string + // 规则区别标识 + Id string } type BucketCORSRules struct { - Rules []BucketCORSRule + Data []BucketCORSRule `json:"data"` +} + +type BucketCORSRuleDeleteInput struct { + Id []string } func (input *BucketCORSRules) Validate() error { - for i := range input.Rules { - if len(input.Rules[i].AllowedOrigins) == 0 { + for i := range input.Data { + if len(input.Data[i].AllowedOrigins) == 0 { return httperrors.NewMissingParameterError("allowed_origins") } - if len(input.Rules[i].AllowedMethods) == 0 { + if len(input.Data[i].AllowedMethods) == 0 { return httperrors.NewMissingParameterError("allowed_methods") } } diff --git a/pkg/apis/compute/cdn.go b/pkg/apis/compute/cdn.go index 361ba7a39a..2a74b5ad7f 100644 --- a/pkg/apis/compute/cdn.go +++ b/pkg/apis/compute/cdn.go @@ -15,17 +15,23 @@ package compute const ( - CDN_AREA_MAINLAND = "mainland" - CDN_AREA_OVERSEAS = "overseas" - CDN_AREA_GLOBAL = "global" - CDN_ORIGIN_TYPE_DOMAIN = "domain" - CDN_ORIGIN_TYPE_IP = "ip" - CDN_ORIGIN_TYPE_BUCKET = "bucket" + CDN_DOMAIN_STATUS_ONLINE = "online" + CDN_DOMAIN_STATUS_OFFLINE = "offline" + CDN_DOMAIN_STATUS_PROCESSING = "processing" + CDN_DOMAIN_STATUS_REJECTED = "rejected" + CDN_DOMAIN_AREA_MAINLAND = "mainland" + CDN_DOMAIN_AREA_OVERSEAS = "overseas" + CDN_DOMAIN_AREA_GLOBAL = "global" + CDN_DOMAIN_ORIGIN_TYPE_DOMAIN = "domain" + CDN_DOMAIN_ORIGIN_TYPE_IP = "ip" + CDN_DOMAIN_ORIGIN_TYPE_BUCKET = "bucket" ) type CdnDomain struct { // cdn加速域名 Domain string + // 状态 rejected(域名未审核)|processing(部署中)|online|offline + Status string // 区域 mainland|overseas|global Area string // cdn Cname @@ -37,5 +43,5 @@ type CdnDomain struct { } type CdnDomains struct { - Domains []CdnDomain + Data []CdnDomain `json:"data"` } diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 29b39e3c8f..bbbe6aeeca 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -37,6 +37,7 @@ const ( CLOUD_PROVIDER_ONECLOUD = "OneCloud" CLOUD_PROVIDER_VMWARE = "VMware" CLOUD_PROVIDER_ALIYUN = "Aliyun" + CLOUD_PROVIDER_APSARA = "Apsara" CLOUD_PROVIDER_QCLOUD = "Qcloud" CLOUD_PROVIDER_AZURE = "Azure" CLOUD_PROVIDER_AWS = "Aws" @@ -85,6 +86,7 @@ var ( CLOUD_PROVIDER_ONECLOUD, CLOUD_PROVIDER_VMWARE, CLOUD_PROVIDER_ALIYUN, + CLOUD_PROVIDER_APSARA, CLOUD_PROVIDER_QCLOUD, CLOUD_PROVIDER_AZURE, CLOUD_PROVIDER_AWS, diff --git a/pkg/apis/compute/dbinstance.go b/pkg/apis/compute/dbinstance.go index 2df0a39de7..da3bb85b48 100644 --- a/pkg/apis/compute/dbinstance.go +++ b/pkg/apis/compute/dbinstance.go @@ -135,6 +135,9 @@ type DBInstanceCreateInput struct { // required: true DiskSizeGB int `json:"disk_size_gb"` + // 指定连接端口 + Port int `json:"port"` + // rds初始化密码 // 阿里云不需要此参数 // 华为云会默认创建一个用户,若不传此参数, 则为随机密码 @@ -154,6 +157,9 @@ type DBInstanceCreateInput struct { // rds实例内存大小 // 若指定实例套餐,此参数将根据套餐设置 VmemSizeMb int `json:"vmem_size_mb"` + + // 从备份中创建新实例 + DBInstancebackupId string `json:"dbinstancebackup_id"` } type SDBInstanceChangeConfigInput struct { diff --git a/pkg/apis/compute/elasticcache.go b/pkg/apis/compute/elasticcache.go index bddf7ab1a8..32c9b78bbc 100644 --- a/pkg/apis/compute/elasticcache.go +++ b/pkg/apis/compute/elasticcache.go @@ -32,6 +32,9 @@ type ElasticcacheDetails struct { // 关联安全组列表 Secgroups []apis.StandaloneShortDesc `json:"secgroups"` + + // 备可用区列表 + SlaveZoneInfos []apis.StandaloneShortDesc `json:"slave_zone_infos"` } type ElasticcacheResourceInfo struct { diff --git a/pkg/apis/compute/elasticcachesku.go b/pkg/apis/compute/elasticcachesku.go index f0356f9e14..2735425a64 100644 --- a/pkg/apis/compute/elasticcachesku.go +++ b/pkg/apis/compute/elasticcachesku.go @@ -20,6 +20,7 @@ type ElasticcacheSkuDetails struct { apis.StatusStandaloneResourceDetails CloudregionResourceInfo ZoneResourceInfoBase + SlaveZoneResourceInfoBase SElasticcacheSku } diff --git a/pkg/apis/compute/guest_const.go b/pkg/apis/compute/guest_const.go index b41e403164..50aeff048c 100644 --- a/pkg/apis/compute/guest_const.go +++ b/pkg/apis/compute/guest_const.go @@ -151,6 +151,7 @@ const ( HYPERVISOR_XEN = "xen" HYPERVISOR_ALIYUN = "aliyun" + HYPERVISOR_APSARA = "apsara" HYPERVISOR_QCLOUD = "qcloud" HYPERVISOR_AZURE = "azure" HYPERVISOR_AWS = "aws" @@ -184,6 +185,7 @@ var HYPERVISORS = []string{ HYPERVISOR_ESXI, HYPERVISOR_CONTAINER, HYPERVISOR_ALIYUN, + HYPERVISOR_APSARA, HYPERVISOR_AZURE, HYPERVISOR_AWS, HYPERVISOR_QCLOUD, @@ -215,6 +217,7 @@ var PUBLIC_CLOUD_HYPERVISORS = []string{ var PRIVATE_CLOUD_HYPERVISORS = []string{ HYPERVISOR_ZSTACK, HYPERVISOR_OPENSTACK, + HYPERVISOR_APSARA, } // var HYPERVISORS = []string{HYPERVISOR_ALIYUN} @@ -225,6 +228,7 @@ var HYPERVISOR_HOSTTYPE = map[string]string{ HYPERVISOR_ESXI: HOST_TYPE_ESXI, HYPERVISOR_CONTAINER: HOST_TYPE_KUBELET, HYPERVISOR_ALIYUN: HOST_TYPE_ALIYUN, + HYPERVISOR_APSARA: HOST_TYPE_APSARA, HYPERVISOR_AZURE: HOST_TYPE_AZURE, HYPERVISOR_AWS: HOST_TYPE_AWS, HYPERVISOR_QCLOUD: HOST_TYPE_QCLOUD, @@ -242,6 +246,7 @@ var HOSTTYPE_HYPERVISOR = map[string]string{ HOST_TYPE_ESXI: HYPERVISOR_ESXI, HOST_TYPE_KUBELET: HYPERVISOR_CONTAINER, HOST_TYPE_ALIYUN: HYPERVISOR_ALIYUN, + HOST_TYPE_APSARA: HYPERVISOR_APSARA, HOST_TYPE_AZURE: HYPERVISOR_AZURE, HOST_TYPE_AWS: HYPERVISOR_AWS, HOST_TYPE_QCLOUD: HYPERVISOR_QCLOUD, @@ -254,9 +259,11 @@ var HOSTTYPE_HYPERVISOR = map[string]string{ } const ( + VM_DEFAULT_WINDOWS_LOGIN_USER = "Administrator" + VM_DEFAULT_LINUX_LOGIN_USER = "root" VM_AWS_DEFAULT_LOGIN_USER = "ec2user" VM_AWS_DEFAULT_WINDOWS_LOGIN_USER = "Administrator" - VM_AZURE_DEFAULT_LOGIN_USER = "toor" + VM_AZURE_DEFAULT_LOGIN_USER = "azureuser" VM_ZSTACK_DEFAULT_LOGIN_USER = "root" VM_METADATA_APP_TAGS = "app_tags" diff --git a/pkg/apis/compute/guest_metadata.go b/pkg/apis/compute/guest_metadata.go new file mode 100644 index 0000000000..e6b29d7a7c --- /dev/null +++ b/pkg/apis/compute/guest_metadata.go @@ -0,0 +1,9 @@ +package compute + +const ( + MIRROR_JOB = "__mirror_job_status" + MIRROR_JOB_READY = "ready" + MIRROR_JOB_FAILED = "failed" +) + +const BASE_INSTANCE_SNAPSHOT_ID = "__base_instance_snapshot_id" diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 8f1d0ec590..c3dc465fba 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -438,3 +438,12 @@ type ServerResetInput struct { // 自动启动 AutoStart *bool `json:"auto_start"` } + +type ServerStopInput struct { + // 是否强制关机 + IsForce bool `json:"is_force"` + + // 是否关机停止计费, 若平台不支持停止计费,此参数无作用 + // 目前仅阿里云,腾讯云此参数生效 + StopCharging bool `json:"stop_charging"` +} diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go index 5db120eb99..e0a9637a1d 100644 --- a/pkg/apis/compute/host_const.go +++ b/pkg/apis/compute/host_const.go @@ -24,6 +24,7 @@ const ( HOST_TYPE_XEN = "xen" // # XenServer HOST_TYPE_ALIYUN = "aliyun" + HOST_TYPE_APSARA = "apsara" HOST_TYPE_AWS = "aws" HOST_TYPE_QCLOUD = "qcloud" HOST_TYPE_AZURE = "azure" @@ -102,6 +103,7 @@ var HOST_TYPES = []string{ HOST_TYPE_KUBELET, HOST_TYPE_XEN, HOST_TYPE_ALIYUN, + HOST_TYPE_APSARA, HOST_TYPE_AZURE, HOST_TYPE_AWS, HOST_TYPE_QCLOUD, diff --git a/pkg/apis/compute/inter_vpc_network.go b/pkg/apis/compute/inter_vpc_network.go new file mode 100644 index 0000000000..4aeb11c3d9 --- /dev/null +++ b/pkg/apis/compute/inter_vpc_network.go @@ -0,0 +1,73 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + INTER_VPC_NETWORK_STATUS_AVAILABLE = "available" + INTER_VPC_NETWORK_STATUS_CREATING = "creating" + INTER_VPC_NETWORK_STATUS_CREATE_FAILED = "create_failed" + INTER_VPC_NETWORK_STATUS_DELETE_FAILED = "delete_failed" + INTER_VPC_NETWORK_STATUS_DELETING = "deleting" + INTER_VPC_NETWORK_STATUS_ACTIVE = "active" + INTER_VPC_NETWORK_STATUS_ADDVPC = "add_vpc" + INTER_VPC_NETWORK_STATUS_ADDVPC_FAILED = "add_vpc_failed" + INTER_VPC_NETWORK_STATUS_REMOVEVPC = "remove_vpc" + INTER_VPC_NETWORK_STATUS_REMOVEVPC_FAILED = "remove_vpc_failed" + INTER_VPC_NETWORK_STATUS_UPDATEROUTE = "update_route" + INTER_VPC_NETWORK_STATUS_UPDATEROUTE_FAILED = "update_route_failed" + INTER_VPC_NETWORK_STATUS_UNKNOWN = "unknown" +) + +type InterVpcNetworkListInput struct { + apis.EnabledStatusInfrasResourceBaseListInput + ManagedResourceListInput +} + +type InterVpcNetworkCreateInput struct { + apis.EnabledStatusInfrasResourceBaseCreateInput + ManagerId string `json:"manager_id"` +} + +type InterVpcNetworkUpdateInput struct { + apis.EnabledStatusInfrasResourceBaseUpdateInput +} + +type InterVpcNetworkDetails struct { + apis.EnabledStatusInfrasResourceBaseDetails + ManagedResourceInfo + VpcCount int `json:"vpc_count"` +} + +type InterVpcNetworkSyncstatusInput struct { +} + +type InterVpcNetworkAddVpcInput struct { + VpcId string +} + +type InterVpcNetworkRemoveVpcInput struct { + VpcId string +} + +type InterVpcNetworkFilterListBase struct { + InterVpcNetworkId string `json:"inter_vpc_network_id"` +} + +type InterVpcNetworkManagerListInput struct { + apis.EnabledStatusInfrasResourceBaseListInput + ManagedResourceListInput +} diff --git a/pkg/apis/compute/inter_vpc_network_routeset.go b/pkg/apis/compute/inter_vpc_network_routeset.go new file mode 100644 index 0000000000..378159cd79 --- /dev/null +++ b/pkg/apis/compute/inter_vpc_network_routeset.go @@ -0,0 +1,42 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + INTER_VPCNETWORK_ATTACHED_INSTAMCE_TYPE_VPC = "VPC" + INTER_VPCNETWORK_ATTACHED_INSTAMCE_TYPE_VBR = "VBR" // 边界路由器 +) + +type InterVpcNetworkRouteSetEnableInput struct { + apis.PerformEnableInput +} + +type InterVpcNetworkRouteSetDisableInput struct { + apis.PerformDisableInput +} + +type InterVpcNetworkRouteSetDetails struct { + apis.EnabledStatusStandaloneResourceDetails + VpcResourceInfo +} + +type InterVpcNetworkRouteSetListInput struct { + apis.EnabledStatusStandaloneResourceListInput + apis.ExternalizedResourceBaseListInput + VpcFilterListInput + InterVpcNetworkId string +} diff --git a/pkg/apis/compute/routetable_routesets.go b/pkg/apis/compute/routetable_routesets.go index 98c6225464..1a5f128c39 100644 --- a/pkg/apis/compute/routetable_routesets.go +++ b/pkg/apis/compute/routetable_routesets.go @@ -36,10 +36,14 @@ const ( Next_HOP_TYPE_VPCPEERING = "VpcPeering" // vpc对等连接 Next_HOP_TYPE_INTERVPCNETWORK = "InterVpcNetwork" //vpc 互联网络 Next_HOP_TYPE_DIRECTCONNECTION = "DirectConnection" //专线 + Next_HOP_TYPE_VPC = "VPC" + Next_HOP_TYPE_VBR = "VBR" // 边界路由器 ) const ( ROUTE_ENTRY_STATUS_AVAILIABLE = "available" + ROUTE_ENTRY_STATUS_CONFLICT = "conflict" + ROUTE_ENTRY_STATUS_DISABLED = "disabled" ROUTE_ENTRY_STATUS_UNKNOWN = "unknown" ) diff --git a/pkg/apis/compute/schedtag.go b/pkg/apis/compute/schedtag.go index 6e0edb2e39..d326c64f95 100644 --- a/pkg/apis/compute/schedtag.go +++ b/pkg/apis/compute/schedtag.go @@ -109,3 +109,7 @@ type SchedtagJointsListInput struct { apis.JointResourceBaseListInput SchedtagFilterListInput } + +type SchedtagSetResourceInput struct { + ResourceIds []string `json:"resource_ids"` +} diff --git a/pkg/apis/compute/storage_const.go b/pkg/apis/compute/storage_const.go index 5668d8a665..9ed4f15e4c 100644 --- a/pkg/apis/compute/storage_const.go +++ b/pkg/apis/compute/storage_const.go @@ -28,6 +28,7 @@ const ( STORAGE_VSAN = "vsan" STORAGE_NFS = "nfs" STORAGE_GPFS = "gpfs" + STORAGE_CIFS = "cifs" STORAGE_PUBLIC_CLOUD = "cloud" STORAGE_CLOUD_EFFICIENCY = "cloud_efficiency" @@ -45,6 +46,7 @@ const ( // aws storage type STORAGE_GP2_SSD = "gp2" // aws general purpose ssd STORAGE_IO1_SSD = "io1" // aws Provisioned IOPS SSD + STORAGE_IO2_SSD = "io2" // aws Provisioned IOPS 2 SSD STORAGE_ST1_HDD = "st1" // aws Throughput Optimized HDD STORAGE_SC1_HDD = "sc1" // aws Cold HDD STORAGE_STANDARD_HDD = "standard" // aws Magnetic volumes @@ -116,7 +118,7 @@ var ( STORAGE_ALL_TYPES = []string{ STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_SHEEPDOG, STORAGE_RBD, STORAGE_DOCKER, STORAGE_NAS, STORAGE_VSAN, - STORAGE_NFS, STORAGE_GPFS, + STORAGE_NFS, STORAGE_GPFS, STORAGE_CIFS, } STORAGE_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_SHEEPDOG, STORAGE_RBD, STORAGE_DOCKER, STORAGE_NAS, STORAGE_VSAN, STORAGE_NFS, @@ -128,12 +130,12 @@ var ( STORAGE_HUAWEI_SSD, STORAGE_HUAWEI_SAS, STORAGE_HUAWEI_SATA, STORAGE_OPENSTACK_ISCSI, STORAGE_UCLOUD_CLOUD_NORMAL, STORAGE_UCLOUD_CLOUD_SSD, STORAGE_UCLOUD_LOCAL_NORMAL, STORAGE_UCLOUD_LOCAL_SSD, STORAGE_UCLOUD_EXCLUSIVE_LOCAL_DISK, - STORAGE_ZSTACK_LOCAL_STORAGE, STORAGE_ZSTACK_CEPH, STORAGE_GPFS, + STORAGE_ZSTACK_LOCAL_STORAGE, STORAGE_ZSTACK_CEPH, STORAGE_GPFS, STORAGE_CIFS, } HOST_STORAGE_LOCAL_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_ZSTACK_LOCAL_STORAGE, STORAGE_OPENSTACK_NOVA} - STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS, STORAGE_GPFS} + STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS, STORAGE_GPFS, STORAGE_VSAN, STORAGE_CIFS} SHARED_FILE_STORAGE = []string{STORAGE_NFS, STORAGE_GPFS} FIEL_STORAGE = []string{STORAGE_LOCAL, STORAGE_NFS, STORAGE_GPFS} diff --git a/pkg/apis/compute/vpcs_const.go b/pkg/apis/compute/vpcs_const.go index 99c1b11f0b..e676e62f6d 100644 --- a/pkg/apis/compute/vpcs_const.go +++ b/pkg/apis/compute/vpcs_const.go @@ -55,6 +55,8 @@ type VpcListInput struct { DnsZoneFilterListBase + InterVpcNetworkFilterListBase + UsableResourceListInput UsableVpcResourceListInput diff --git a/pkg/apis/compute/zone.go b/pkg/apis/compute/zone.go index 47b727858b..35f65c121c 100644 --- a/pkg/apis/compute/zone.go +++ b/pkg/apis/compute/zone.go @@ -104,6 +104,15 @@ type Zone1ResourceInfoBase struct { Zone1ExtId string `json:"zone_1_ext_id"` } +type SlaveZoneResourceInfoBase struct { + // 可用区名称 + // example: 北京2区 + SlaveZone string `json:"slave_zone"` + + // 纳管云的zoneId + SlaveZoneExtId string `json:"slave_zone_ext_id"` +} + type ZoneResourceInfo struct { ZoneResourceInfoBase diff --git a/pkg/apis/identity/input.go b/pkg/apis/identity/input.go index 3fe9528091..e54f3a8b2b 100644 --- a/pkg/apis/identity/input.go +++ b/pkg/apis/identity/input.go @@ -167,6 +167,9 @@ type DomainListInput struct { // 按IDP过滤 IdpId string `json:"idp_id"` + + // 按IDP_ENTITY_ID过滤 + IdpEntityId string `json:"idp_entity_id"` } type UserListInput struct { @@ -191,6 +194,9 @@ type UserListInput struct { // 关联IDP IdpId string `json:"idp_id"` + + // 按IDP_ENTITY_ID过滤 + IdpEntityId string `json:"idp_entity_id"` } type EndpointListInput struct { diff --git a/pkg/apis/monitor/alertrecord.go b/pkg/apis/monitor/alertrecord.go index c76ca36313..1d5b0f5991 100644 --- a/pkg/apis/monitor/alertrecord.go +++ b/pkg/apis/monitor/alertrecord.go @@ -34,7 +34,9 @@ type AlertRecordCreateInput struct { type AlertRecordRule struct { Metric string `json:"metric"` + Measurement string `json:"measurement"` MeasurementDesc string `json:"measurement_desc"` + Field string `json:"field"` FieldDesc string `json:"field_desc"` // 比较运算符, 比如: >, <, >=, <= Comparator string `json:"comparator"` diff --git a/pkg/apis/monitor/suggestsysalert.go b/pkg/apis/monitor/suggestsysalert.go index 840bd8d1a3..b67b978831 100644 --- a/pkg/apis/monitor/suggestsysalert.go +++ b/pkg/apis/monitor/suggestsysalert.go @@ -56,6 +56,7 @@ type SuggestSysAlertDetails struct { Suggest string `json:"suggest"` Brand string `json:"brand"` Account string `json:"account"` + ResName string `json:"res_name"` } type SuggestSysAlertUpdateInput struct { @@ -76,6 +77,7 @@ type SuggestSysAlertUpdateInput struct { type SuggestAlertIngoreInput struct { apis.ScopedResourceCreateInput + BatchIgnore bool `json:"batch_ignore"` } type SuggestAlertProblem struct { diff --git a/pkg/apis/monitor/suggestsysrule.go b/pkg/apis/monitor/suggestsysrule.go index 70d26b819e..cf5dd169e0 100644 --- a/pkg/apis/monitor/suggestsysrule.go +++ b/pkg/apis/monitor/suggestsysrule.go @@ -58,27 +58,30 @@ type SuggestSysRuleCreateInput struct { apis.StandaloneResourceCreateInput // 查询指标周期 - Period string `json:"period"` - TimeFrom string `json:"time_from"` - Type string `json:"type"` - Enabled *bool `json:"enabled"` - Setting *SSuggestSysAlertSetting `json:"setting"` + Period string `json:"period"` + TimeFrom string `json:"time_from"` + Type string `json:"type"` + Enabled *bool `json:"enabled"` + Setting *SSuggestSysAlertSetting `json:"setting"` + IgnoreTimeFrom *bool `json:"ignore_time_from"` } type SuggestSysRuleUpdateInput struct { apis.Meta // 查询指标周期 - Period string `json:"period"` - Name string `json:"name"` - Type string `json:"type"` - Setting *SSuggestSysAlertSetting `json:"setting"` - Enabled *bool `json:"enabled"` - ExecTime time.Time `json:"exec_time"` + Period string `json:"period"` + Name string `json:"name"` + Type string `json:"type"` + Setting *SSuggestSysAlertSetting `json:"setting"` + Enabled *bool `json:"enabled"` + ExecTime time.Time `json:"exec_time"` + IgnorePeriod *bool `json:"ignore_period"` } type SuggestSysRuleDetails struct { apis.StandaloneResourceDetails + CommonAlertMetricDetails []*CommonAlertMetricDetails `json:"common_alert_metric_details"` ID string `json:"id"` Name string `json:"name"` diff --git a/pkg/apis/monitor/suggestsysruleconfig.go b/pkg/apis/monitor/suggestsysruleconfig.go index 0726d85e43..2dc7ac3474 100644 --- a/pkg/apis/monitor/suggestsysruleconfig.go +++ b/pkg/apis/monitor/suggestsysruleconfig.go @@ -53,6 +53,7 @@ type SuggestSysRuleConfigDetails struct { RuleId string `json:"rule_id"` Rule string `json:"rule"` RuleEnabled bool `json:"rule_enabled"` + ResName string `json:"res_name"` } type SuggestSysRuleConfigListInput struct { @@ -62,3 +63,7 @@ type SuggestSysRuleConfigListInput struct { ResourceType *MonitorResourceType `json:"resource_type"` IgnoreAlert *bool `json:"ignore_alert"` } + +type SuggestSysRuleConfigTypeInfo struct { + Name string `json:"name"` +} diff --git a/pkg/cloudcommon/db/jointbase.go b/pkg/cloudcommon/db/jointbase.go index ed69d7841b..7d8d063d15 100644 --- a/pkg/cloudcommon/db/jointbase.go +++ b/pkg/cloudcommon/db/jointbase.go @@ -34,7 +34,7 @@ import ( type SJointResourceBase struct { SResourceBase - RowId int64 `primary:"true" auto_increment:"true"` + RowId int64 `primary:"true" auto_increment:"true" list:"user"` } type SJointResourceBaseManager struct { diff --git a/pkg/cloudcommon/db/opslog_const.go b/pkg/cloudcommon/db/opslog_const.go index 6efc174e08..858ca11020 100644 --- a/pkg/cloudcommon/db/opslog_const.go +++ b/pkg/cloudcommon/db/opslog_const.go @@ -265,4 +265,11 @@ const ( ACT_SYNC_VPCS = "sync_vpcs" ACT_SYNC_RECORD_SETS = "sync_record_sets" + + ACT_NETWORK_ADD_VPC = "network_add_vpc" + ACT_NETWORK_ADD_VPC_FAILED = "network_add_vpc_failed" + ACT_NETWORK_REMOVE_VPC = "network_remove_vpc" + ACT_NETWORK_REMOVE_VPC_FAILED = "network_remove_vpc_failed" + ACT_NETWORK_MODIFY_ROUTE = "network_modify_route" + ACT_NETWORK_MODIFY_ROUTE_FAILED = "network_modify_route_failed" ) diff --git a/pkg/cloudcommon/notifyclient/notify.go b/pkg/cloudcommon/notifyclient/notify.go index d5ec46313d..1c23a1fec2 100644 --- a/pkg/cloudcommon/notifyclient/notify.go +++ b/pkg/cloudcommon/notifyclient/notify.go @@ -91,7 +91,9 @@ func getTemplate(ctx context.Context, topic string, contType string, channel npk if err != nil { return nil, err } - tmp, err := template.New(key).Parse(string(cont)) + tmp := template.New(key) + tmp.Funcs(template.FuncMap{"unescaped": unescaped}) + tmp, err = tmp.Parse(string(cont)) if err != nil { return nil, err } @@ -100,6 +102,10 @@ func getTemplate(ctx context.Context, topic string, contType string, channel npk return templatesTable[key], nil } +func unescaped(str string) template.HTML { + return template.HTML(str) +} + func getContent(ctx context.Context, topic string, contType string, channel npk.TNotifyChannel, data jsonutils.JSONObject) (string, error) { if channel == npk.NotifyByWebhook { return "", nil diff --git a/pkg/cloudevent/models/cloudevents.go b/pkg/cloudevent/models/cloudevents.go index 5d7483675c..298c604d36 100644 --- a/pkg/cloudevent/models/cloudevents.go +++ b/pkg/cloudevent/models/cloudevents.go @@ -27,10 +27,13 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" ) type SCloudeventManager struct { db.SModelBaseManager + db.SProjectizedResourceBaseManager } var CloudeventManager *SCloudeventManager @@ -49,6 +52,7 @@ func init() { type SCloudevent struct { db.SModelBase + db.SProjectizedResourceBase EventId int64 `primary:"true" auto_increment:"true" list:"user"` Name string `width:"128" charset:"utf8" nullable:"false" index:"true" list:"user"` @@ -125,6 +129,67 @@ func (manager *SCloudeventManager) ListItemFilter( return q, nil } +func (self *SCloudevent) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudeventDetails, error) { + return api.CloudeventDetails{}, nil +} + +func (manager *SCloudeventManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudeventDetails { + rows := make([]api.CloudeventDetails, len(objs)) + base := manager.SModelBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + projRows := manager.SProjectizedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i].ModelBaseDetails = base[i] + rows[i].ProjectizedResourceInfo = projRows[i] + } + return rows +} + +func (manager *SCloudeventManager) ResourceScope() rbacutils.TRbacScope { + return rbacutils.ScopeProject +} + +func (self *SCloudevent) GetOwnerId() mcclient.IIdentityProvider { + owner := db.SOwnerId{DomainId: self.DomainId, ProjectId: self.ProjectId} + return &owner +} + +func (manager *SCloudeventManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + return manager.SProjectizedResourceBaseManager.FilterByOwner(q, owner, scope) +} + +func (manager *SCloudeventManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { + return manager.SProjectizedResourceBaseManager.FetchOwnerId(ctx, data) +} + +func (manager *SCloudeventManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) { + return manager.SProjectizedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) +} + +func (manager *SCloudeventManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + return manager.SProjectizedResourceBaseManager.QueryDistinctExtraField(q, field) +} + +func (manager *SCloudeventManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudeventListInput, +) (*sqlchemy.SQuery, error) { + return manager.SProjectizedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ProjectizedResourceListInput) +} + func (manager *SCloudeventManager) SyncCloudevent(ctx context.Context, userCred mcclient.TokenCredential, cloudprovider *SCloudprovider, iEvents []cloudprovider.ICloudEvent) int { count := 0 for _, iEvent := range iEvents { @@ -142,6 +207,8 @@ func (manager *SCloudeventManager) SyncCloudevent(ctx context.Context, userCred Brand: cloudprovider.Brand, CloudproviderId: cloudprovider.Id, } + event.DomainId = cloudprovider.DomainId + event.ProjectId = cloudprovider.ProjectId if len(event.Brand) == 0 { event.Brand = event.Provider } diff --git a/pkg/cloudevent/models/cloudproviders.go b/pkg/cloudevent/models/cloudproviders.go index e58f2d55c4..1190319ea4 100644 --- a/pkg/cloudevent/models/cloudproviders.go +++ b/pkg/cloudevent/models/cloudproviders.go @@ -28,6 +28,7 @@ import ( "yunion.io/x/pkg/util/compare" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" proxyapi "yunion.io/x/onecloud/pkg/apis/cloudcommon/proxy" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -35,6 +36,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudevent/options" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" @@ -44,6 +46,7 @@ import ( type SCloudproviderManager struct { db.SEnabledStatusStandaloneResourceBaseManager + db.SProjectizedResourceBaseManager } var CloudproviderManager *SCloudproviderManager @@ -62,6 +65,7 @@ func init() { type SCloudprovider struct { db.SEnabledStatusStandaloneResourceBase + db.SProjectizedResourceBase SyncStatus string LastSync time.Time @@ -111,6 +115,17 @@ func (manager *SCloudproviderManager) GetLocalCloudproviders() ([]SCloudprovider return dbProviders, nil } +func (manager *SCloudproviderManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + func (manager *SCloudproviderManager) syncCloudproviders(ctx context.Context, userCred mcclient.TokenCredential) compare.SyncResult { result := compare.SyncResult{} providers, err := manager.GetRegionCloudproviders(ctx, userCred) @@ -176,6 +191,8 @@ func (provider *SCloudprovider) syncWithRegionProvider(ctx context.Context, user provider.Status = cloudprovider.Status provider.Enabled = cloudprovider.Enabled provider.Brand = cloudprovider.Brand + provider.ProjectId = cloudprovider.ProjectId + provider.DomainId = cloudprovider.DomainId return nil }) return err diff --git a/pkg/cloudprovider/cdn.go b/pkg/cloudprovider/cdn.go index 82fbe5b555..474a35f451 100644 --- a/pkg/cloudprovider/cdn.go +++ b/pkg/cloudprovider/cdn.go @@ -17,6 +17,8 @@ package cloudprovider type SCdnDomain struct { // cdn加速域名 Domain string + // 状态 rejected(域名未审核)|processing(部署中)|online|offline + Status string // 区域 mainland|overseas|global Area string // cdn Cname diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index 530aea6acd..e90c507b66 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -94,6 +94,9 @@ type SCloudaccountCredential struct { GCPPrivateKeyId string `json:"gcp_private_key_id"` // Google服务账号秘钥 (gcp) GCPPrivateKey string `json:"gcp_private_key"` + + // 阿里云专有云Endpoints + *SApsaraEndpoints } type SCloudaccount struct { @@ -155,6 +158,8 @@ type ProviderConfig struct { AccountId string + SApsaraEndpoints + ProxyFunc httputils.TransportProxyFunc } @@ -285,6 +290,10 @@ type ICloudProvider interface { GetICloudDnsZones() ([]ICloudDnsZone, error) GetICloudDnsZoneById(id string) (ICloudDnsZone, error) CreateICloudDnsZone(opts *SDnsZoneCreateOptions) (ICloudDnsZone, error) + + GetICloudInterVpcNetworks() ([]ICloudInterVpcNetwork, error) + GetICloudInterVpcNetworkById(id string) (ICloudInterVpcNetwork, error) + CreateICloudInterVpcNetwork(opts *SInterVpcNetworkCreateOptions) (ICloudInterVpcNetwork, error) } func IsSupportProject(prod ICloudProvider) bool { @@ -295,6 +304,10 @@ func IsSupportDnsZone(prod ICloudProvider) bool { return utils.IsInStringArray(CLOUD_CAPABILITY_DNSZONE, prod.GetCapabilities()) } +func IsSupportInterVpcNetwork(prod ICloudProvider) bool { + return utils.IsInStringArray(CLOUD_CAPABILITY_INTERVPCNETWORK, prod.GetCapabilities()) +} + func IsSupportCompute(prod ICloudProvider) bool { return utils.IsInStringArray(CLOUD_CAPABILITY_COMPUTE, prod.GetCapabilities()) } @@ -497,6 +510,20 @@ func (self *SBaseProvider) GetSamlEntityId() string { return "" } +func (self *SBaseProvider) GetSamlSpInitiatedLoginUrl(idpName string) string { + return "" +} + +func (self *SBaseProvider) GetICloudInterVpcNetworks() ([]ICloudInterVpcNetwork, error) { + return nil, ErrNotImplemented +} +func (self *SBaseProvider) GetICloudInterVpcNetworkById(id string) (ICloudInterVpcNetwork, error) { + return nil, ErrNotImplemented +} +func (self *SBaseProvider) CreateICloudInterVpcNetwork(opts *SInterVpcNetworkCreateOptions) (ICloudInterVpcNetwork, error) { + return nil, ErrNotImplemented +} + func NewBaseProvider(factory ICloudProviderFactory) SBaseProvider { return SBaseProvider{factory: factory} } diff --git a/pkg/cloudprovider/consts.go b/pkg/cloudprovider/consts.go index c94b5c1035..8ee16254f1 100644 --- a/pkg/cloudprovider/consts.go +++ b/pkg/cloudprovider/consts.go @@ -41,17 +41,19 @@ const ( ) const ( - CLOUD_CAPABILITY_PROJECT = "project" - CLOUD_CAPABILITY_COMPUTE = "compute" - CLOUD_CAPABILITY_NETWORK = "network" - CLOUD_CAPABILITY_LOADBALANCER = "loadbalancer" - CLOUD_CAPABILITY_OBJECTSTORE = "objectstore" - CLOUD_CAPABILITY_RDS = "rds" - CLOUD_CAPABILITY_CACHE = "cache" // 弹性缓存包含redis、memcached - CLOUD_CAPABILITY_EVENT = "event" - CLOUD_CAPABILITY_CLOUDID = "cloudid" - CLOUD_CAPABILITY_DNSZONE = "dnszone" - CLOUD_CAPABILITY_PUBLIC_IP = "public_ip" + CLOUD_CAPABILITY_PROJECT = "project" + CLOUD_CAPABILITY_COMPUTE = "compute" + CLOUD_CAPABILITY_NETWORK = "network" + CLOUD_CAPABILITY_LOADBALANCER = "loadbalancer" + CLOUD_CAPABILITY_OBJECTSTORE = "objectstore" + CLOUD_CAPABILITY_RDS = "rds" + CLOUD_CAPABILITY_CACHE = "cache" // 弹性缓存包含redis、memcached + CLOUD_CAPABILITY_EVENT = "event" + CLOUD_CAPABILITY_CLOUDID = "cloudid" + CLOUD_CAPABILITY_DNSZONE = "dnszone" + CLOUD_CAPABILITY_PUBLIC_IP = "public_ip" + CLOUD_CAPABILITY_INTERVPCNETWORK = "intervpcnetwork" + CLOUD_CAPABILITY_SAML_AUTH = "saml_auth" // 是否支持SAML 2.0 ) const ( diff --git a/pkg/cloudprovider/dbinstance.go b/pkg/cloudprovider/dbinstance.go index 894e7e35a5..9f5d1f4154 100644 --- a/pkg/cloudprovider/dbinstance.go +++ b/pkg/cloudprovider/dbinstance.go @@ -16,6 +16,14 @@ package cloudprovider import "yunion.io/x/onecloud/pkg/util/billing" +type TBackupMethod string + +const ( + BackupMethodLogical = TBackupMethod("Logical") + BackupMethodPhysical = TBackupMethod("Physical") + BackupMethodUnknown = TBackupMethod("") +) + type SDBInstanceNetwork struct { IP string NetworkId string @@ -61,6 +69,9 @@ type SManagedDBInstanceCreateConfig struct { BillingCycle *billing.SBillingCycle Tags map[string]string + + // 仅从备份恢复到新实例用到 + RdsId string } type SManagedDBInstanceChangeConfig struct { diff --git a/pkg/cloudprovider/endpoints.go b/pkg/cloudprovider/endpoints.go new file mode 100644 index 0000000000..5f51a52717 --- /dev/null +++ b/pkg/cloudprovider/endpoints.go @@ -0,0 +1,29 @@ +// 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 cloudprovider + +type SApsaraEndpoints struct { + EcsEndpoint string `default:"$APSARA_ECS_ENDPOINT" metavar:"APSARA_ECS_ENDPOINT"` + RdsEndpoint string `default:"$APSARA_RDS_ENDPOINT"` + VpcEndpoint string `default:"$APSARA_VPC_ENDPOINT"` + KvsEndpoint string `default:"$APSARA_KVS_ENDPOINT"` + SlbEndpoint string `default:"$APSARA_SLB_ENDPOINT"` + OssEndpoint string `default:"$APSARA_OSS_ENDPOINT"` + StsEndpoint string `default:"$APSARA_STS_ENDPOINT"` + ActionTrailEndpoint string `default:"$APSARA_ACTION_TRAIL_ENDPOINT"` + RamEndpoint string `default:"$APSARA_RAM_ENDPOINT"` + MetricsEndpoint string `default:"$APSRRA_METRICS_ENDPOINT"` + ResourcemanagerEndpoint string `default:"$APSARA_RESOURCEMANAGER_ENDPOINT"` +} diff --git a/pkg/cloudprovider/instance.go b/pkg/cloudprovider/instance.go index 862ba6264f..4fa257d42d 100644 --- a/pkg/cloudprovider/instance.go +++ b/pkg/cloudprovider/instance.go @@ -26,6 +26,38 @@ import ( "yunion.io/x/onecloud/pkg/util/seclib2" ) +type SDistDefaultAccount struct { + // 操作系统发行版 + OsDistribution string + // 默认用户名 + DefaultAccount string + // 是否可更改 + Changeable bool +} + +type SOsDefaultAccount struct { + // 默认用户名 + DefaultAccount string + // 是否可更改用户名 + Changeable bool + // 禁止使用的账号 + DisabledAccounts []string + // 各操作系统发行版的默认用户名信息 + DistAccounts []SDistDefaultAccount +} + +type SDefaultAccount struct { + Linux SOsDefaultAccount + Windows SOsDefaultAccount +} + +type SInstanceCapability struct { + Provider string + Hypervisor string + + DefaultAccount SDefaultAccount +} + type SDiskInfo struct { StorageExternalId string StorageType string @@ -46,6 +78,11 @@ type SPublicIpInfo struct { PublicIpChargeType TElasticipChargeType } +type ServerStopOptions struct { + IsForce bool + StopCharging bool +} + type SManagedVMCreateConfig struct { Name string ExternalImageId string diff --git a/pkg/cloudprovider/inter_vpc_network.go b/pkg/cloudprovider/inter_vpc_network.go new file mode 100644 index 0000000000..63fee59bc7 --- /dev/null +++ b/pkg/cloudprovider/inter_vpc_network.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 cloudprovider + +type SInterVpcNetworkAttachVpcOption struct { + VpcId string + VpcRegionId string + VpcAuthorityOwnerId string +} + +type SInterVpcNetworkDetachVpcOption struct { + VpcId string + VpcRegionId string + VpcAuthorityOwnerId string +} + +type SVpcJointInterVpcNetworkOption struct { + InterVpcNetworkId string + NetworkAuthorityOwnerId string +} + +type SInterVpcNetworkCreateOptions struct { + Name string + Desc string +} diff --git a/pkg/cloudprovider/objectstore.go b/pkg/cloudprovider/objectstore.go index 5909d7b07f..30263d4720 100644 --- a/pkg/cloudprovider/objectstore.go +++ b/pkg/cloudprovider/objectstore.go @@ -105,6 +105,8 @@ type SBucketCORSRule struct { AllowedHeaders []string MaxAgeSeconds int ExposeHeaders []string + // 规则区别标识 + Id string } type SBucketRefererConf struct { @@ -216,7 +218,7 @@ type ICloudBucket interface { SetCORS(rules []SBucketCORSRule) error GetCORSRules() ([]SBucketCORSRule, error) - DeleteCORS() error + DeleteCORS(id []string) error SetReferer(conf SBucketRefererConf) error GetReferer() (SBucketRefererConf, error) diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 25cf6eb0b1..57910bda2c 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -294,7 +294,7 @@ type ICloudVM interface { // GetSecurityGroup() ICloudSecurityGroup StartVM(ctx context.Context) error - StopVM(ctx context.Context, isForce bool) error + StopVM(ctx context.Context, opts *ServerStopOptions) error DeleteVM(ctx context.Context) error UpdateVM(ctx context.Context, name string) error @@ -417,7 +417,6 @@ type ICloudDisk interface { Delete(ctx context.Context) error CreateISnapshot(ctx context.Context, name string, desc string) (ICloudSnapshot, error) - GetISnapshot(idStr string) (ICloudSnapshot, error) GetISnapshots() ([]ICloudSnapshot, error) GetExtSnapshotPolicyIds() ([]string, error) @@ -476,7 +475,10 @@ type ICloudVpc interface { GetICloudVpcPeeringConnectionById(id string) (ICloudVpcPeeringConnection, error) CreateICloudVpcPeeringConnection(opts *VpcPeeringConnectionCreateOptions) (ICloudVpcPeeringConnection, error) AcceptICloudVpcPeeringConnection(id string) error + GetAuthorityOwnerId() string + + ProposeJoinICloudInterVpcNetwork(opts *SVpcJointInterVpcNetworkOption) error } type ICloudWire interface { @@ -849,6 +851,9 @@ type ICloudDBInstanceBackup interface { GetBackupSizeMb() int GetDBNames() string GetBackupMode() string + GetBackupMethod() TBackupMethod + + CreateICloudDBInstance(opts *SManagedDBInstanceCreateConfig) (ICloudDBInstance, error) Delete() error } @@ -1127,3 +1132,25 @@ type ICloudrole interface { Delete() error } + +type ICloudInterVpcNetwork interface { + ICloudResource + GetAuthorityOwnerId() string + GetICloudVpcIds() ([]string, error) + AttachVpc(opts *SInterVpcNetworkAttachVpcOption) error + DetachVpc(opts *SInterVpcNetworkDetachVpcOption) error + Delete() error + GetIRoutes() ([]ICloudInterVpcNetworkRoute, error) + EnableRouteEntry(routeId string) error + DisableRouteEntry(routeId string) error +} + +type ICloudInterVpcNetworkRoute interface { + ICloudResource + GetInstanceId() string + GetInstanceType() string + GetInstanceRegionId() string + + GetEnabled() bool + GetCidr() string +} diff --git a/pkg/compute/guestdrivers/aliyun.go b/pkg/compute/guestdrivers/aliyun.go index bae7781d65..7362adb5db 100644 --- a/pkg/compute/guestdrivers/aliyun.go +++ b/pkg/compute/guestdrivers/aliyun.go @@ -164,6 +164,23 @@ func (self *SAliyunGuestDriver) GetGuestInitialStateAfterRebuild() string { return api.VM_READY } +func (self *SAliyunGuestDriver) 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 *SAliyunGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { userName := "root" if desc.OsType == "Windows" { diff --git a/pkg/compute/guestdrivers/apsara.go b/pkg/compute/guestdrivers/apsara.go new file mode 100644 index 0000000000..42828f8537 --- /dev/null +++ b/pkg/compute/guestdrivers/apsara.go @@ -0,0 +1,84 @@ +// 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 guestdrivers + +import ( + 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/billing" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SApsaraGuestDriver struct { + SAliyunGuestDriver +} + +func init() { + driver := SApsaraGuestDriver{} + models.RegisterGuestDriver(&driver) +} + +func (self *SApsaraGuestDriver) GetHypervisor() string { + return api.HYPERVISOR_APSARA +} + +func (self *SApsaraGuestDriver) GetProvider() string { + return api.CLOUD_PROVIDER_APSARA +} + +func (self *SApsaraGuestDriver) 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_PRIVATE_CLOUD + keys.Provider = api.CLOUD_PROVIDER_APSARA + keys.Brand = api.CLOUD_PROVIDER_APSARA + keys.Hypervisor = api.HYPERVISOR_APSARA + return keys +} + +func (self *SApsaraGuestDriver) GetGuestInitialStateAfterCreate() string { + return api.VM_READY +} + +func (self *SApsaraGuestDriver) GetGuestInitialStateAfterRebuild() string { + return api.VM_READY +} + +func (self *SApsaraGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { + userName := "root" + if desc.OsType == "Windows" { + userName = "Administrator" + } + return userName +} + +func (self *SApsaraGuestDriver) AllowReconfigGuest() bool { + return true +} + +func (self *SApsaraGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { + return false +} + +func (self *SApsaraGuestDriver) IsSupportPublicipToEip() bool { + return false +} + +func (self *SApsaraGuestDriver) IsSupportSetAutoRenew() bool { + return false +} diff --git a/pkg/compute/guestdrivers/aws.go b/pkg/compute/guestdrivers/aws.go index 25775c6346..a04fca0f08 100644 --- a/pkg/compute/guestdrivers/aws.go +++ b/pkg/compute/guestdrivers/aws.go @@ -86,6 +86,23 @@ func (self *SAwsGuestDriver) GetWindowsUserDataType() string { return cloudprovider.CLOUD_EC2 } +func (self *SAwsGuestDriver) 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 *SAwsGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { // return fetchAwsUserName(desc) if desc.OsType == "Windows" { @@ -125,6 +142,7 @@ func (self *SAwsGuestDriver) GetStorageTypes() []string { return []string{ api.STORAGE_GP2_SSD, api.STORAGE_IO1_SSD, + api.STORAGE_IO2_SSD, api.STORAGE_ST1_HDD, api.STORAGE_SC1_HDD, api.STORAGE_STANDARD_HDD, @@ -181,10 +199,10 @@ func (self *SAwsGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *mode if !utils.IsInStringArray(guest.Status, []string{api.VM_RUNNING, api.VM_READY}) { return fmt.Errorf("Cannot resize disk when guest in status %s", guest.Status) } - if disk.DiskType == api.DISK_TYPE_SYS && !utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_IO1_SSD, api.STORAGE_STANDARD_HDD, api.STORAGE_GP2_SSD}) { + if disk.DiskType == api.DISK_TYPE_SYS && !utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_IO1_SSD, api.STORAGE_IO2_SSD, api.STORAGE_STANDARD_HDD, api.STORAGE_GP2_SSD}) { return fmt.Errorf("Cannot resize system disk with unsupported volumes type %s", storage.StorageType) } - if !utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_GP2_SSD, api.STORAGE_IO1_SSD, api.STORAGE_ST1_HDD, api.STORAGE_SC1_HDD, api.STORAGE_STANDARD_HDD}) { + if !utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_GP2_SSD, api.STORAGE_IO1_SSD, api.STORAGE_IO2_SSD, api.STORAGE_ST1_HDD, api.STORAGE_SC1_HDD, api.STORAGE_STANDARD_HDD}) { return fmt.Errorf("Cannot resize %s disk", storage.StorageType) } return nil diff --git a/pkg/compute/guestdrivers/azure.go b/pkg/compute/guestdrivers/azure.go index deda893f41..d7871ec696 100644 --- a/pkg/compute/guestdrivers/azure.go +++ b/pkg/compute/guestdrivers/azure.go @@ -222,6 +222,23 @@ func (self *SAzureGuestDriver) GetGuestInitialStateAfterRebuild() string { return api.VM_READY } +func (self *SAzureGuestDriver) GetInstanceCapability() cloudprovider.SInstanceCapability { + return cloudprovider.SInstanceCapability{ + Hypervisor: self.GetHypervisor(), + Provider: self.GetProvider(), + DefaultAccount: cloudprovider.SDefaultAccount{ + Linux: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_AZURE_DEFAULT_LOGIN_USER, + Changeable: false, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + Changeable: false, + }, + }, + } +} + func (self *SAzureGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { return api.VM_AZURE_DEFAULT_LOGIN_USER } diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go index d1c2d4fb9f..51e6cea801 100644 --- a/pkg/compute/guestdrivers/baremetals.go +++ b/pkg/compute/guestdrivers/baremetals.go @@ -30,6 +30,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/baremetal" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" @@ -56,6 +57,13 @@ func (self *SBaremetalGuestDriver) GetProvider() string { return api.CLOUD_PROVIDER_ONECLOUD } +func (self *SBaremetalGuestDriver) GetInstanceCapability() cloudprovider.SInstanceCapability { + return cloudprovider.SInstanceCapability{ + Hypervisor: self.GetHypervisor(), + Provider: self.GetProvider(), + } +} + func (self *SBaremetalGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) @@ -322,7 +330,7 @@ func (self *SBaremetalGuestDriver) RequestStopGuestForDelete(ctx context.Context !guest.PendingDeleted && !overridePendingDelete && !purge { - return guest.StartGuestStopTask(ctx, task.GetUserCred(), true, task.GetTaskId()) + return guest.StartGuestStopTask(ctx, task.GetUserCred(), true, false, task.GetTaskId()) } if host != nil && !host.GetEnabled() && !purge { return fmt.Errorf("fail to contact baremetal") diff --git a/pkg/compute/guestdrivers/container.go b/pkg/compute/guestdrivers/container.go index 11f82d4e86..f0fbe99154 100644 --- a/pkg/compute/guestdrivers/container.go +++ b/pkg/compute/guestdrivers/container.go @@ -24,6 +24,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" @@ -57,6 +58,13 @@ func (self *SContainerDriver) GetProvider() string { return api.CLOUD_PROVIDER_ONECLOUD } +func (self *SContainerDriver) GetInstanceCapability() cloudprovider.SInstanceCapability { + return cloudprovider.SInstanceCapability{ + Hypervisor: self.GetHypervisor(), + Provider: self.GetProvider(), + } +} + // for backward compatibility, deprecated driver func (self *SContainerDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} diff --git a/pkg/compute/guestdrivers/ctyun.go b/pkg/compute/guestdrivers/ctyun.go index e7f3a7adaa..eb38088a55 100644 --- a/pkg/compute/guestdrivers/ctyun.go +++ b/pkg/compute/guestdrivers/ctyun.go @@ -39,6 +39,23 @@ func (self *SCtyunGuestDriver) GetProvider() string { return api.CLOUD_PROVIDER_CTYUN } +func (self *SCtyunGuestDriver) 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 *SCtyunGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) diff --git a/pkg/compute/guestdrivers/esxi.go b/pkg/compute/guestdrivers/esxi.go index 872710232d..d12bc37b9b 100644 --- a/pkg/compute/guestdrivers/esxi.go +++ b/pkg/compute/guestdrivers/esxi.go @@ -66,6 +66,23 @@ func (self *SESXiGuestDriver) GetProvider() string { return api.CLOUD_PROVIDER_VMWARE } +func (self *SESXiGuestDriver) 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: true, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + Changeable: false, + }, + }, + } +} + func (self *SESXiGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) diff --git a/pkg/compute/guestdrivers/google.go b/pkg/compute/guestdrivers/google.go index 7bbc8500de..f7806325ee 100644 --- a/pkg/compute/guestdrivers/google.go +++ b/pkg/compute/guestdrivers/google.go @@ -65,6 +65,21 @@ func (self *SGoogleGuestDriver) GetProvider() string { return api.CLOUD_PROVIDER_GOOGLE } +func (self *SGoogleGuestDriver) 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, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + }, + }, + } +} + func (self *SGoogleGuestDriver) GetDefaultSysDiskBackend() string { return api.STORAGE_GOOGLE_PD_STANDARD } diff --git a/pkg/compute/guestdrivers/huawei.go b/pkg/compute/guestdrivers/huawei.go index 1cc1ef8f55..fd2f53d349 100644 --- a/pkg/compute/guestdrivers/huawei.go +++ b/pkg/compute/guestdrivers/huawei.go @@ -110,6 +110,21 @@ func (self *SHuaweiGuestDriver) GetGuestInitialStateAfterRebuild() string { return api.VM_RUNNING } +func (self *SHuaweiGuestDriver) 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, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + }, + }, + } +} + func (self *SHuaweiGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { userName := "root" if desc.OsType == "Windows" { diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index aeb38f9aa2..1d7349d372 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -32,6 +32,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" @@ -68,6 +69,22 @@ func (self *SKVMGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, own return keys } +func (self *SKVMGuestDriver) 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: true, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + }, + }, + } +} + func (self *SKVMGuestDriver) GetDefaultSysDiskBackend() string { return api.STORAGE_LOCAL } @@ -298,13 +315,13 @@ func (self *SKVMGuestDriver) RequestSyncstatusOnHost(ctx context.Context, guest } func (self *SKVMGuestDriver) OnDeleteGuestFinalCleanup(ctx context.Context, guest *models.SGuest, userCred mcclient.TokenCredential) error { - if ispId := guest.GetMetadata("__base_instance_snapshot_id", userCred); len(ispId) > 0 { + if ispId := guest.GetMetadata(api.BASE_INSTANCE_SNAPSHOT_ID, userCred); len(ispId) > 0 { ispM, err := models.InstanceSnapshotManager.FetchById(ispId) if err == nil { isp := ispM.(*models.SInstanceSnapshot) isp.DecRefCount(ctx, userCred) } - guest.SetMetadata(ctx, "__base_instance_snapshot_id", "", userCred) + guest.SetMetadata(ctx, api.BASE_INSTANCE_SNAPSHOT_ID, "", userCred) } return nil } diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 5b0830312d..b6b7431741 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -651,8 +651,7 @@ func (self *SManagedVirtualizedGuestDriver) RequestUndeployGuestOnHost(ctx conte if errors.Cause(err) == cloudprovider.ErrNotFound { return nil, nil } - log.Errorf("host.GetIHost fail %s", err) - return nil, err + return nil, errors.Wrapf(err, "host.GetIHost") } // 创建失败时external id为空。此时直接返回即可。不需要再调用公有云api @@ -665,14 +664,11 @@ func (self *SManagedVirtualizedGuestDriver) RequestUndeployGuestOnHost(ctx conte if errors.Cause(err) == cloudprovider.ErrNotFound { return nil, nil } - - log.Errorf("ihost.GetIVMById fail %s", err) - return nil, err + return nil, errors.Wrapf(err, "ihost.GetIVMById(%s)", guest.ExternalId) } err = ivm.DeleteVM(ctx) if err != nil { - log.Errorf("ivm.DeleteVM fail %s", err) - return nil, err + return nil, errors.Wrapf(err, "ivm.DeleteVM") } for _, guestdisk := range guest.GetDisks() { @@ -684,16 +680,14 @@ func (self *SManagedVirtualizedGuestDriver) RequestUndeployGuestOnHost(ctx conte if errors.Cause(err) == cloudprovider.ErrNotFound { continue } - log.Errorf("disk.GetIDisk fail %s", err) - return nil, err + return nil, errors.Wrapf(err, "disk.GetIDisk") } if idisk.GetStatus() == api.DISK_DEALLOC { continue } err = idisk.Delete(ctx) if err != nil { - log.Errorf("idisk.Delete fail %s", err) - return nil, err + return nil, errors.Wrapf(err, "idisk.Delete") } } } @@ -706,13 +700,16 @@ func (self *SManagedVirtualizedGuestDriver) RequestStopOnHost(ctx context.Contex taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { ihost, err := host.GetIHost() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "host.GetIHost") } ivm, err := ihost.GetIVMById(guest.ExternalId) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "ihost.GetIVMById") } - err = ivm.StopVM(ctx, true) + opts := &cloudprovider.ServerStopOptions{} + task.GetParams().Unmarshal(&opts) + + err = ivm.StopVM(ctx, opts) return nil, err }) return nil diff --git a/pkg/compute/guestdrivers/openstack.go b/pkg/compute/guestdrivers/openstack.go index 208fa4bf2f..fdbcd2a512 100644 --- a/pkg/compute/guestdrivers/openstack.go +++ b/pkg/compute/guestdrivers/openstack.go @@ -64,6 +64,23 @@ func (self *SOpenStackGuestDriver) GetProvider() string { return api.CLOUD_PROVIDER_OPENSTACK } +func (self *SOpenStackGuestDriver) 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 *SOpenStackGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) @@ -268,7 +285,10 @@ func (self *SOpenStackGuestDriver) RemoteDeployGuestForRebuildRoot(ctx context.C log.Debugf("VMrebuildRoot %s new instance, wait status %s ...", iVM.GetGlobalId(), initialState) cloudprovider.WaitStatus(iVM, initialState, time.Second*5, time.Second*1800) - iVM.StopVM(ctx, true) + opts := &cloudprovider.ServerStopOptions{ + IsForce: true, + } + iVM.StopVM(ctx, opts) iDisks, err = iVM.GetIDisks() if err != nil { diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index 45f6ff8168..9eebc6d9a2 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -243,6 +243,30 @@ func (self *SQcloudGuestDriver) GetUserDataType() string { return cloudprovider.CLOUD_SHELL } +func (self *SQcloudGuestDriver) 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, + DistAccounts: []cloudprovider.SDistDefaultAccount{ + { + OsDistribution: "Ubuntu", + DefaultAccount: "ubuntu", + Changeable: false, + }, + }, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + Changeable: false, + }, + }, + } +} + func (self *SQcloudGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { userName := "root" if desc.ImageType == "system" { diff --git a/pkg/compute/guestdrivers/ucloud.go b/pkg/compute/guestdrivers/ucloud.go index 4286b5d556..37bae09faa 100644 --- a/pkg/compute/guestdrivers/ucloud.go +++ b/pkg/compute/guestdrivers/ucloud.go @@ -96,6 +96,21 @@ func (self *SUCloudGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *m return nil } +func (self *SUCloudGuestDriver) 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, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + }, + }, + } +} + func (self *SUCloudGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { if desc.OsType == "Windows" { return "Administrator" diff --git a/pkg/compute/guestdrivers/virtualization.go b/pkg/compute/guestdrivers/virtualization.go index 51fbb4d951..89a86026aa 100644 --- a/pkg/compute/guestdrivers/virtualization.go +++ b/pkg/compute/guestdrivers/virtualization.go @@ -226,7 +226,7 @@ func (self *SVirtualizedGuestDriver) RequestStopGuestForDelete(ctx context.Conte host = guest.GetHost() } if host != nil && host.GetEnabled() && host.HostStatus == api.HOST_ONLINE { - return guest.StartGuestStopTask(ctx, task.GetUserCred(), true, task.GetTaskId()) + return guest.StartGuestStopTask(ctx, task.GetUserCred(), true, false, task.GetTaskId()) } if host != nil && !jsonutils.QueryBoolean(task.GetParams(), "purge", false) { return fmt.Errorf("fail to contact host") diff --git a/pkg/compute/guestdrivers/zstack.go b/pkg/compute/guestdrivers/zstack.go index 1e3a3b3b11..80237a04b3 100644 --- a/pkg/compute/guestdrivers/zstack.go +++ b/pkg/compute/guestdrivers/zstack.go @@ -158,6 +158,21 @@ func (self *SZStackGuestDriver) IsWindowsUserDataTypeNeedEncode() bool { return true } +func (self *SZStackGuestDriver) 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, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + }, + }, + } +} + func (self *SZStackGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { userName := "root" if desc.OsType == "Windows" { diff --git a/pkg/compute/hostdrivers/apsara.go b/pkg/compute/hostdrivers/apsara.go new file mode 100644 index 0000000000..22930bfc02 --- /dev/null +++ b/pkg/compute/hostdrivers/apsara.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 SApsaraHostDriver struct { + SAliyunHostDriver +} + +func init() { + driver := SApsaraHostDriver{} + models.RegisterHostDriver(&driver) +} + +func (self *SApsaraHostDriver) GetHostType() string { + return api.HOST_TYPE_APSARA +} + +func (self *SApsaraHostDriver) GetHypervisor() string { + return api.HYPERVISOR_APSARA +} diff --git a/pkg/compute/hostdrivers/aws.go b/pkg/compute/hostdrivers/aws.go index 7fd9657918..6fbe76d78c 100644 --- a/pkg/compute/hostdrivers/aws.go +++ b/pkg/compute/hostdrivers/aws.go @@ -49,7 +49,7 @@ func (self *SAwsHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb in if sizeGb < 1 || sizeGb > 16384 { return fmt.Errorf("The %s disk size must be in the range of 1G ~ 16384GB", storage.StorageType) } - } else if storage.StorageType == api.STORAGE_IO1_SSD { + } else if storage.StorageType == api.STORAGE_IO1_SSD || storage.StorageType == api.STORAGE_IO2_SSD { if sizeGb < 4 || sizeGb > 16384 { return fmt.Errorf("The %s disk size must be in the range of 4G ~ 16384GB", storage.StorageType) } diff --git a/pkg/compute/models/buckets.go b/pkg/compute/models/buckets.go index 321f1b2a6a..8fdb2bceff 100644 --- a/pkg/compute/models/buckets.go +++ b/pkg/compute/models/buckets.go @@ -1367,13 +1367,14 @@ func (bucket *SBucket) PerformSetCors( return nil, errors.Wrap(err, "GetIBucket") } rules := []cloudprovider.SBucketCORSRule{} - for i := range input.Rules { + for i := range input.Data { rules = append(rules, cloudprovider.SBucketCORSRule{ - AllowedOrigins: input.Rules[i].AllowedOrigins, - AllowedMethods: input.Rules[i].AllowedMethods, - AllowedHeaders: input.Rules[i].AllowedHeaders, - MaxAgeSeconds: input.Rules[i].MaxAgeSeconds, - ExposeHeaders: input.Rules[i].ExposeHeaders, + AllowedOrigins: input.Data[i].AllowedOrigins, + AllowedMethods: input.Data[i].AllowedMethods, + AllowedHeaders: input.Data[i].AllowedHeaders, + MaxAgeSeconds: input.Data[i].MaxAgeSeconds, + ExposeHeaders: input.Data[i].ExposeHeaders, + Id: input.Data[i].Id, }) } err = iBucket.SetCORS(rules) @@ -1388,7 +1389,7 @@ func (bucket *SBucket) PerformSetCors( func (bucket *SBucket) AllowPerformDeleteCors( userCred mcclient.TokenCredential, query jsonutils.JSONObject, - input jsonutils.JSONObject, + input api.BucketCORSRuleDeleteInput, ) bool { return bucket.IsOwner(userCred) } @@ -1397,13 +1398,13 @@ func (bucket *SBucket) PerformDeleteCors( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - input jsonutils.JSONObject, + input api.BucketCORSRuleDeleteInput, ) (jsonutils.JSONObject, error) { iBucket, err := bucket.GetIBucket() if err != nil { return nil, errors.Wrap(err, "GetIBucket") } - err = iBucket.DeleteCORS() + err = iBucket.DeleteCORS(input.Id) if err != nil { return nil, httperrors.NewInternalServerError("iBucket.DeleteCORS error %s", err) } @@ -1436,12 +1437,13 @@ func (bucket *SBucket) GetDetailsCors( } for i := range corsRules { - rules.Rules = append(rules.Rules, api.BucketCORSRule{ + rules.Data = append(rules.Data, api.BucketCORSRule{ AllowedOrigins: corsRules[i].AllowedOrigins, AllowedMethods: corsRules[i].AllowedMethods, AllowedHeaders: corsRules[i].AllowedHeaders, MaxAgeSeconds: corsRules[i].MaxAgeSeconds, ExposeHeaders: corsRules[i].ExposeHeaders, + Id: corsRules[i].Id, }) } @@ -1471,8 +1473,9 @@ func (bucket *SBucket) GetDetailsCdnDomain( return domains, httperrors.NewInternalServerError("iBucket.GetCdnDomains error %s", err) } for i := range cdnDomains { - domains.Domains = append(domains.Domains, api.CdnDomain{ + domains.Data = append(domains.Data, api.CdnDomain{ Domain: cdnDomains[i].Domain, + Status: cdnDomains[i].Status, Area: cdnDomains[i].Area, Cname: cdnDomains[i].Cname, Origin: cdnDomains[i].Origin, diff --git a/pkg/compute/models/capabilities.go b/pkg/compute/models/capabilities.go index 66d1fb2750..7e02898d27 100644 --- a/pkg/compute/models/capabilities.go +++ b/pkg/compute/models/capabilities.go @@ -48,6 +48,9 @@ type SCapabilities struct { DisabledRdsEngineBrands []string `json:",allowempty"` CloudIdBrands []string `json:",allowempty"` DisabledCloudIdBrands []string `json:",allowempty"` + // 支持SAML 2.0 + SamlAuthBrands []string `json:",allowempty"` + DisabledSamlAuthBrands []string `json:",allowempty"` PublicIpBrands []string `json:",allowempty"` NetworkManageBrands []string `json:",allowempty"` DisabledNetworkManageBrands []string `json:",allowempty"` @@ -77,6 +80,8 @@ type SCapabilities struct { StorageTypes3 map[string]map[string]*SimpleStorageInfo `json:",allowempty"` DataStorageTypes2 map[string][]string `json:",allowempty"` DataStorageTypes3 map[string]map[string]*SimpleStorageInfo `json:",allowempty"` + + InstanceCapabilities []cloudprovider.SInstanceCapability } func GetCapabilities(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, region *SCloudregion, zone *SZone) (SCapabilities, error) { @@ -109,6 +114,13 @@ func GetCapabilities(ctx context.Context, userCred mcclient.TokenCredential, que domainId = "" } capa.Hypervisors = getHypervisors(region, zone, domainId) + capa.InstanceCapabilities = []cloudprovider.SInstanceCapability{} + for _, hypervisor := range capa.Hypervisors { + driver := GetDriver(hypervisor) + if driver != nil { + capa.InstanceCapabilities = append(capa.InstanceCapabilities, driver.GetInstanceCapability()) + } + } getBrands(region, zone, domainId, &capa) // capa.Brands, capa.ComputeEngineBrands, capa.NetworkManageBrands, capa.ObjectStorageBrands = a, c, n, o capa.ResourceTypes = getResourceTypes(region, zone, domainId) @@ -273,6 +285,7 @@ func getBrands(region *SCloudregion, zone *SZone, domainId string, capa *SCapabi capa.CloudIdBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_CLOUDID) capa.PublicIpBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_PUBLIC_IP) capa.LoadbalancerEngineBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_LOADBALANCER) + capa.SamlAuthBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_SAML_AUTH) if utils.IsInStringArray(api.HYPERVISOR_KVM, capa.Hypervisors) || utils.IsInStringArray(api.HYPERVISOR_BAREMETAL, capa.Hypervisors) { capa.Brands = append(capa.Brands, api.ONECLOUD_BRAND_ONECLOUD) @@ -291,6 +304,7 @@ func getBrands(region *SCloudregion, zone *SZone, domainId string, capa *SCapabi capa.DisabledNetworkManageBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_NETWORK) capa.DisabledObjectStorageBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE) capa.DisabledCloudIdBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_CLOUDID) + capa.DisabledSamlAuthBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_SAML_AUTH) return } diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index cdee50c2e2..39995a6de5 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -991,6 +991,15 @@ func (manager *SCloudaccountManager) validateCreateData( input.AutoCreateProject = &createProject } + endpoints := cloudprovider.SApsaraEndpoints{} + if input.SCloudaccountCredential.SApsaraEndpoints != nil { + if input.Options == nil { + input.Options = jsonutils.NewDict() + } + endpoints = *input.SCloudaccountCredential.SApsaraEndpoints + input.Options.Update(jsonutils.Marshal(input.SCloudaccountCredential.SApsaraEndpoints)) + } + input.SCloudaccount, err = providerDriver.ValidateCreateCloudaccountData(ctx, userCred, input.SCloudaccountCredential) if err != nil { return input, err @@ -1044,6 +1053,8 @@ func (manager *SCloudaccountManager) validateCreateData( Account: input.Account, Secret: input.Secret, ProxyFunc: proxyFunc, + + SApsaraEndpoints: endpoints, }) if err != nil { if err == cloudprovider.ErrNoSuchProvder { @@ -1419,6 +1430,10 @@ func (self *SCloudaccount) getProviderInternal() (cloudprovider.ICloudProvider, if err != nil { return nil, fmt.Errorf("Invalid password %s", err) } + endpoints := cloudprovider.SApsaraEndpoints{} + if self.Provider == api.CLOUD_PROVIDER_APSARA && self.Options != nil { + self.Options.Unmarshal(&endpoints) + } return cloudprovider.GetProvider(cloudprovider.ProviderConfig{ Id: self.Id, Name: self.Name, @@ -1427,6 +1442,8 @@ func (self *SCloudaccount) getProviderInternal() (cloudprovider.ICloudProvider, Account: self.Account, Secret: secret, + SApsaraEndpoints: endpoints, + ProxyFunc: self.proxyFunc(), }) } diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 56e9d3c0b6..0694744aff 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -26,6 +26,7 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/compare" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -811,6 +812,11 @@ func (self *SCloudprovider) GetProvider() (cloudprovider.ICloudProvider, error) account := self.GetCloudaccount() + endpoints := cloudprovider.SApsaraEndpoints{} + if account.Options != nil { + account.Options.Unmarshal(&endpoints) + } + return cloudprovider.GetProvider(cloudprovider.ProviderConfig{ Id: self.Id, Name: self.Name, @@ -819,6 +825,8 @@ func (self *SCloudprovider) GetProvider() (cloudprovider.ICloudProvider, error) Account: self.Account, Secret: passwd, ProxyFunc: account.proxyFunc(), + + SApsaraEndpoints: endpoints, }) } @@ -1698,3 +1706,108 @@ func (self *SCloudprovider) PerformSetSchedtag(ctx context.Context, userCred mcc func (self *SCloudprovider) GetSchedtagJointManager() ISchedtagJointManager { return CloudproviderschedtagManager } + +func (self *SCloudprovider) GetInterVpcNetworks() ([]SInterVpcNetwork, error) { + networks := []SInterVpcNetwork{} + q := InterVpcNetworkManager.Query().Equals("manager_id", self.Id) + err := db.FetchModelObjects(InterVpcNetworkManager, q, &networks) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return networks, nil + +} + +func (self *SCloudprovider) SyncInterVpcNetwork(ctx context.Context, userCred mcclient.TokenCredential, interVpcNetworks []cloudprovider.ICloudInterVpcNetwork) ([]SInterVpcNetwork, []cloudprovider.ICloudInterVpcNetwork, compare.SyncResult) { + lockman.LockRawObject(ctx, self.Keyword(), fmt.Sprintf("%s-interVpcNetwork", self.Id)) + defer lockman.ReleaseRawObject(ctx, self.Keyword(), fmt.Sprintf("%s-interVpcNetwork", self.Id)) + + result := compare.SyncResult{} + + localNetworks := []SInterVpcNetwork{} + remoteNetworks := []cloudprovider.ICloudInterVpcNetwork{} + + dbNetworks, err := self.GetInterVpcNetworks() + if err != nil { + result.Error(errors.Wrapf(err, "GetInterVpcNetworks")) + return nil, nil, result + } + + removed := make([]SInterVpcNetwork, 0) + commondb := make([]SInterVpcNetwork, 0) + commonext := make([]cloudprovider.ICloudInterVpcNetwork, 0) + added := make([]cloudprovider.ICloudInterVpcNetwork, 0) + + err = compare.CompareSets(dbNetworks, interVpcNetworks, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(err) + return nil, nil, result + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].syncRemove(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].SyncWithCloudInterVpcNetwork(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(errors.Wrapf(err, "SyncWithCloudInterVpcNetwork")) + continue + } + localNetworks = append(localNetworks, commondb[i]) + remoteNetworks = append(remoteNetworks, commonext[i]) + + result.Update() + } + + for i := 0; i < len(added); i += 1 { + network, err := InterVpcNetworkManager.newFromCloudInterVpcNetwork(ctx, userCred, added[i], self) + if err != nil { + result.AddError(err) + continue + } + + localNetworks = append(localNetworks, *network) + remoteNetworks = append(remoteNetworks, added[i]) + + result.Add() + } + + return localNetworks, remoteNetworks, result +} + +func (self *SCloudprovider) SyncCallSyncCloudproviderInterVpcNetwork(ctx context.Context, userCred mcclient.TokenCredential) { + driver, err := self.GetProvider() + if err != nil { + log.Errorf("failed to get ICloudProvider from SCloudprovider:%s %s", self.GetName(), self.Id) + return + } + if cloudprovider.IsSupportInterVpcNetwork(driver) { + networks, err := driver.GetICloudInterVpcNetworks() + if err != nil { + log.Errorf("failed to get inter vpc network for Manager %s error: %v", self.Id, err) + return + } else { + localNetwork, remoteNetwork, result := self.SyncInterVpcNetwork(ctx, userCred, networks) + if result.IsError() { + return + } + for i := range localNetwork { + lockman.LockObject(ctx, &localNetwork[i]) + defer lockman.ReleaseObject(ctx, &localNetwork[i]) + + if localNetwork[i].Deleted { + return + } + localNetwork[i].SyncInterVpcNetworkRouteSets(ctx, userCred, remoteNetwork[i]) + } + log.Infof("Sync inter vpc network for cloudaccount %s result: %s", self.GetName(), result.Result()) + return + } + } +} diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index 9857e0a086..cd38d11279 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -866,6 +866,10 @@ func syncDBInstanceResource(ctx context.Context, userCred mcclient.TokenCredenti if err != nil { log.Errorf("syncDBInstanceAccounts: %v", err) } + err = syncDBInstanceBackups(ctx, userCred, syncResults, localInstance, remoteInstance) + if err != nil { + log.Errorf("syncDBInstanceBackups: %v", err) + } } func syncDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { @@ -901,6 +905,26 @@ func syncDBInstanceSecgroups(ctx context.Context, userCred mcclient.TokenCredent return nil } +func syncDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { + backups, err := remoteInstance.GetIDBInstanceBackups() + if err != nil { + return errors.Wrapf(err, "GetIDBInstanceBackups") + } + + region := localInstance.GetRegion() + provider := localInstance.GetCloudprovider() + + result := DBInstanceBackupManager.SyncDBInstanceBackups(ctx, userCred, provider, localInstance, region, backups) + syncResults.Add(DBInstanceBackupManager, result) + + msg := result.Result() + log.Infof("SyncDBInstanceBackups for dbinstance %s result: %s", localInstance.Name, msg) + if result.IsError() { + return result.AllError() + } + return nil +} + func syncDBInstanceParameters(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { parameters, err := remoteInstance.GetIDBInstanceParameters() if err != nil { diff --git a/pkg/compute/models/dbinstance_backups.go b/pkg/compute/models/dbinstance_backups.go index 6c323791e1..b4928c4c18 100644 --- a/pkg/compute/models/dbinstance_backups.go +++ b/pkg/compute/models/dbinstance_backups.go @@ -85,9 +85,8 @@ type SDBInstanceBackup struct { // example: 32 BackupSizeMb int `nullable:"false" list:"user" json:"backup_size_mb"` - // RDS实例Id - // example: 239b9663-6d06-4ef4-8cfc-320a7fb6660d - // DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` + // 备份方式 Logical|Physical + BackupMethod string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"optional" json:"backup_method"` } func (manager *SDBInstanceBackupManager) GetContextManagers() [][]db.IModelManager { @@ -381,6 +380,22 @@ func (backup *SDBInstanceBackup) GetIDBInstanceBackup() (cloudprovider.ICloudDBI if err != nil { return nil, errors.Wrapf(err, "GetIDBInstance") } + err = cloudprovider.Wait(time.Second*3, time.Second*15, func() (bool, error) { + backups, err := iRds.GetIDBInstanceBackups() + if err != nil { + return false, errors.Wrapf(err, "GetIDBInstanceBackups") + } + for i := range backups { + if backups[i].GetGlobalId() == backup.ExternalId { + return true, nil + } + } + log.Warningf("failed to found backup %s", backup.ExternalId) + return false, nil + }) + if err != nil { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "timeout for search backup %s", backup.ExternalId) + } backups, err := iRds.GetIDBInstanceBackups() if err != nil { return nil, errors.Wrapf(err, "GetIDBInstanceBackups") @@ -390,7 +405,7 @@ func (backup *SDBInstanceBackup) GetIDBInstanceBackup() (cloudprovider.ICloudDBI return backups[i], nil } } - return nil, errors.Wrapf(cloudprovider.ErrNotFound, "externalId: %s", backup.ExternalId) + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "search backup %s", backup.ExternalId) } iRegion, err := backup.GetIRegion() @@ -464,6 +479,7 @@ func (self *SDBInstanceBackup) SyncWithCloudDBInstanceBackup( self.Engine = extBackup.GetEngine() self.EngineVersion = extBackup.GetEngineVersion() self.DBNames = extBackup.GetDBNames() + self.BackupMethod = string(extBackup.GetBackupMethod()) if dbinstanceId := extBackup.GetDBInstanceId(); len(dbinstanceId) > 0 { //有可能云上删除了实例,未删除备份 @@ -522,6 +538,7 @@ func (manager *SDBInstanceBackupManager) newFromCloudDBInstanceBackup( backup.BackupSizeMb = extBackup.GetBackupSizeMb() backup.DBNames = extBackup.GetDBNames() backup.BackupMode = extBackup.GetBackupMode() + backup.BackupMethod = string(extBackup.GetBackupMethod()) backup.ExternalId = extBackup.GetGlobalId() if dbinstanceId := extBackup.GetDBInstanceId(); len(dbinstanceId) > 0 { @@ -616,3 +633,70 @@ func (manager *SDBInstanceBackupManager) ListItemExportKeys(ctx context.Context, func (self *SDBInstanceBackup) GetChangeOwnerCandidateDomainIds() []string { return self.SManagedResourceBase.GetChangeOwnerCandidateDomainIds() } + +func (self *SDBInstanceBackup) fillRdsConfig(output *api.DBInstanceCreateInput) error { + if self.Status != api.DBINSTANCE_BACKUP_READY { + return fmt.Errorf("backup %s status is %s require %s", self.Name, self.Status, api.DBINSTANCE_BACKUP_READY) + } + if len(self.DBInstanceId) == 0 { + if len(self.Engine) == 0 { + return fmt.Errorf("backup engine %s is unknown", self.Name) + } + output.Engine = self.Engine + if len(self.EngineVersion) == 0 { + return fmt.Errorf("backup engine version %s is unknown", self.Name) + } + output.EngineVersion = self.EngineVersion + return nil + } + rds, err := self.GetDBInstance() + if err != nil { + return errors.Wrapf(err, "backup.GetDBInstance") + } + if len(output.NetworkId) == 0 { + networks, err := rds.GetDBNetworks() + if err != nil { + return errors.Wrapf(err, "GetDBNetworks") + } + if len(networks) > 0 { + output.NetworkId = networks[0].NetworkId + } + } + + if output.VcpuCount == 0 { + output.VcpuCount = rds.VcpuCount + } + if output.VmemSizeMb == 0 { + output.VmemSizeMb = rds.VmemSizeMb + } + if output.DiskSizeGB == 0 { + output.DiskSizeGB = rds.DiskSizeGB + } + if output.Port == 0 { + output.Port = rds.Port + } + if len(output.Category) == 0 { + output.Category = rds.Category + } + if len(output.StorageType) == 0 { + output.StorageType = rds.StorageType + } + output.Engine = rds.Engine + output.EngineVersion = rds.EngineVersion + if len(output.InstanceType) == 0 { + output.InstanceType = rds.InstanceType + } + if len(output.VpcId) == 0 { + output.VpcId = rds.VpcId + } + if len(output.Zone1) == 0 { + output.Zone1 = rds.Zone1 + } + if len(output.Zone2) == 0 { + output.Zone2 = rds.Zone2 + } + if len(output.Zone3) == 0 { + output.Zone3 = rds.Zone3 + } + return nil +} diff --git a/pkg/compute/models/dbinstances.go b/pkg/compute/models/dbinstances.go index e63655a75f..0037e8cc0d 100644 --- a/pkg/compute/models/dbinstances.go +++ b/pkg/compute/models/dbinstances.go @@ -132,6 +132,9 @@ type SDBInstance struct { Zone2 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` // 可用区3 Zone3 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` + + // 从备份创建新实例 + DBInstancebackupId string `width:"36" name:"dbinstancebackup_id" charset:"ascii" nullable:"false" create:"optional"` } func (manager *SDBInstanceManager) GetContextManagers() [][]db.IModelManager { @@ -284,6 +287,17 @@ func (manager *SDBInstanceManager) BatchCreateValidateCreateData(ctx context.Con } func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.DBInstanceCreateInput) (api.DBInstanceCreateInput, error) { + if len(input.DBInstancebackupId) > 0 { + _backup, err := validators.ValidateModel(userCred, DBInstanceBackupManager, &input.DBInstancebackupId) + if err != nil { + return input, err + } + backup := _backup.(*SDBInstanceBackup) + err = backup.fillRdsConfig(&input) + if err != nil { + return input, err + } + } for _, v := range map[string]*string{"zone1": &input.Zone1, "zone2": &input.Zone2, "zone3": &input.Zone3} { if len(*v) > 0 { _, err := validators.ValidateModel(userCred, ZoneManager, v) @@ -298,28 +312,35 @@ func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred return input, httperrors.NewWeakPasswordError() } } - if len(input.NetworkId) == 0 { - return input, httperrors.NewMissingParameterError("network_id") - } - _network, err := validators.ValidateModel(userCred, NetworkManager, &input.NetworkId) - if err != nil { - return input, err - } - - network := _network.(*SNetwork) - - if len(input.Address) > 0 { - ip := net.ParseIP(input.Address).To4() - if ip == nil { - return input, httperrors.NewInputParameterError("invalid address: %s", input.Address) + var vpc *SVpc + var network *SNetwork + if len(input.NetworkId) > 0 { + _network, err := validators.ValidateModel(userCred, NetworkManager, &input.NetworkId) + if err != nil { + return input, err } - addr, _ := netutils.NewIPV4Addr(input.Address) - if !network.IsAddressInRange(addr) { - return input, httperrors.NewInputParameterError("Ip %s not in network %s(%s) range", input.Address, network.Name, network.Id) + network = _network.(*SNetwork) + if len(input.Address) > 0 { + ip := net.ParseIP(input.Address).To4() + if ip == nil { + return input, httperrors.NewInputParameterError("invalid address: %s", input.Address) + } + addr, _ := netutils.NewIPV4Addr(input.Address) + if !network.IsAddressInRange(addr) { + return input, httperrors.NewInputParameterError("Ip %s not in network %s(%s) range", input.Address, network.Name, network.Id) + } } + vpc = network.GetVpc() + } else if len(input.VpcId) > 0 { + _vpc, err := validators.ValidateModel(userCred, VpcManager, &input.VpcId) + if err != nil { + return input, err + } + vpc = _vpc.(*SVpc) + } else { + return input, httperrors.NewMissingParameterError("vpc_id") } - vpc := network.GetVpc() input.VpcId = vpc.Id input.ManagerId = vpc.ManagerId cloudprovider := vpc.GetCloudprovider() diff --git a/pkg/compute/models/elasticcache_instances.go b/pkg/compute/models/elasticcache_instances.go index 55ef363590..0390451fe8 100644 --- a/pkg/compute/models/elasticcache_instances.go +++ b/pkg/compute/models/elasticcache_instances.go @@ -218,6 +218,7 @@ func (manager *SElasticcacheManager) FetchCustomizeColumns( netIds := make([]string, len(objs)) cacheIds := make([]string, len(objs)) + zoneIds := []string{} for i := range rows { rows[i] = api.ElasticcacheDetails{ VirtualResourceDetails: virtRows[i], @@ -226,6 +227,13 @@ func (manager *SElasticcacheManager) FetchCustomizeColumns( } netIds[i] = objs[i].(*SElasticcache).NetworkId cacheIds[i] = objs[i].(*SElasticcache).Id + + sz := strings.Split(objs[i].(*SElasticcache).SlaveZones, ",") + for j := range sz { + if !utils.IsInStringArray(sz[j], zoneIds) { + zoneIds = append(zoneIds, sz[j]) + } + } } networks := make(map[string]SNetwork) @@ -254,9 +262,50 @@ func (manager *SElasticcacheManager) FetchCustomizeColumns( } } + // zone ids + if len(zoneIds) > 0 { + zss := fetchElasticcacheSlaveZones(zoneIds) + for i := range objs { + if len(objs[i].(*SElasticcache).SlaveZones) > 0 { + sz := strings.Split(objs[i].(*SElasticcache).SlaveZones, ",") + szi := []apis.StandaloneShortDesc{} + for j := range sz { + if info, ok := zss[sz[j]]; ok { + szi = append(szi, info) + } else { + szi = append(szi, apis.StandaloneShortDesc{Id: sz[j], Name: sz[j]}) + } + } + + rows[i].SlaveZoneInfos = szi + } + } + } + return rows } +func fetchElasticcacheSlaveZones(zoneIds []string) map[string]apis.StandaloneShortDesc { + zones := []SZone{} + err := ZoneManager.Query().In("id", zoneIds).All(&zones) + if err != nil { + log.Debugf("fetchElasticcacheSlaveZones.ZoneManager %s", err) + return nil + } + + ret := make(map[string]apis.StandaloneShortDesc) + for i := range zones { + zsd := apis.StandaloneShortDesc{ + Id: zones[i].Id, + Name: zones[i].Name, + } + + ret[zsd.Id] = zsd + } + + return ret +} + func (self *SElasticcache) GetElasticcacheParameters() ([]SElasticcacheParameter, error) { ret := []SElasticcacheParameter{} q := ElasticcacheParameterManager.Query().Equals("elasticcache_id", self.Id) @@ -1295,7 +1344,7 @@ func (self *SElasticcache) GetAdminAccount() (*SElasticcacheAccount, error) { } } - return nil, httperrors.NewNotFoundError(fmt.Sprintf("no admin account found for elastic cache %s", self.Id)) + return nil, httperrors.NewNotFoundError("no admin account found for elastic cache %s", self.Id) } func (self *SElasticcache) StartResetPasswordTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { diff --git a/pkg/compute/models/elasticcache_skus.go b/pkg/compute/models/elasticcache_skus.go index 730ae67101..63beb06af2 100644 --- a/pkg/compute/models/elasticcache_skus.go +++ b/pkg/compute/models/elasticcache_skus.go @@ -135,12 +135,44 @@ func (manager *SElasticcacheSkuManager) FetchCustomizeColumns( stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) regRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) zoneRows := manager.SZoneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + slavezoneRows := manager.FetchSlaveZoneResourceInfos(ctx, userCred, query, objs) for i := range rows { rows[i] = api.ElasticcacheSkuDetails{ StatusStandaloneResourceDetails: stdRows[i], CloudregionResourceInfo: regRows[i], ZoneResourceInfoBase: zoneRows[i].ZoneResourceInfoBase, + SlaveZoneResourceInfoBase: slavezoneRows[i], + } + } + + return rows +} + +func (self *SElasticcacheSkuManager) FetchSlaveZoneResourceInfos(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}) []api.SlaveZoneResourceInfoBase { + rows := make([]api.SlaveZoneResourceInfoBase, len(objs)) + zoneIds := []string{} + for i := range objs { + slavezone := objs[i].(*SElasticcacheSku).SlaveZoneId + if len(slavezone) > 0 { + zoneIds = append(zoneIds, slavezone) + } + } + + zones := make(map[string]SZone) + err := db.FetchStandaloneObjectsByIds(ZoneManager, zoneIds, &zones) + if err != nil { + log.Errorf("FetchStandaloneObjectsByIds fail %s", err) + return rows + } + + for i := range objs { + if zone, ok := zones[objs[i].(*SElasticcacheSku).SlaveZoneId]; ok { + rows[i].SlaveZone = zone.GetName() + rows[i].SlaveZoneExtId = fetchExternalId(zone.GetExternalId()) } } diff --git a/pkg/compute/models/elasticips.go b/pkg/compute/models/elasticips.go index c195bbca09..ba1d0e18b3 100644 --- a/pkg/compute/models/elasticips.go +++ b/pkg/compute/models/elasticips.go @@ -509,6 +509,7 @@ func (manager *SElasticipManager) newFromCloudEip(ctx context.Context, userCred eip.ManagerId = provider.Id eip.CloudregionId = region.Id eip.ChargeType = extEip.GetInternetChargeType() + eip.Bandwidth = extEip.GetBandwidth() if networkId := extEip.GetINetworkId(); len(networkId) > 0 { network, err := db.FetchByExternalIdAndManagerId(NetworkManager, networkId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { wire := WireManager.Query().SubQuery() diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 11ea57f45e..5a00f903bd 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -670,13 +670,16 @@ func (self *SGuest) ValidateAttachDisk(ctx context.Context, disk *SDisk) error { } } - attached, err := disk.isAttached() - if err != nil { - return httperrors.NewInternalServerError("isAttached check failed %s", err) - } - if attached { - return httperrors.NewInputParameterError("Disk %s has been attached", disk.Name) + if disk.IsLocal() { + attached, err := disk.isAttached() + if err != nil { + return httperrors.NewInternalServerError("isAttached check failed %s", err) + } + if attached { + return httperrors.NewInputParameterError("Disk %s has been attached", disk.Name) + } } + if len(disk.GetPathAtHost(self.GetHost())) == 0 { return httperrors.NewInputParameterError("Disk %s not belong the guest's host", disk.Name) } @@ -902,14 +905,13 @@ func (self *SGuest) NotifyAdminServerEvent(ctx context.Context, event string, pr notifyclient.SystemNotifyWithCtx(ctx, priority, event, kwargs) } -func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { +func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.TokenCredential, isForce, stopCharging bool, parentTaskId string) error { if len(parentTaskId) == 0 { self.SetStatus(userCred, api.VM_START_STOP, "") } params := jsonutils.NewDict() - if isForce { - params.Add(jsonutils.JSONTrue, "is_force") - } + params.Add(jsonutils.NewBool(isForce), "is_force") + params.Add(jsonutils.NewBool(stopCharging), "stop_charging") if len(parentTaskId) > 0 { params.Add(jsonutils.JSONTrue, "subtask") } @@ -1018,15 +1020,11 @@ func (self *SGuest) StartInsertIsoTask(ctx context.Context, imageId string, boot return nil } -func (self *SGuest) IsDisksShared() bool { - return self.getDefaultStorageType() == api.STORAGE_RBD -} - func (self *SGuest) StartGueststartTask( ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string, ) error { - if self.Hypervisor == api.HYPERVISOR_KVM && self.IsDisksShared() { + if self.Hypervisor == api.HYPERVISOR_KVM && self.guestDisksStorageTypeIsShared() { return self.GuestSchedStartTask(ctx, userCred, data, parentTaskId) } else { return self.GuestNonSchedStartTask(ctx, userCred, data, parentTaskId) @@ -2095,6 +2093,12 @@ func (self *SGuest) PerformChangeIpaddr(ctx context.Context, userCred mcclient.T } return nil, httperrors.NewBadRequestError("%v", err) } + if _, err := db.Update(&ngn[0], func() error { + ngn[0].EipId = gn.EipId + return nil + }); err != nil { + return nil, err + } return ngn, nil }() @@ -2756,16 +2760,14 @@ func (self *SGuest) PerformStatus(ctx context.Context, userCred mcclient.TokenCr status := input.Status if len(self.BackupHostId) > 0 && status == api.VM_RUNNING { - if len(self.GetMetadata("__mirror_job_status", userCred)) == 0 { - self.SetMetadata(ctx, "__mirror_job_status", "ready", userCred) - } - } else if ispId := self.GetMetadata("__base_instance_snapshot_id", userCred); len(ispId) > 0 { + self.SetMetadata(ctx, api.MIRROR_JOB, api.MIRROR_JOB_READY, userCred) + } else if ispId := self.GetMetadata(api.BASE_INSTANCE_SNAPSHOT_ID, userCred); len(ispId) > 0 { ispM, err := InstanceSnapshotManager.FetchById(ispId) if err == nil { isp := ispM.(*SInstanceSnapshot) isp.DecRefCount(ctx, userCred) } - self.SetMetadata(ctx, "__base_instance_snapshot_id", "", userCred) + self.SetMetadata(ctx, api.BASE_INSTANCE_SNAPSHOT_ID, "", userCred) } if preStatus != self.Status && !self.isNotRunningStatus(preStatus) && self.isNotRunningStatus(self.Status) { @@ -2786,14 +2788,12 @@ func (self *SGuest) AllowPerformStop(ctx context.Context, } func (self *SGuest) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + input api.ServerStopInput) (jsonutils.JSONObject, error) { // XXX if is force, force stop guest - var isForce = jsonutils.QueryBoolean(data, "is_force", false) - if isForce || utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED}) { - return nil, self.StartGuestStopTask(ctx, userCred, isForce, "") - } else { - return nil, httperrors.NewInvalidStatusError("Cannot stop server in status %s", self.Status) + if input.IsForce || utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED}) { + return nil, self.StartGuestStopTask(ctx, userCred, input.IsForce, input.StopCharging, "") } + return nil, httperrors.NewInvalidStatusError("Cannot stop server in status %s", self.Status) } func (self *SGuest) AllowPerformRestart(ctx context.Context, @@ -3172,13 +3172,12 @@ func (self *SGuest) PerformSwitchToBackup(ctx context.Context, userCred mcclient return nil, httperrors.NewBadRequestError("Guest no backup host") } - mirrorJobStatus := self.GetMetadata("__mirror_job_status", userCred) - if mirrorJobStatus != "ready" { + mirrorJobStatus := self.GetMetadata(api.MIRROR_JOB, userCred) + if mirrorJobStatus != api.MIRROR_JOB_READY { return nil, httperrors.NewBadRequestError("Guest can't switch to backup, mirror job not ready") } oldStatus := self.Status - self.SetStatus(userCred, api.VM_SWITCH_TO_BACKUP, "Switch to backup") deleteBackup := jsonutils.QueryBoolean(data, "delete_backup", false) purgeBackup := jsonutils.QueryBoolean(data, "purge_backup", false) @@ -3190,6 +3189,7 @@ func (self *SGuest) PerformSwitchToBackup(ctx context.Context, userCred mcclient log.Errorln(err) return nil, err } else { + self.SetStatus(userCred, api.VM_SWITCH_TO_BACKUP, "Switch to backup") task.ScheduleRun(nil) } return nil, nil @@ -3292,7 +3292,7 @@ func (self *SGuest) AllowPerformBlockStreamFailed(ctx context.Context, userCred func (self *SGuest) PerformBlockStreamFailed(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { if len(self.BackupHostId) > 0 { - self.SetMetadata(ctx, "__mirror_job_status", "failed", userCred) + self.SetMetadata(ctx, api.MIRROR_JOB, api.MIRROR_JOB_FAILED, userCred) } if self.Status == api.VM_BLOCK_STREAM || self.Status == api.VM_RUNNING { reason, _ := data.GetString("reason") @@ -3302,21 +3302,6 @@ func (self *SGuest) PerformBlockStreamFailed(ctx context.Context, userCred mccli return nil, nil } -func (self *SGuest) AllowPerformSlaveStarted(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return db.IsAdminAllowPerform(userCred, self, "slave-started") -} - -func (self *SGuest) PerformSlaveStarted(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.GetMetadata("__mirror_job_status", userCred) == "failed" { - if port, err := data.Int("nbd_server_port"); err != nil { - return nil, httperrors.NewMissingParameterError("nbd_server_port") - } else { - self.StartMirrorJob(ctx, userCred, port, "") - } - } - return nil, nil -} - func (self *SGuest) StartMirrorJob(ctx context.Context, userCred mcclient.TokenCredential, nbdServerPort int64, parentTaskId string) error { taskData := jsonutils.NewDict() taskData.Set("nbd_server_port", jsonutils.NewInt(nbdServerPort)) diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go index 04fdcc1182..ac56872473 100644 --- a/pkg/compute/models/guestdrivers.go +++ b/pkg/compute/models/guestdrivers.go @@ -96,6 +96,7 @@ type IGuestDriver interface { GetGuestInitialStateAfterCreate() string GetGuestInitialStateAfterRebuild() string GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string + GetInstanceCapability() cloudprovider.SInstanceCapability OnGuestDeployTaskDataReceived(ctx context.Context, guest *SGuest, task taskman.ITask, data jsonutils.JSONObject) error diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 35686a44ee..a74a54f1e2 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -1820,7 +1820,7 @@ func (manager *SGuestManager) SetPropertiesWithInstanceSnapshot( delete(metadata, "passwd") metadata["login_key"], _ = utils.EncryptAESBase64(guest.Id, passwd.(string)) } - metadata["__base_instance_snapshot_id"] = isp.Id + metadata[api.BASE_INSTANCE_SNAPSHOT_ID] = isp.Id guest.SetAllMetadata(ctx, metadata, userCred) } } @@ -3709,11 +3709,17 @@ func (self *SGuest) DeleteAllDisksInDB(ctx context.Context, userCred mcclient.To } if disk != nil { - db.OpsLog.LogEvent(disk, db.ACT_DELETE, nil, userCred) - db.OpsLog.LogEvent(disk, db.ACT_DELOCATE, nil, userCred) - err = disk.RealDelete(ctx, userCred) + cnt, err := disk.GetGuestDiskCount() if err != nil { - return err + return errors.Wrap(err, "disk.GetGuestDiskCount") + } + if cnt == 0 { + db.OpsLog.LogEvent(disk, db.ACT_DELETE, nil, userCred) + db.OpsLog.LogEvent(disk, db.ACT_DELOCATE, nil, userCred) + err = disk.RealDelete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "disk.RealDelete") + } } } } diff --git a/pkg/compute/models/host_health.go b/pkg/compute/models/host_health.go index 56a07e7148..a14ce59153 100644 --- a/pkg/compute/models/host_health.go +++ b/pkg/compute/models/host_health.go @@ -107,7 +107,7 @@ func (h *SHostHealthChecker) onHostUnhealthy(ctx context.Context, hostId string) lockman.LockRawObject(ctx, api.HOST_HEALTH_LOCK_PREFIX, hostId) defer lockman.ReleaseRawObject(ctx, api.HOST_HEALTH_LOCK_PREFIX, hostId) host := HostManager.FetchHostById(hostId) - if host.EnableHealthCheck == true { + if host != nil && host.EnableHealthCheck == true { host.OnHostDown(ctx, auth.AdminCredential()) } } diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 9fbc2e658f..7e2c718b28 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -3594,7 +3594,9 @@ func (self *SHost) PerformStop(ctx context.Context, userCred mcclient.TokenCrede return nil, self.InitializedGuestStop(ctx, userCred, guest) } self.SetStatus(userCred, api.BAREMETAL_START_MAINTAIN, "") - return guest.PerformStop(ctx, userCred, query, data) + input := api.ServerStopInput{} + data.Unmarshal(&input) + return guest.PerformStop(ctx, userCred, query, input) } } return nil, self.StartBaremetalUnmaintenanceTask(ctx, userCred, false, "stop") diff --git a/pkg/compute/models/inter_vpc_network.go b/pkg/compute/models/inter_vpc_network.go new file mode 100644 index 0000000000..f9a382ebba --- /dev/null +++ b/pkg/compute/models/inter_vpc_network.go @@ -0,0 +1,568 @@ +// 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 models + +import ( + "context" + "database/sql" + "fmt" + + "gopkg.in/fatih/set.v0" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SInterVpcNetworkManager struct { + db.SEnabledStatusInfrasResourceBaseManager + db.SExternalizedResourceBaseManager + SManagedResourceBaseManager +} + +var InterVpcNetworkManager *SInterVpcNetworkManager + +func init() { + InterVpcNetworkManager = &SInterVpcNetworkManager{ + SEnabledStatusInfrasResourceBaseManager: db.NewEnabledStatusInfrasResourceBaseManager( + SInterVpcNetwork{}, + "inter_vpc_networks_tbl", + "inter_vpc_network", + "inter_vpc_networks", + ), + } + InterVpcNetworkManager.SetVirtualObject(InterVpcNetworkManager) +} + +type SInterVpcNetwork struct { + db.SEnabledStatusInfrasResourceBase + db.SExternalizedResourceBase + SManagedResourceBase +} + +func (manager *SInterVpcNetworkManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := manager.SEnabledStatusInfrasResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SManagedResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SInterVpcNetworkManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.InterVpcNetworkManagerListInput, +) (*sqlchemy.SQuery, error) { + q, err := manager.SEnabledStatusInfrasResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.EnabledStatusInfrasResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SInfrasResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SManagedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +// 列表 +func (manager *SInterVpcNetworkManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.InterVpcNetworkListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SEnabledStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.EnabledStatusInfrasResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.ListItemFilter") + } + q, err = manager.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (manager *SInterVpcNetworkManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.InterVpcNetworkCreateInput, +) (api.InterVpcNetworkCreateInput, error) { + return input, nil +} + +func (self *SInterVpcNetwork) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "InterVpcNetworkCreateTask", self, userCred, params, "", "", nil) + if err != nil { + return + } + self.SetStatus(userCred, api.INTER_VPC_NETWORK_STATUS_CREATING, "") + task.ScheduleRun(nil) +} + +func (self *SInterVpcNetwork) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.InterVpcNetworkDetails, error) { + return api.InterVpcNetworkDetails{}, nil +} + +func (manager *SInterVpcNetworkManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.InterVpcNetworkDetails { + rows := make([]api.InterVpcNetworkDetails, len(objs)) + stdRows := manager.SEnabledStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + vpcNetworkIds := make([]string, len(objs)) + for i := range rows { + rows[i] = api.InterVpcNetworkDetails{ + EnabledStatusInfrasResourceBaseDetails: stdRows[i], + } + vpcNetwork := objs[i].(*SInterVpcNetwork) + vpcNetworkIds[i] = vpcNetwork.Id + } + + vpcNetworkVpcs := []SInterVpcNetworkVpc{} + q := InterVpcNetworkVpcManager.Query().In("inter_vpc_network_id", vpcNetworkIds) + err := db.FetchModelObjects(InterVpcNetworkVpcManager, q, &vpcNetworkVpcs) + if err != nil { + return rows + } + vpcMap := map[string][]string{} + for i := range vpcNetworkVpcs { + if _, ok := vpcMap[vpcNetworkVpcs[i].InterVpcNetworkId]; !ok { + vpcMap[vpcNetworkVpcs[i].InterVpcNetworkId] = []string{} + } + vpcMap[vpcNetworkVpcs[i].InterVpcNetworkId] = append(vpcMap[vpcNetworkVpcs[i].InterVpcNetworkId], vpcNetworkVpcs[i].VpcId) + } + for i := range rows { + rows[i].VpcCount = len(vpcMap[vpcNetworkIds[i]]) + } + return rows +} + +func (manager *SInterVpcNetworkManager) newFromCloudInterVpcNetwork(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudInterVpcNetwork, provider *SCloudprovider) (*SInterVpcNetwork, error) { + externalVpcIds, err := ext.GetICloudVpcIds() + if err != nil { + return nil, errors.Wrapf(err, "GetICloudVpcIds") + } + vpcIds := []string{} + for i := range externalVpcIds { + vpc, err := db.FetchByExternalIdAndManagerId(VpcManager, externalVpcIds[i], func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + managerQ := CloudproviderManager.Query("id").Equals("provider", provider.Provider) + return q.In("manager_id", managerQ.SubQuery()) + }) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return nil, errors.Wrapf(err, "vpc.FetchByExternalIdAndManagerId(%s)", externalVpcIds[i]) + } + } + vpcIds = append(vpcIds, vpc.GetId()) + } + + interVpcNetwork := &SInterVpcNetwork{} + interVpcNetwork.SetModelManager(manager, interVpcNetwork) + interVpcNetwork.Name = ext.GetName() + interVpcNetwork.Enabled = tristate.True + interVpcNetwork.Status = ext.GetStatus() + interVpcNetwork.ManagerId = provider.Id + interVpcNetwork.ExternalId = ext.GetGlobalId() + err = manager.TableSpec().Insert(ctx, interVpcNetwork) + if err != nil { + return nil, errors.Wrapf(err, "interVpcNetwork.Insert") + } + + for i := range vpcIds { + err := interVpcNetwork.AddVpc(ctx, vpcIds[i]) + if err != nil { + return nil, errors.Wrapf(err, "interVpcNetwork.AddVpc(%s)", vpcIds[i]) + } + } + + SyncCloudDomain(userCred, interVpcNetwork, provider.GetOwnerId()) + interVpcNetwork.SyncShareState(ctx, userCred, provider.getAccountShareInfo()) + + return interVpcNetwork, nil +} + +func (self *SInterVpcNetwork) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + task, err := taskman.TaskManager.NewTask(ctx, "InterVpcNetworkDeleteTask", self, userCred, nil, "", "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.INTER_VPC_NETWORK_STATUS_DELETING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SInterVpcNetwork) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := self.RemoveAllVpc(ctx) + if err != nil { + return errors.Wrapf(err, "RemoveAllVpc") + } + return self.SEnabledStatusInfrasResourceBase.Delete(ctx, userCred) +} + +func (self *SInterVpcNetwork) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "syncstatus") +} + +func (self *SInterVpcNetwork) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkSyncstatusInput) (jsonutils.JSONObject, error) { + return nil, StartResourceSyncStatusTask(ctx, userCred, self, "InterVpcNetworkSyncstatusTask", "") +} + +func (self *SInterVpcNetwork) syncRemove(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.RealDelete(ctx, userCred) +} + +func (self *SInterVpcNetwork) StartInterVpcNetworkAddVpcTask(ctx context.Context, userCred mcclient.TokenCredential, vpc *SVpc) error { + data := jsonutils.NewDict() + data.Set("vpc_id", jsonutils.NewString(vpc.Id)) + task, err := taskman.TaskManager.NewTask(ctx, "InterVpcNetworkAddVpcTask", self, userCred, data, "", "", nil) + if err != nil { + return err + } + self.SetStatus(userCred, api.INTER_VPC_NETWORK_STATUS_ADDVPC, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SInterVpcNetwork) AllowPerformAddvpc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "addvpc") +} + +func (self *SInterVpcNetwork) PerformAddvpc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkAddVpcInput) (jsonutils.JSONObject, error) { + if len(input.VpcId) == 0 { + return nil, httperrors.NewMissingParameterError("vpc_id") + } + // get vpc + _vpc, err := VpcManager.FetchByIdOrName(userCred, input.VpcId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("vpc", input.VpcId) + } + return nil, httperrors.NewGeneralError(err) + } + vpc := _vpc.(*SVpc) + + vpcCloudProvider := vpc.GetCloudprovider() + cloudProvider := self.GetCloudprovider() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if vpcCloudProvider.Provider != cloudProvider.Provider { + return nil, httperrors.NewNotSupportedError("vpc joint interVpcNetwork on different cloudprovider is not supported") + } + if vpcCloudProvider.AccessUrl != cloudProvider.AccessUrl { + return nil, httperrors.NewNotSupportedError("vpc joint interVpcNetwork on different cloudEnv is not supported") + } + + q := InterVpcNetworkVpcManager.Query().Equals("vpc_id", vpc.Id) + vpcNetworkjoints := []SInterVpcNetworkVpc{} + err = db.FetchModelObjects(InterVpcNetworkVpcManager, q, &vpcNetworkjoints) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if len(vpcNetworkjoints) > 0 { + return nil, httperrors.NewInputParameterError("vpc %s already connected to a interVpcNetwork", vpc.Id) + } + + err = self.StartInterVpcNetworkAddVpcTask(ctx, userCred, vpc) + if err != nil { + return nil, err + } + + return nil, nil +} + +func (self *SInterVpcNetwork) AllowPerformRemovevpc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "removevpc") +} + +func (self *SInterVpcNetwork) StartInterVpcNetworkRemoveVpcTask(ctx context.Context, userCred mcclient.TokenCredential, vpc *SVpc) error { + data := jsonutils.NewDict() + data.Set("vpc_id", jsonutils.NewString(vpc.Id)) + task, err := taskman.TaskManager.NewTask(ctx, "InterVpcNetworkRemoveVpcTask", self, userCred, data, "", "", nil) + if err != nil { + return err + } + self.SetStatus(userCred, api.INTER_VPC_NETWORK_STATUS_REMOVEVPC, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SInterVpcNetwork) PerformRemovevpc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkRemoveVpcInput) (jsonutils.JSONObject, error) { + if len(input.VpcId) == 0 { + return nil, httperrors.NewMissingParameterError("vpc_id") + } + // get vpc + _vpc, err := VpcManager.FetchByIdOrName(userCred, input.VpcId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("vpc", input.VpcId) + } + return nil, httperrors.NewGeneralError(err) + } + + vpc := _vpc.(*SVpc) + + q := InterVpcNetworkVpcManager.Query().Equals("inter_vpc_network_id", self.Id).Equals("vpc_id", vpc.Id) + vpcNetworkjoints := []SInterVpcNetworkVpc{} + err = db.FetchModelObjects(InterVpcNetworkVpcManager, q, &vpcNetworkjoints) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if len(vpcNetworkjoints) == 0 { + return nil, httperrors.NewInputParameterError("vpc %s is not connected to this interVpcNetwork", vpc.Id) + } + + err = self.StartInterVpcNetworkRemoveVpcTask(ctx, userCred, vpc) + if err != nil { + return nil, err + } + + return nil, nil +} + +func (self *SInterVpcNetwork) AddVpc(ctx context.Context, vpcId string) error { + networkVpc := &SInterVpcNetworkVpc{} + networkVpc.SetModelManager(InterVpcNetworkVpcManager, networkVpc) + networkVpc.VpcId = vpcId + networkVpc.InterVpcNetworkId = self.Id + return InterVpcNetworkVpcManager.TableSpec().Insert(ctx, networkVpc) +} + +func (self *SInterVpcNetwork) GetVpcs() ([]SVpc, error) { + sq := InterVpcNetworkVpcManager.Query("vpc_id").Equals("inter_vpc_network_id", self.Id) + q := VpcManager.Query().In("id", sq.SubQuery()) + vpcs := []SVpc{} + err := db.FetchModelObjects(VpcManager, q, &vpcs) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return vpcs, nil +} + +func (self *SInterVpcNetwork) RemoveVpc(ctx context.Context, vpcId string) error { + q := InterVpcNetworkVpcManager.Query().Equals("inter_vpc_network_id", self.Id).Equals("vpc_id", vpcId) + networkVpcs := []SInterVpcNetworkVpc{} + err := db.FetchModelObjects(InterVpcNetworkVpcManager, q, &networkVpcs) + if err != nil { + return errors.Wrapf(err, "db.FetchModelObjects") + } + for i := range networkVpcs { + err = networkVpcs[i].Delete(ctx, nil) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + return nil +} + +func (self *SInterVpcNetwork) RemoveAllVpc(ctx context.Context) error { + q := InterVpcNetworkVpcManager.Query().Equals("inter_vpc_network_id", self.Id) + networkVpcs := []SInterVpcNetworkVpc{} + err := db.FetchModelObjects(InterVpcNetworkVpcManager, q, &networkVpcs) + if err != nil { + return errors.Wrapf(err, "db.FetchModelObjects") + } + for i := range networkVpcs { + err = networkVpcs[i].Delete(ctx, nil) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + return nil +} + +func (self *SInterVpcNetwork) SyncWithCloudInterVpcNetwork(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudInterVpcNetwork) error { + _, err := db.Update(self, func() error { + self.ExternalId = ext.GetGlobalId() + self.Status = ext.GetStatus() + self.Name = ext.GetName() + return nil + }) + if err != nil { + return errors.Wrapf(err, "db.Update") + } + localVpcs, err := self.GetVpcs() + if err != nil { + return errors.Wrapf(err, "GetVpcs") + } + externalVpcIds, err := ext.GetICloudVpcIds() + if err != nil { + return errors.Wrapf(err, "GetICloudVpcIds") + } + remoteVpcIds := []string{} + manager := self.GetCloudprovider() + for i := range externalVpcIds { + vpc, err := db.FetchByExternalIdAndManagerId(VpcManager, externalVpcIds[i], func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + managerQ := CloudproviderManager.Query("id").Equals("provider", manager.Provider) + return q.In("manager_id", managerQ.SubQuery()) + }) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return errors.Wrapf(err, "vpc.FetchByExternalIdAndManagerId(%s)", externalVpcIds[i]) + } + } + remoteVpcIds = append(remoteVpcIds, vpc.GetId()) + } + + localVpcIdSet := set.New(set.ThreadSafe) + for i := range localVpcs { + localVpcIdSet.Add(localVpcs[i].Id) + } + remoteVpcIdSet := set.New(set.ThreadSafe) + for i := range remoteVpcIds { + remoteVpcIdSet.Add(remoteVpcIds[i]) + } + + for _, del := range set.Difference(localVpcIdSet, remoteVpcIdSet).List() { + err := self.RemoveVpc(ctx, del.(string)) + if err != nil { + return errors.Wrapf(err, "self.RemoveVpc %s", del.(string)) + } + } + for _, add := range set.Difference(remoteVpcIdSet, localVpcIdSet).List() { + err := self.AddVpc(ctx, add.(string)) + if err != nil { + return errors.Wrapf(err, "self.RemoveVpc %s", add.(string)) + } + } + + return nil +} + +/* +func (self *SInterVpcNetwork) GetCloudaccount() (*SCloudaccount, error) { + account, err := CloudaccountManager.FetchById(self.CloudaccountId) + if err != nil { + return nil, errors.Wrapf(err, "CloudaccountManager.FetchById(%s)", self.CloudaccountId) + } + return account.(*SCloudaccount), nil +} +*/ + +func (self *SInterVpcNetwork) GetInterVpcNetworkRouteSets() ([]SInterVpcNetworkRouteSet, error) { + routes := []SInterVpcNetworkRouteSet{} + q := InterVpcNetworkRouteSetManager.Query().Equals("inter_vpc_network_id", self.Id) + err := db.FetchModelObjects(InterVpcNetworkRouteSetManager, q, &routes) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return routes, nil +} + +func (self *SInterVpcNetwork) SyncInterVpcNetworkRouteSets(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudInterVpcNetwork) compare.SyncResult { + lockman.LockRawObject(ctx, self.Keyword(), fmt.Sprintf("%s-records", self.Id)) + defer lockman.ReleaseRawObject(ctx, self.Keyword(), fmt.Sprintf("%s-records", self.Id)) + + syncResult := compare.SyncResult{} + + iRoutes, err := ext.GetIRoutes() + if err != nil { + syncResult.Error(errors.Wrapf(err, "GetIRoutes")) + return syncResult + } + + dbRouteSets, err := self.GetInterVpcNetworkRouteSets() + if err != nil { + syncResult.Error(errors.Wrapf(err, "GetRouteTableRouteSets")) + return syncResult + } + + removed := make([]SInterVpcNetworkRouteSet, 0) + commondb := make([]SInterVpcNetworkRouteSet, 0) + commonext := make([]cloudprovider.ICloudInterVpcNetworkRoute, 0) + added := make([]cloudprovider.ICloudInterVpcNetworkRoute, 0) + if err := compare.CompareSets(dbRouteSets, iRoutes, &removed, &commondb, &commonext, &added); err != nil { + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i++ { + err := removed[i].syncRemoveRouteSet(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + } else { + syncResult.Delete() + } + } + + for i := 0; i < len(commondb); i++ { + err := commondb[i].syncWithCloudRouteSet(ctx, userCred, self, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + continue + } + syncResult.Update() + } + + for i := 0; i < len(added); i++ { + _, err := InterVpcNetworkRouteSetManager.newRouteSetFromCloud(ctx, userCred, self, added[i]) + if err != nil { + syncResult.AddError(err) + continue + } + syncResult.Add() + } + + return syncResult +} + +func (self *SInterVpcNetwork) GetProvider() (cloudprovider.ICloudProvider, error) { + provider, err := self.GetCloudprovider().GetProvider() + if err != nil { + return nil, errors.Wrapf(err, "self.GetCloudprovider().GetProvider()") + } + return provider, nil +} + +func (self *SInterVpcNetwork) GetICloudInterVpcNetwork() (cloudprovider.ICloudInterVpcNetwork, error) { + provider, err := self.GetProvider() + if err != nil { + return nil, errors.Wrap(err, "snetwork.GetProvider()") + } + iVpcNetwork, err := provider.GetICloudInterVpcNetworkById(self.ExternalId) + if err != nil { + return nil, errors.Wrapf(err, "GetICloudInterVpcNetworkById(%s)", self.ExternalId) + } + return iVpcNetwork, nil +} diff --git a/pkg/compute/models/inter_vpc_network_routeset.go b/pkg/compute/models/inter_vpc_network_routeset.go new file mode 100644 index 0000000000..fab0322b3b --- /dev/null +++ b/pkg/compute/models/inter_vpc_network_routeset.go @@ -0,0 +1,316 @@ +// 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 models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SInterVpcNetworkRouteSetManager struct { + db.SEnabledStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SVpcResourceBaseManager +} + +var InterVpcNetworkRouteSetManager *SInterVpcNetworkRouteSetManager + +func init() { + InterVpcNetworkRouteSetManager = &SInterVpcNetworkRouteSetManager{ + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SInterVpcNetworkRouteSet{}, + "inter_vpc_network_route_sets_tbl", + "inter_vpc_network_route_set", + "inter_vpc_network_route_sets", + ), + } + InterVpcNetworkRouteSetManager.SetVirtualObject(InterVpcNetworkRouteSetManager) +} + +type SInterVpcNetworkRouteSet struct { + db.SEnabledStatusStandaloneResourceBase + db.SExternalizedResourceBase + SVpcResourceBase + InterVpcNetworkId string + + Cidr string `width:"36" charset:"ascii" nullable:"true" list:"domain"` + ExtInstanceId string `width:"36" charset:"ascii" nullable:"false" list:"domain"` + ExtInstanceType string `width:"36" charset:"ascii" nullable:"false" list:"domain"` + ExtInstanceRegionId string `width:"36" charset:"ascii" nullable:"false" list:"domain"` +} + +func (manager *SInterVpcNetworkRouteSetManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.InterVpcNetworkRouteSetListInput, +) (*sqlchemy.SQuery, error) { + q, err := manager.SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields") + } + + q, err = manager.SVpcResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VpcFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SInterVpcNetworkRouteSetManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.InterVpcNetworkRouteSetListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemFilter") + } + + q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + + q, err = manager.SVpcResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VpcFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + if len(query.InterVpcNetworkId) > 0 { + vpcNetwork, err := InterVpcNetworkManager.FetchByIdOrName(userCred, query.InterVpcNetworkId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("inter_vpc_network_id", query.InterVpcNetworkId) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("inter_vpc_network_id", vpcNetwork.GetId()) + } + return q, nil +} + +func (self *SInterVpcNetworkRouteSet) syncRemoveRouteSet(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + err := self.ValidateDeleteCondition(ctx) + if err != nil { + return err + } + err = self.RealDelete(ctx, userCred) + return err +} + +func (self *SInterVpcNetworkRouteSet) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (self *SInterVpcNetworkRouteSet) syncWithCloudRouteSet(ctx context.Context, userCred mcclient.TokenCredential, interVpcNetwork *SInterVpcNetwork, cloudRouteSet cloudprovider.ICloudInterVpcNetworkRoute) error { + vpcId := "" + if cloudRouteSet.GetInstanceType() == api.INTER_VPCNETWORK_ATTACHED_INSTAMCE_TYPE_VPC { + provider := interVpcNetwork.GetCloudprovider() + vpc, err := db.FetchByExternalIdAndManagerId(VpcManager, cloudRouteSet.GetInstanceId(), func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + managerQ := CloudproviderManager.Query("id").Equals("provider", provider.Provider) + return q.In("manager_id", managerQ.SubQuery()) + }) + if err != nil { + return errors.Wrap(err, "db.FetchByExternalIdAndManagerId(VpcManager") + } + vpcId = vpc.GetId() + } + + diff, err := db.UpdateWithLock(ctx, self, func() error { + self.Name = cloudRouteSet.GetName() + self.Enabled = tristate.NewFromBool(cloudRouteSet.GetEnabled()) + self.Status = cloudRouteSet.GetStatus() + self.Cidr = cloudRouteSet.GetCidr() + self.InterVpcNetworkId = interVpcNetwork.GetId() + self.VpcId = vpcId + self.ExtInstanceId = cloudRouteSet.GetInstanceId() + self.ExtInstanceType = cloudRouteSet.GetInstanceType() + self.ExtInstanceRegionId = cloudRouteSet.GetInstanceRegionId() + return nil + }) + if err != nil { + return err + } + + db.OpsLog.LogSyncUpdate(self, diff, userCred) + return nil +} + +func (manager *SInterVpcNetworkRouteSetManager) newRouteSetFromCloud(ctx context.Context, userCred mcclient.TokenCredential, interVpcNetwork *SInterVpcNetwork, cloudRouteSet cloudprovider.ICloudInterVpcNetworkRoute) (*SInterVpcNetworkRouteSet, error) { + routeSet := &SInterVpcNetworkRouteSet{ + InterVpcNetworkId: interVpcNetwork.GetId(), + Cidr: cloudRouteSet.GetCidr(), + ExtInstanceId: cloudRouteSet.GetInstanceId(), + ExtInstanceType: cloudRouteSet.GetInstanceType(), + ExtInstanceRegionId: cloudRouteSet.GetInstanceRegionId(), + } + + if cloudRouteSet.GetInstanceType() == api.INTER_VPCNETWORK_ATTACHED_INSTAMCE_TYPE_VPC { + provider := interVpcNetwork.GetCloudprovider() + vpc, err := db.FetchByExternalIdAndManagerId(VpcManager, cloudRouteSet.GetInstanceId(), func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + managerQ := CloudproviderManager.Query("id").Equals("provider", provider.Provider) + return q.In("manager_id", managerQ.SubQuery()) + }) + if err != nil { + return nil, errors.Wrap(err, "db.FetchByExternalIdAndManagerId(VpcManager") + } + routeSet.VpcId = vpc.GetId() + } + routeSet.ExternalId = cloudRouteSet.GetId() + routeSet.Name = cloudRouteSet.GetName() + routeSet.Enabled = tristate.NewFromBool(cloudRouteSet.GetEnabled()) + routeSet.Status = cloudRouteSet.GetStatus() + + routeSet.SetModelManager(manager, routeSet) + if err := manager.TableSpec().Insert(ctx, routeSet); err != nil { + return nil, err + } + + db.OpsLog.LogEvent(routeSet, db.ACT_CREATE, routeSet.GetShortDesc(ctx), userCred) + return routeSet, nil +} + +func (manager *SInterVpcNetworkRouteSetManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys") + } + + q, err = manager.SVpcResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SVpcResourceBaseManager.ListItemExportKeys") + } + + return q, nil +} + +func (manager *SInterVpcNetworkRouteSetManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := manager.SEnabledStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + q, err = manager.SVpcResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (self *SInterVpcNetworkRouteSet) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.InterVpcNetworkRouteSetDetails, error) { + return api.InterVpcNetworkRouteSetDetails{}, nil +} + +func (manager *SInterVpcNetworkRouteSetManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.InterVpcNetworkRouteSetDetails { + rows := make([]api.InterVpcNetworkRouteSetDetails, len(objs)) + stdRows := manager.SEnabledStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + vpcRows := manager.SVpcResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.InterVpcNetworkRouteSetDetails{ + EnabledStatusStandaloneResourceDetails: stdRows[i], + VpcResourceInfo: vpcRows[i], + } + } + return rows +} + +func (self *SInterVpcNetworkRouteSet) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkRouteSetEnableInput) bool { + return db.IsDomainAllowPerform(userCred, self, "enable") +} + +func (self *SInterVpcNetworkRouteSet) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkRouteSetEnableInput) (jsonutils.JSONObject, error) { + _, err := self.SEnabledStatusStandaloneResourceBase.PerformEnable(ctx, userCred, query, input.PerformEnableInput) + if err != nil { + return nil, err + } + network, err := self.GetInterVpcNetwork() + if err != nil { + return nil, errors.Wrap(err, "self.GetInterVpcNetwork()") + } + err = network.StartInterVpcNetworkUpdateRoutesetTask(ctx, userCred, self, "enable") + + return nil, err +} + +func (self *SInterVpcNetworkRouteSet) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkRouteSetDisableInput) bool { + return db.IsDomainAllowPerform(userCred, self, "disable") +} + +func (self *SInterVpcNetworkRouteSet) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InterVpcNetworkRouteSetDisableInput) (jsonutils.JSONObject, error) { + _, err := self.SEnabledStatusStandaloneResourceBase.PerformDisable(ctx, userCred, query, input.PerformDisableInput) + if err != nil { + return nil, err + } + network, err := self.GetInterVpcNetwork() + if err != nil { + return nil, errors.Wrap(err, "self.GetInterVpcNetwork()") + } + err = network.StartInterVpcNetworkUpdateRoutesetTask(ctx, userCred, self, "disable") + return nil, err +} + +func (self *SInterVpcNetworkRouteSet) GetInterVpcNetwork() (*SInterVpcNetwork, error) { + network, err := InterVpcNetworkManager.FetchById(self.InterVpcNetworkId) + if err != nil { + return nil, errors.Wrapf(err, "InterVpcNetworkManager.FetchById(%s)", self.InterVpcNetworkId) + } + return network.(*SInterVpcNetwork), nil +} + +func (self *SInterVpcNetwork) StartInterVpcNetworkUpdateRoutesetTask(ctx context.Context, userCred mcclient.TokenCredential, routeSet *SInterVpcNetworkRouteSet, routeSetAction string) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(routeSetAction), "action") + params.Add(jsonutils.NewString(routeSet.GetId()), "inter_vpc_network_route_set_id") + task, err := taskman.TaskManager.NewTask(ctx, "InterVpcNetworkUpdateRoutesetTask", self, userCred, params, "", "", nil) + if err != nil { + return errors.Wrap(err, "Start InterVpcNetworkUpdateRoutesetTask fail") + } + self.SetStatus(userCred, api.INTER_VPC_NETWORK_STATUS_UPDATEROUTE, "update route") + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/inter_vpc_network_vpc.go b/pkg/compute/models/inter_vpc_network_vpc.go new file mode 100644 index 0000000000..8a9f5b98fa --- /dev/null +++ b/pkg/compute/models/inter_vpc_network_vpc.go @@ -0,0 +1,64 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SInterVpcNetworkVpcManager struct { + db.SJointResourceBaseManager + SInterVpcNetworkResourceBaseManager +} + +var InterVpcNetworkVpcManager *SInterVpcNetworkVpcManager + +func init() { + db.InitManager(func() { + InterVpcNetworkVpcManager = &SInterVpcNetworkVpcManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + SInterVpcNetworkVpc{}, + "inter_vpc_network_vpc_tbl", + "inter_vpc_network_vpc", + "inter_vpc_network_vpcs", + InterVpcNetworkManager, + VpcManager, + ), + } + InterVpcNetworkManager.SetVirtualObject(InterVpcNetworkManager) + }) +} + +type SInterVpcNetworkVpc struct { + db.SJointResourceBase + SInterVpcNetworkResourceBase + + VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user"` +} + +func (manager *SInterVpcNetworkVpcManager) GetMasterFieldName() string { + return "inter_vpc_network_id" +} + +func (manager *SInterVpcNetworkVpcManager) GetSlaveFieldName() string { + return "vpc_id" +} + +func (self *SInterVpcNetworkVpc) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DetachJoint(ctx, userCred, self) +} diff --git a/pkg/compute/models/intervpcnetworkresource.go b/pkg/compute/models/intervpcnetworkresource.go new file mode 100644 index 0000000000..b435644111 --- /dev/null +++ b/pkg/compute/models/intervpcnetworkresource.go @@ -0,0 +1,52 @@ +// 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 models + +import ( + "context" + "database/sql" + + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SInterVpcNetworkResourceBase struct { + InterVpcNetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" json:"inter_vpc_network_id"` +} + +type SInterVpcNetworkResourceBaseManager struct{} + +func (manager *SInterVpcNetworkResourceBaseManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.InterVpcNetworkFilterListBase, +) (*sqlchemy.SQuery, error) { + if len(query.InterVpcNetworkId) > 0 { + network, err := InterVpcNetworkManager.FetchByIdOrName(userCred, query.InterVpcNetworkId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("inter_vpc_network", query.InterVpcNetworkId) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("inter_vpc_network_id", network.GetId()) + } + return q, nil +} diff --git a/pkg/compute/models/loadbalancerbackends.go b/pkg/compute/models/loadbalancerbackends.go index e275466bc9..488280fd6b 100644 --- a/pkg/compute/models/loadbalancerbackends.go +++ b/pkg/compute/models/loadbalancerbackends.go @@ -187,7 +187,8 @@ func (man *SLoadbalancerBackendManager) ValidateBackendVpc(lb *SLoadbalancer, gu return httperrors.NewBadRequestError("%s", err) } if len(lb.VpcId) > 0 { - if vpc.Id != lb.VpcId { + lbVpc := lb.GetVpc() + if lbVpc != nil && !lbVpc.IsEmulated && vpc.Id != lb.VpcId { return httperrors.NewBadRequestError("guest %s(%s) vpc %s(%s) not same as loadbalancer vpc %s", guest.Name, guest.Id, vpc.Name, vpc.Id, lb.VpcId) } return nil diff --git a/pkg/compute/models/loadbalancercachedcertificates.go b/pkg/compute/models/loadbalancercachedcertificates.go index 83dd6835d8..5ca4e5d6da 100644 --- a/pkg/compute/models/loadbalancercachedcertificates.go +++ b/pkg/compute/models/loadbalancercachedcertificates.go @@ -114,6 +114,10 @@ func (self *SCachedLoadbalancerCertificate) ValidateDeleteCondition(ctx context. return nil } +func (self *SCachedLoadbalancerCertificate) ValidatePurgeCondition(ctx context.Context) error { + return nil +} + func (self *SCachedLoadbalancerCertificate) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index 34c3689bd0..0d54e8f565 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -176,7 +176,7 @@ func (lbcert *SCachedLoadbalancerCertificate) purge(ctx context.Context, userCre lockman.LockObject(ctx, lbcert) defer lockman.ReleaseObject(ctx, lbcert) - err := lbcert.ValidateDeleteCondition(ctx) + err := lbcert.ValidatePurgeCondition(ctx) if err != nil { return err } diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index 6c3b931372..87d1ae696c 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -161,6 +161,7 @@ type IDBInstanceDriver interface { ValidateResetDBInstancePassword(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, account string) error RequestCreateDBInstance(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, task taskman.ITask) error + RequestCreateDBInstanceFromBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, task taskman.ITask) error RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, backup *SDBInstanceBackup, task taskman.ITask) error RequestChangeDBInstanceConfig(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, task taskman.ITask) error diff --git a/pkg/compute/models/routetables.go b/pkg/compute/models/routetables.go index 0f7ec00cb2..ad4ec08863 100644 --- a/pkg/compute/models/routetables.go +++ b/pkg/compute/models/routetables.go @@ -460,6 +460,7 @@ func (man *SRouteTableManager) newRouteTableFromCloud(userCred mcclient.TokenCre routeTable.Name = newName } // routeTable.ManagerId = vpc.ManagerId + routeTable.Status = cloudRouteTable.GetStatus() routeTable.ExternalId = cloudRouteTable.GetGlobalId() routeTable.Description = cloudRouteTable.GetDescription() // routeTable.ProjectId = userCred.GetProjectId() @@ -515,6 +516,7 @@ func (self *SRouteTable) SyncWithCloudRouteTable(ctx context.Context, userCred m } diff, err := db.UpdateWithLock(ctx, self, func() error { // self.CloudregionId = routeTable.CloudregionId + self.Status = routeTable.GetStatus() self.VpcId = vpc.Id self.Type = routeTable.Type self.Routes = routeTable.Routes diff --git a/pkg/compute/models/schedtagresource.go b/pkg/compute/models/schedtagresource.go index ababd05a4c..e4c75fa971 100644 --- a/pkg/compute/models/schedtagresource.go +++ b/pkg/compute/models/schedtagresource.go @@ -172,3 +172,23 @@ func (manager *SSchedtagResourceBaseManager) GetOrderBySubQuery( func (manager *SSchedtagResourceBaseManager) GetOrderByFields(query api.SchedtagFilterListInput) []string { return []string{query.OrderBySchedtag, query.OrderByResourceType} } + +func InsertJointResourceSchedtag(ctx context.Context, jointMan ISchedtagJointManager, resourceId, schedtagId string) (ISchedtagJointModel, error) { + newTagObj, err := db.NewModelObject(jointMan) + if err != nil { + return nil, errors.Wrap(err, "NewModelObject") + } + + objectKey := jointMan.GetResourceIdKey(jointMan) + createData := jsonutils.NewDict() + createData.Add(jsonutils.NewString(schedtagId), "schedtag_id") + createData.Add(jsonutils.NewString(resourceId), objectKey) + if err := createData.Unmarshal(newTagObj); err != nil { + return nil, errors.Wrapf(err, "Create %s joint schedtag", jointMan.Keyword()) + } + if err := newTagObj.GetModelManager().TableSpec().Insert(ctx, newTagObj); err != nil { + return nil, errors.Wrap(err, "Insert to database") + } + + return newTagObj.(ISchedtagJointModel), nil +} diff --git a/pkg/compute/models/schedtags.go b/pkg/compute/models/schedtags.go index 01b476e195..625af2bdb8 100644 --- a/pkg/compute/models/schedtags.go +++ b/pkg/compute/models/schedtags.go @@ -18,6 +18,7 @@ import ( "context" "database/sql" "fmt" + "reflect" "strings" "yunion.io/x/jsonutils" @@ -133,6 +134,10 @@ func (manager *SSchedtagManager) GetResourceTypes() []string { return ret } +func (manager *SSchedtagManager) GetJointManager(resTypePlural string) ISchedtagJointManager { + return manager.jointsManager[resTypePlural] +} + type SSchedtag struct { db.SStandaloneResourceBase db.SScopedResourceBase @@ -386,14 +391,33 @@ func (self *SSchedtag) ValidateDeleteCondition(ctx context.Context) error { return self.SStandaloneResourceBase.ValidateDeleteCondition(ctx) } -func (self *SSchedtag) GetObjects(objs interface{}) error { +// GetObjectPtr wraps the given value with pointer: V => *V, *V => **V, etc. +func GetObjectPtr(obj interface{}) interface{} { + v := reflect.ValueOf(obj) + pt := reflect.PtrTo(v.Type()) + pv := reflect.New(pt.Elem()) + pv.Elem().Set(v) + + return pv.Interface() +} + +func (self *SSchedtag) GetResources() ([]IModelWithSchedtag, error) { + objs := make([]interface{}, 0) q := self.GetObjectQuery() - masterMan := self.GetJointManager().GetMasterManager() - err := db.FetchModelObjects(masterMan, q, objs) + masterMan, err := self.GetResourceManager() if err != nil { - return err + return nil, err } - return nil + if err := db.FetchModelObjects(masterMan, q, &objs); err != nil { + return nil, err + } + + ret := make([]IModelWithSchedtag, len(objs)) + for i := range objs { + obj := objs[i] + ret[i] = GetObjectPtr(obj).(IModelWithSchedtag) + } + return ret, nil } func (self *SSchedtag) GetObjectQuery() *sqlchemy.SQuery { @@ -410,7 +434,16 @@ func (self *SSchedtag) GetObjectQuery() *sqlchemy.SQuery { } func (self *SSchedtag) GetJointManager() ISchedtagJointManager { - return SchedtagManager.jointsManager[self.ResourceType] + return SchedtagManager.GetJointManager(self.ResourceType) +} + +func (s *SSchedtag) GetResourceManager() (db.IStandaloneModelManager, error) { + jResMan := s.GetJointManager() + if jResMan == nil { + return nil, errors.Errorf("Not found bind joint resource manager by type %q", s.ResourceType) + } + + return jResMan.GetMasterManager(), nil } func (self *SSchedtag) GetObjectCount() (int, error) { @@ -585,19 +618,8 @@ func PerformSetResourceSchedtag(obj IModelWithSchedtag, ctx context.Context, use jointMan := obj.GetSchedtagJointManager() for _, setTagId := range setTagsId { if !utils.IsInStringArray(setTagId, oldTagIds) { - if newTagObj, err := db.NewModelObject(jointMan); err != nil { - return nil, httperrors.NewGeneralError(err) - } else { - objectKey := jointMan.GetResourceIdKey(jointMan) - createData := jsonutils.NewDict() - createData.Add(jsonutils.NewString(setTagId), "schedtag_id") - createData.Add(jsonutils.NewString(obj.GetId()), objectKey) - if err := createData.Unmarshal(newTagObj); err != nil { - return nil, httperrors.NewGeneralError(fmt.Errorf("Create %s joint schedtag error: %v", jointMan.Keyword(), err)) - } - if err := newTagObj.GetModelManager().TableSpec().Insert(ctx, newTagObj); err != nil { - return nil, httperrors.NewGeneralError(err) - } + if _, err := InsertJointResourceSchedtag(ctx, jointMan, obj.GetId(), setTagId); err != nil { + return nil, errors.Wrapf(err, "InsertJointResourceSchedtag %s %s", obj.GetId(), setTagId) } } } @@ -649,3 +671,78 @@ func (s *SSchedtag) AllowPerformSetScope(ctx context.Context, userCred mcclient. func (s *SSchedtag) PerformSetScope(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { return db.PerformSetScope(ctx, s, userCred, data) } + +func (s *SSchedtag) GetJointResourceTag(resId string) (ISchedtagJointModel, error) { + jMan := s.GetJointManager() + jObj, err := db.FetchJointByIds(jMan, resId, s.GetId(), nil) + if err != nil { + return nil, err + } + return jObj.(ISchedtagJointModel), nil +} + +func (s *SSchedtag) PerformSetResource(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.SchedtagSetResourceInput) (jsonutils.JSONObject, error) { + if input == nil { + return nil, nil + } + + setResIds := make(map[string]IModelWithSchedtag, 0) + resMan, err := s.GetResourceManager() + if err != nil { + return nil, errors.Wrap(err, "get resource manager") + } + + // get need set resource ids + for i := 0; i < len(input.ResourceIds); i++ { + resId := input.ResourceIds[i] + res, err := resMan.FetchByIdOrName(userCred, resId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("Resource %s %s not found", s.ResourceType, resId) + } + return nil, errors.Wrapf(err, "Fetch resource %s by id or name", s.ResourceType) + } + setResIds[res.GetId()] = res.(IModelWithSchedtag) + } + + // unbind current resources if not in input + curRess, err := s.GetResources() + if err != nil { + return nil, errors.Wrap(err, "Get current bind resources") + } + curResIds := make([]string, 0) + for i := range curRess { + res := curRess[i] + if _, ok := setResIds[res.GetId()]; ok { + curResIds = append(curResIds, res.GetId()) + continue + } + jObj, err := s.GetJointResourceTag(res.GetId()) + if err != nil { + return nil, errors.Wrapf(err, "Get joint resource tag by id %s", res.GetId()) + } + if err := jObj.Detach(ctx, userCred); err != nil { + return nil, errors.Wrap(err, "detach joint tag") + } + } + + // bind input resources + jointMan := s.GetJointManager() + for resId := range setResIds { + if utils.IsInStringArray(resId, curResIds) { + // already binded + continue + } + + if _, err := InsertJointResourceSchedtag(ctx, jointMan, resId, s.GetId()); err != nil { + return nil, errors.Wrapf(err, "InsertJointResourceSchedtag %s %s", resId, s.GetId()) + } + + res := setResIds[resId] + if err := res.ClearSchedDescCache(); err != nil { + log.Errorf("Resource %s/%s ClearSchedDescCache error: %v", res.Keyword(), res.GetId(), err) + } + } + + return nil, nil +} diff --git a/pkg/compute/models/skus_tools.go b/pkg/compute/models/skus_tools.go index 76c74fcf00..2423367eda 100644 --- a/pkg/compute/models/skus_tools.go +++ b/pkg/compute/models/skus_tools.go @@ -27,6 +27,7 @@ import ( "yunion.io/x/pkg/errors" v "yunion.io/x/pkg/util/version" + apis "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/mcclient" @@ -161,13 +162,36 @@ func (self *SSkuResourcesMeta) GetServerSkusByRegionExternalId(regionExternalId return result, nil } +func getElaticCacheSkuRegionExtId(regionExtId string) string { + if strings.HasPrefix(regionExtId, apis.CLOUD_ACCESS_ENV_ALIYUN_FINANCE) && strings.HasSuffix(regionExtId, "cn-hangzhou") { + return regionExtId + "-finance" + } + + return regionExtId +} + +func getElaticCacheSkuZoneId(zoneExtId string) string { + if strings.HasPrefix(zoneExtId, apis.CLOUD_ACCESS_ENV_ALIYUN_FINANCE) { + if strings.HasSuffix(zoneExtId, "cn-hangzhou-finance-b") || strings.HasSuffix(zoneExtId, "cn-hangzhou-finance-c") || strings.HasSuffix(zoneExtId, "cn-hangzhou-finance-d") { + zoneExtId = strings.Replace(zoneExtId, "-finance", "", -1) + } else if strings.Contains(zoneExtId, "cn-hangzhou") { + zoneExtId = strings.Replace(zoneExtId, "-finance", "", 1) + } + } + + return zoneExtId +} + func (self *SSkuResourcesMeta) GetElasticCacheSkusByRegionExternalId(regionExternalId string) ([]SElasticcacheSku, error) { regionId, zoneMaps, err := self.GetRegionIdAndZoneMaps(regionExternalId) if err != nil { return nil, errors.Wrap(err, "GetRegionIdAndZoneMaps") } result := []SElasticcacheSku{} - objs, err := self.getSkusByRegion(self.ElasticCacheBase, regionExternalId) + + // aliyun finance cloud + remoteRegion := getElaticCacheSkuRegionExtId(regionExternalId) + objs, err := self.getSkusByRegion(self.ElasticCacheBase, remoteRegion) if err != nil { return nil, errors.Wrap(err, "getSkusByRegion") } @@ -179,16 +203,16 @@ func (self *SSkuResourcesMeta) GetElasticCacheSkusByRegionExternalId(regionExter return nil, errors.Wrapf(err, "obj.Unmarshal") } if len(sku.ZoneId) > 0 { - zoneId := self.getZoneIdBySuffix(zoneMaps, sku.ZoneId) + zoneId := self.getZoneIdBySuffix(zoneMaps, getElaticCacheSkuZoneId(sku.ZoneId)) if len(zoneId) == 0 { - return nil, fmt.Errorf("invalid sku %s %s master zoneId: %s", sku.Id, sku.CloudregionId, sku.ZoneId) + return nil, fmt.Errorf("invalid sku %s %s master zoneId: %s", sku.Id, sku.CloudregionId, getElaticCacheSkuZoneId(sku.ZoneId)) } sku.ZoneId = zoneId } if len(sku.SlaveZoneId) > 0 { - zoneId := self.getZoneIdBySuffix(zoneMaps, sku.SlaveZoneId) + zoneId := self.getZoneIdBySuffix(zoneMaps, getElaticCacheSkuZoneId(sku.SlaveZoneId)) if len(zoneId) == 0 { - return nil, fmt.Errorf("invalid sku %s %s slave zoneId: %s", sku.Id, sku.CloudregionId, sku.SlaveZoneId) + return nil, fmt.Errorf("invalid sku %s %s slave zoneId: %s", sku.Id, sku.CloudregionId, getElaticCacheSkuZoneId(sku.SlaveZoneId)) } sku.SlaveZoneId = zoneId } diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index b09d1d5409..0defdf5daf 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -129,6 +129,17 @@ func (self *SVpc) GetDnsZones() ([]SDnsZone, error) { return dnsZones, nil } +func (self *SVpc) GetInterVpcNetworks() ([]SInterVpcNetwork, error) { + sq := InterVpcNetworkVpcManager.Query("inter_vpc_network_id").Equals("vpc_id", self.Id) + q := InterVpcNetworkManager.Query().In("id", sq.SubQuery()) + vpcNetworks := []SInterVpcNetwork{} + err := db.FetchModelObjects(InterVpcNetworkManager, q, &vpcNetworks) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return vpcNetworks, nil +} + func (self *SVpc) GetDnsZoneCount() (int, error) { sq := DnsZoneVpcManager.Query("dns_zone_id").Equals("vpc_id", self.Id) q := DnsZoneManager.Query().In("id", sq.SubQuery()) @@ -271,7 +282,7 @@ func (manager *SVpcManager) GetOrCreateVpcForClassicNetwork(ctx context.Context, vpc.IsDefault = false vpc.CloudregionId = region.Id vpc.SetModelManager(manager, vpc) - vpc.Name = fmt.Sprintf("emulated vpc for %s %s classic network", region.Name, cloudprovider.Name) + vpc.Name = "-" vpc.IsEmulated = true vpc.SetEnabled(false) vpc.Status = api.VPC_STATUS_UNAVAILABLE @@ -719,6 +730,26 @@ func (manager *SVpcManager) InitializeData() error { } } + { + vpcs := []SVpc{} + q := manager.Query().IsTrue("is_emulated").IsNotEmpty("external_id").NotEquals("name", "-") + err := db.FetchModelObjects(manager, q, &vpcs) + if err != nil { + return errors.Wrapf(err, "db.FetchModelObjects") + } + for i := range vpcs { + if vpcs[i].ExternalId == manager.getVpcExternalIdForClassicNetwork(vpcs[i].CloudregionId, vpcs[i].ManagerId) { + _, err = db.Update(&vpcs[i], func() error { + vpcs[i].Name = "-" + return nil + }) + if err != nil { + return errors.Wrapf(err, "db.Update class vpc name") + } + } + } + } + return nil } @@ -916,6 +947,17 @@ func (self *SVpc) RealDelete(ctx context.Context, userCred mcclient.TokenCredent } } + vpcNetwork, err := self.GetInterVpcNetworks() + if err != nil { + return errors.Wrapf(err, "self.GetInterVpcNetwork") + } + for i := range vpcNetwork { + err = vpcNetwork[i].RemoveVpc(ctx, self.Id) + if err != nil { + return errors.Wrapf(err, "remove vpc from InterVpcNetwork %s", vpcNetwork[i].Id) + } + } + return self.SEnabledStatusInfrasResourceBase.Delete(ctx, userCred) } @@ -1027,6 +1069,18 @@ func (manager *SVpcManager) ListItemFilter( q = q.In("id", sq.SubQuery()) } + if len(query.InterVpcNetworkId) > 0 { + vpcNetwork, err := InterVpcNetworkManager.FetchByIdOrName(userCred, query.InterVpcNetworkId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("inter_vpc_network", query.InterVpcNetworkId) + } + return nil, httperrors.NewGeneralError(err) + } + sq := InterVpcNetworkVpcManager.Query("vpc_id").Equals("inter_vpc_network_id", vpcNetwork.GetId()) + q = q.In("id", sq.SubQuery()) + } + usable := (query.Usable != nil && *query.Usable) vpcUsable := (query.UsableVpc != nil && *query.UsableVpc) if vpcUsable || usable { diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index 37019f0547..da20fac620 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -1000,25 +1000,26 @@ func (self *SAliyunRegionDriver) ValidateCreateDBInstanceData(ctx context.Contex return input, httperrors.NewInputParameterError("slave dbinstance not support prepaid billing type") } - wire := network.GetWire() - if wire == nil { - return input, httperrors.NewGeneralError(fmt.Errorf("failed to found wire for network %s(%s)", network.Name, network.Id)) - } - zone := wire.GetZone() - if zone == nil { - return input, httperrors.NewGeneralError(fmt.Errorf("failed to found zone for wire %s(%s)", wire.Name, wire.Id)) - } - - match := false - for _, sku := range skus { - if utils.IsInStringArray(zone.Id, []string{sku.Zone1, sku.Zone2, sku.Zone3}) { - match = true - break + if network != nil { + wire := network.GetWire() + if wire == nil { + return input, httperrors.NewGeneralError(fmt.Errorf("failed to found wire for network %s(%s)", network.Name, network.Id)) + } + zone := wire.GetZone() + if zone == nil { + return input, httperrors.NewGeneralError(fmt.Errorf("failed to found zone for wire %s(%s)", wire.Name, wire.Id)) } - } - if !match { - return input, httperrors.NewInputParameterError("failed to match any skus in the network %s(%s) zone %s(%s)", network.Name, network.Id, zone.Name, zone.Id) + match := false + for _, sku := range skus { + if utils.IsInStringArray(zone.Id, []string{sku.Zone1, sku.Zone2, sku.Zone3}) { + match = true + break + } + } + if !match { + return input, httperrors.NewInputParameterError("failed to match any skus in the network %s(%s) zone %s(%s)", network.Name, network.Id, zone.Name, zone.Id) + } } var master *models.SDBInstance @@ -1204,6 +1205,7 @@ func (self *SAliyunRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont // validate sku billingType, _ := data.GetString("billing_type") zone := zoneV.Model.(*models.SZone) + network := networkV.Model.(*models.SNetwork) if sku, err := data.GetString("instance_type"); err != nil || len(sku) == 0 { return nil, httperrors.NewMissingParameterError("instance_type") } else { @@ -1213,7 +1215,7 @@ func (self *SAliyunRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont } skuModel := _skuModel.(*models.SElasticcacheSku) - if err := ValidateElasticcacheSku(zone.Id, billingType, skuModel); err != nil { + if err := ValidateElasticcacheSku(zone.Id, billingType, skuModel, network); err != nil { return nil, err } else { data.Set("instance_type", jsonutils.NewString(skuModel.InstanceSpec)) @@ -1237,7 +1239,6 @@ func (self *SAliyunRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont data.Set("billing_cycle", jsonutils.NewString(cycle.String())) } - network := networkV.Model.(*models.SNetwork) vpc := network.GetVpc() if vpc == nil { return nil, httperrors.NewNotFoundError("network %s related vpc not found", network.GetId()) @@ -1369,7 +1370,7 @@ func (self *SAliyunRegionDriver) ValidateCreateElasticcacheAccountData(ctx conte accountPrivilegeV.Value) } - return data, nil + return self.SManagedVirtualizationRegionDriver.ValidateCreateElasticcacheAccountData(ctx, userCred, ownerId, data) } func (self *SAliyunRegionDriver) RequestCreateElasticcacheAccount(ctx context.Context, userCred mcclient.TokenCredential, ea *models.SElasticcacheAccount, task taskman.ITask) error { diff --git a/pkg/compute/regiondrivers/apsara.go b/pkg/compute/regiondrivers/apsara.go new file mode 100644 index 0000000000..fd3e0efa18 --- /dev/null +++ b/pkg/compute/regiondrivers/apsara.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 regiondrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SApsaraRegionDriver struct { + SAliyunRegionDriver +} + +func init() { + driver := SApsaraRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SApsaraRegionDriver) GetProvider() string { + return api.CLOUD_PROVIDER_APSARA +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index eafdfc5f90..18f952e588 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -295,6 +295,10 @@ func (self *SBaseRegionDriver) RequestCreateDBInstance(ctx context.Context, user return fmt.Errorf("Not Implement RequestCreateDBInstance") } +func (self *SBaseRegionDriver) RequestCreateDBInstanceFromBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateDBInstanceFromBackup") +} + func (self *SBaseRegionDriver) RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, backup *models.SDBInstanceBackup, task taskman.ITask) error { return fmt.Errorf("Not Implement RequestCreateDBInstanceBackup") } diff --git a/pkg/compute/regiondrivers/huawei.go b/pkg/compute/regiondrivers/huawei.go index 63c146ab3a..733641267c 100644 --- a/pkg/compute/regiondrivers/huawei.go +++ b/pkg/compute/regiondrivers/huawei.go @@ -2170,12 +2170,13 @@ func (self *SHuaWeiRegionDriver) ValidateCreateDBInstanceData(ctx context.Contex return input, httperrors.NewInputParameterError("The disk_size_gb must be an integer multiple of 10") } - if len(input.Password) == 0 { - return input, httperrors.NewMissingParameterError("password") + if len(input.Password) == 0 { // 华为云RDS必须要有密码 + resetPassword := true + input.ResetPassword = &resetPassword } if len(input.SecgroupIds) == 0 { - input.SecgroupIds = []string{"default"} + input.SecgroupIds = []string{api.SECGROUP_DEFAULT_ID} } return input, nil @@ -2339,7 +2340,7 @@ func validatorSlaveZones(ownerId mcclient.IIdentityProvider, data *jsonutils.JSO skuModel := _skuModel.(*models.SElasticcacheSku) for _, zoneId := range zones { - if err := ValidateElasticcacheSku(zoneId, chargeType, skuModel); err != nil { + if err := ValidateElasticcacheSku(zoneId, chargeType, skuModel, nil); err != nil { return err } } @@ -2349,11 +2350,17 @@ func validatorSlaveZones(ownerId mcclient.IIdentityProvider, data *jsonutils.JSO return nil } -func ValidateElasticcacheSku(zoneId string, chargeType string, sku *models.SElasticcacheSku) error { +func ValidateElasticcacheSku(zoneId string, chargeType string, sku *models.SElasticcacheSku, network *models.SNetwork) error { if sku.ZoneId != zoneId { return httperrors.NewResourceNotFoundError("zone mismatch, elastic cache sku zone %s != %s", sku.ZoneId, zoneId) } + if network != nil { + if zone := network.GetZone(); zone != nil && zone.Id != sku.ZoneId { + return httperrors.NewResourceNotFoundError("elastic cache sku zone (%s) and subnet zone (%s) mismatch", sku.ZoneId, zone.Id) + } + } + if chargeType == billing_api.BILLING_TYPE_PREPAID { if sku.PrepaidStatus != api.SkuStatusAvailable { return httperrors.NewOutOfResourceError("sku %s is soldout", sku.Name) @@ -2408,8 +2415,8 @@ func (self *SHuaWeiRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont sku := instanceTypeV.Model.(*models.SElasticcacheSku) zoneId, _ := data.GetString("zone_id") billingType, _ := data.GetString("billing_type") - - if err := ValidateElasticcacheSku(zoneId, billingType, sku); err != nil { + network := networkV.Model.(*models.SNetwork) + if err := ValidateElasticcacheSku(zoneId, billingType, sku, network); err != nil { return nil, err } else { data.Set("instance_type", jsonutils.NewString(sku.InstanceSpec)) @@ -2461,7 +2468,6 @@ func (self *SHuaWeiRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont data.Set("billing_cycle", jsonutils.NewString(cycle.String())) } - network := networkV.Model.(*models.SNetwork) vpc := network.GetVpc() if vpc == nil { return nil, httperrors.NewNotFoundError("network %s related vpc not found", network.GetId()) diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index 8cdf288196..fc76b988f0 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -1801,6 +1801,85 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont return nil } +func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstanceFromBackup(ctx context.Context, userCred mcclient.TokenCredential, rds *models.SDBInstance, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + _backup, err := models.DBInstanceBackupManager.FetchById(rds.DBInstancebackupId) + if err != nil { + return nil, errors.Wrapf(err, "DBInstanceBackupManager.FetchById(%s)", rds.DBInstancebackupId) + } + backup := _backup.(*models.SDBInstanceBackup) + iBackup, err := backup.GetIDBInstanceBackup() + if err != nil { + return nil, errors.Wrapf(err, "backup.GetIDBInstanceBackup") + } + vpc, err := rds.GetVpc() + if err != nil { + return nil, errors.Wrap(err, "rds.GetVpc()") + } + desc := cloudprovider.SManagedDBInstanceCreateConfig{ + Name: rds.Name, + Description: rds.Description, + StorageType: rds.StorageType, + DiskSizeGB: rds.DiskSizeGB, + VcpuCount: rds.VcpuCount, + VmemSizeMb: rds.VmemSizeMb, + VpcId: vpc.ExternalId, + Engine: rds.Engine, + EngineVersion: rds.EngineVersion, + Category: rds.Category, + Port: rds.Port, + } + if len(backup.DBInstanceId) > 0 { + parentRds, err := backup.GetDBInstance() + if err != nil { + return nil, errors.Wrapf(err, "backup.GetDBInstance") + } + desc.RdsId = parentRds.ExternalId + } + + log.Debugf("create from backup params: %s", jsonutils.Marshal(desc).String()) + + networks, err := rds.GetDBNetworks() + if err != nil { + return nil, errors.Wrapf(err, "dbinstance.GetDBNetworks") + } + + if len(networks) > 0 { + net, err := networks[0].GetNetwork() + if err != nil { + return nil, errors.Wrapf(err, "GetNetwork") + } + desc.NetworkId, desc.Address = net.ExternalId, networks[0].IpAddr + } + if rds.BillingType == billing_api.BILLING_TYPE_PREPAID { + bc, err := billing.ParseBillingCycle(rds.BillingCycle) + if err != nil { + log.Errorf("failed to parse billing cycle %s: %v", rds.BillingCycle, err) + } else if bc.IsValid() { + desc.BillingCycle = &bc + desc.BillingCycle.AutoRenew = rds.AutoRenew + } + } + + iRds, err := iBackup.CreateICloudDBInstance(&desc) + if err != nil { + return nil, errors.Wrapf(err, "iBackup.CreateICloudDBInstance") + } + + err = db.SetExternalId(rds, userCred, iRds.GetGlobalId()) + if err != nil { + return nil, errors.Wrapf(err, "db.SetExternalId") + } + + err = cloudprovider.WaitStatus(iRds, api.DBINSTANCE_RUNNING, time.Second*5, time.Hour*1) + if err != nil { + return nil, errors.Wrapf(err, "cloudprovider.WaitStatus runing") + } + return nil, nil + }) + return nil +} + func (self *SManagedVirtualizationRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error { task.ScheduleRun(nil) return nil @@ -2210,7 +2289,11 @@ func (self *SManagedVirtualizationRegionDriver) RequestElasticcacheUpdateBackupP } func (self *SManagedVirtualizationRegionDriver) ValidateCreateElasticcacheAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, nil + if name, _ := data.GetString("name"); name == "root" { + return nil, httperrors.NewConflictError("account name 'root' is not allowed") + } + + return data, nil } func (self *SManagedVirtualizationRegionDriver) ValidateCreateElasticcacheAclData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { @@ -2433,14 +2516,9 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstanceBackup(ct return nil, errors.Wrapf(err, "backup.GetIDBInstanceBackup") } - _, err = db.Update(backup, func() error { - backup.StartTime = iBackup.GetStartTime() - backup.EndTime = iBackup.GetEndTime() - backup.BackupSizeMb = iBackup.GetBackupSizeMb() - return nil - }) + err = backup.SyncWithCloudDBInstanceBackup(ctx, userCred, iBackup, instance.GetCloudprovider()) if err != nil { - return nil, errors.Wrap(err, "db.Update") + log.Warningf("sync backup info error: %v", err) } instance.SetStatus(userCred, api.DBINSTANCE_RUNNING, "") diff --git a/pkg/compute/regiondrivers/qcloud.go b/pkg/compute/regiondrivers/qcloud.go index 36c7e6ec4b..bb50ce3f2c 100644 --- a/pkg/compute/regiondrivers/qcloud.go +++ b/pkg/compute/regiondrivers/qcloud.go @@ -1486,7 +1486,8 @@ func (self *SQcloudRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont billingType, _ := data.GetString("billing_type") // validate sku sku := instanceTypeV.Model.(*models.SElasticcacheSku) - if err := ValidateElasticcacheSku(zoneId, billingType, sku); err != nil { + network := networkV.Model.(*models.SNetwork) + if err := ValidateElasticcacheSku(zoneId, billingType, sku, network); err != nil { return nil, err } else { data.Set("instance_type", jsonutils.NewString(sku.InstanceSpec)) @@ -1528,7 +1529,6 @@ func (self *SQcloudRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont data.Set("billing_cycle", jsonutils.NewString(cycle.String())) } - network := networkV.Model.(*models.SNetwork) vpc := network.GetVpc() if vpc == nil { return nil, httperrors.NewNotFoundError("network %s related vpc not found", network.GetId()) @@ -1701,7 +1701,7 @@ func (self *SQcloudRegionDriver) ValidateCreateElasticcacheAccountData(ctx conte return nil, httperrors.NewWeakPasswordError() } - return data, nil + return self.SManagedVirtualizationRegionDriver.ValidateCreateElasticcacheAccountData(ctx, userCred, ownerId, data) } func (self *SQcloudRegionDriver) RequestCreateElasticcacheAccount(ctx context.Context, userCred mcclient.TokenCredential, ea *models.SElasticcacheAccount, task taskman.ITask) error { diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index f0c0a75760..81d78a7bb7 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -156,6 +156,7 @@ func InitHandlers(app *appsrv.Application) { models.RouteTableManager, models.RouteTableAssociationManager, models.RouteTableRouteSetManager, + models.InterVpcNetworkRouteSetManager, models.SchedpolicyManager, models.DynamicschedtagManager, @@ -198,6 +199,7 @@ func InitHandlers(app *appsrv.Application) { models.DnsTrafficPolicyManager, models.VpcPeeringConnectionManager, + models.InterVpcNetworkManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) @@ -228,6 +230,7 @@ func InitHandlers(app *appsrv.Application) { models.DnsZoneVpcManager, models.DBInstanceSecgroupManager, models.ElasticcachesecgroupManager, + models.InterVpcNetworkVpcManager, } { db.RegisterModelManager(manager) handler := db.NewJointModelHandler(manager) diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index cad171557b..20df50b80c 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -98,6 +98,7 @@ func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderPreInfoComplete(ctx co taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) { provider.SyncCallSyncCloudproviderRegions(ctx, self.UserCred, syncRange) + provider.SyncCallSyncCloudproviderInterVpcNetwork(ctx, self.UserCred) return nil, nil }) } diff --git a/pkg/compute/tasks/dbinstance_create_task.go b/pkg/compute/tasks/dbinstance_create_task.go index 021e32e44d..9eb6ff426d 100644 --- a/pkg/compute/tasks/dbinstance_create_task.go +++ b/pkg/compute/tasks/dbinstance_create_task.go @@ -48,12 +48,17 @@ func (self *DBInstanceCreateTask) OnInit(ctx context.Context, obj db.IStandalone self.CreateDBInstance(ctx, dbinstance) } -func (self *DBInstanceCreateTask) CreateDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { - region := dbinstance.GetRegion() +func (self *DBInstanceCreateTask) CreateDBInstance(ctx context.Context, rds *models.SDBInstance) { + region := rds.GetRegion() self.SetStage("OnCreateDBInstanceComplete", nil) - err := region.GetDriver().RequestCreateDBInstance(ctx, self.UserCred, dbinstance, self) + var err error + if len(rds.DBInstancebackupId) > 0 { + err = region.GetDriver().RequestCreateDBInstanceFromBackup(ctx, self.UserCred, rds, self) + } else { + err = region.GetDriver().RequestCreateDBInstance(ctx, self.UserCred, rds, self) + } if err != nil { - self.taskFailed(ctx, dbinstance, err) + self.taskFailed(ctx, rds, err) return } } diff --git a/pkg/compute/tasks/guest_backup_tasks.go b/pkg/compute/tasks/guest_backup_tasks.go index 012e761aa9..e862287c7c 100644 --- a/pkg/compute/tasks/guest_backup_tasks.go +++ b/pkg/compute/tasks/guest_backup_tasks.go @@ -142,7 +142,7 @@ func (self *GuestSwitchToBackupTask) OnComplete(ctx context.Context, guest *mode } func (self *GuestSwitchToBackupTask) OnSwitched(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { - if err := guest.SetMetadata(ctx, "__mirror_job_status", "", self.UserCred); err != nil { + if err := guest.SetMetadata(ctx, api.MIRROR_JOB, "", self.UserCred); err != nil { self.OnSwitchedFailed(ctx, guest, jsonutils.NewString("guest set metadata failed")) return } @@ -256,13 +256,13 @@ func (self *GuestStartAndSyncToBackupTask) OnStartBackupGuest(ctx context.Contex } func (self *GuestStartAndSyncToBackupTask) OnStartBackupGuestFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { - guest.SetMetadata(ctx, "__mirror_job_status", "failed", self.UserCred) + guest.SetMetadata(ctx, api.MIRROR_JOB, api.MIRROR_JOB_FAILED, self.UserCred) db.OpsLog.LogEvent(guest, db.ACT_BACKUP_START_FAILED, data.String(), self.UserCred) self.SetStageFailed(ctx, data) } func (self *GuestStartAndSyncToBackupTask) OnRequestSyncToBackup(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { - guest.SetMetadata(ctx, "__mirror_job_status", "", self.UserCred) + guest.SetMetadata(ctx, api.MIRROR_JOB, "", self.UserCred) guest.SetStatus(self.UserCred, api.VM_BLOCK_STREAM, "OnSyncToBackup") self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/guest_deploy_task.go b/pkg/compute/tasks/guest_deploy_task.go index 3db594cee8..692ffcc088 100644 --- a/pkg/compute/tasks/guest_deploy_task.go +++ b/pkg/compute/tasks/guest_deploy_task.go @@ -44,7 +44,7 @@ func (self *GuestDeployTask) OnInit(ctx context.Context, obj db.IStandaloneModel func (self *GuestDeployTask) OnGuestNetworkReady(ctx context.Context, guest *models.SGuest) { self.SetStage("OnDeployWaitServerStop", nil) if jsonutils.QueryBoolean(self.Params, "restart", false) { - guest.StartGuestStopTask(ctx, self.UserCred, false, self.GetTaskId()) + guest.StartGuestStopTask(ctx, self.UserCred, false, false, self.GetTaskId()) } else { // Note: have to use LocalTaskRun, run to another place implement OnDeployWaitServerStop taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) { diff --git a/pkg/compute/tasks/guest_rebuild_root_task.go b/pkg/compute/tasks/guest_rebuild_root_task.go index 84ce1b8b6e..9846d87c48 100644 --- a/pkg/compute/tasks/guest_rebuild_root_task.go +++ b/pkg/compute/tasks/guest_rebuild_root_task.go @@ -45,7 +45,7 @@ func (self *GuestRebuildRootTask) OnInit(ctx context.Context, obj db.IStandalone guest := obj.(*models.SGuest) if jsonutils.QueryBoolean(self.Params, "need_stop", false) { self.SetStage("OnStopServerComplete", nil) - guest.StartGuestStopTask(ctx, self.UserCred, false, self.GetTaskId()) + guest.StartGuestStopTask(ctx, self.UserCred, false, false, self.GetTaskId()) } else { self.StartRebuildRootDisk(ctx, guest) } diff --git a/pkg/compute/tasks/guest_reset_task.go b/pkg/compute/tasks/guest_reset_task.go index 620c14ce93..ddf04baf2b 100644 --- a/pkg/compute/tasks/guest_reset_task.go +++ b/pkg/compute/tasks/guest_reset_task.go @@ -57,7 +57,7 @@ func (self *GuestHardResetTask) OnInit(ctx context.Context, obj db.IStandaloneMo func (self *GuestHardResetTask) StopServer(ctx context.Context, guest *models.SGuest) { guest.SetStatus(self.UserCred, api.VM_STOPPING, "") self.SetStage("OnServerStopComplete", nil) - guest.StartGuestStopTask(ctx, self.UserCred, false, self.GetTaskId()) + guest.StartGuestStopTask(ctx, self.UserCred, false, false, self.GetTaskId()) // logclient.AddActionLogWith(guest, logclient.ACT_VM_RESTART, `{"is_force": true}`, self.UserCred, true) } @@ -81,6 +81,6 @@ type GuestRestartTask struct { func (self *GuestRestartTask) StopServer(ctx context.Context, guest *models.SGuest) { self.SetStage("OnServerStopComplete", nil) isForce := jsonutils.QueryBoolean(self.Params, "is_force", false) - guest.StartGuestStopTask(ctx, self.UserCred, isForce, self.GetTaskId()) + guest.StartGuestStopTask(ctx, self.UserCred, isForce, false, self.GetTaskId()) // logclient.AddActionLog(guest, logclient.ACT_VM_RESTART, `{"is_force": false}`, self.UserCred, true) } diff --git a/pkg/compute/tasks/guest_save_image_task.go b/pkg/compute/tasks/guest_save_image_task.go index 5ae0645d19..df86d41fcd 100644 --- a/pkg/compute/tasks/guest_save_image_task.go +++ b/pkg/compute/tasks/guest_save_image_task.go @@ -39,7 +39,7 @@ func (self *GuestSaveImageTask) OnInit(ctx context.Context, obj db.IStandaloneMo log.Infof("Saving server image: %s", guest.Name) if restart, _ := self.GetParams().Bool("restart"); restart { self.SetStage("OnStopServerComplete", nil) - guest.StartGuestStopTask(ctx, self.GetUserCred(), false, self.GetTaskId()) + guest.StartGuestStopTask(ctx, self.GetUserCred(), false, false, self.GetTaskId()) } else { self.OnStopServerComplete(ctx, guest, nil) } diff --git a/pkg/compute/tasks/ha_guest_start_task.go b/pkg/compute/tasks/ha_guest_start_task.go index 5c54c33e1e..5e211b7aa2 100644 --- a/pkg/compute/tasks/ha_guest_start_task.go +++ b/pkg/compute/tasks/ha_guest_start_task.go @@ -46,12 +46,15 @@ func (self *HAGuestStartTask) RequestStartBacking(ctx context.Context, guest *mo self.SetStage("OnStartBackupGuestComplete", nil) host := models.HostManager.FetchHostById(guest.BackupHostId) guest.SetStatus(self.UserCred, api.VM_BACKUP_STARTING, "") + result, err := guest.GetDriver().RequestStartOnHost(ctx, guest, host, self.UserCred, self) if err != nil { self.OnStartCompleteFailed(ctx, guest, jsonutils.NewString(err.Error())) } else { if result != nil && jsonutils.QueryBoolean(result, "is_running", false) { - self.OnStartBackupGuestComplete(ctx, guest, nil) + self.RequestStart(ctx, guest) + } else { + self.OnStartCompleteFailed(ctx, guest, jsonutils.NewString("start backup guest failed")) } } } @@ -71,6 +74,7 @@ func (self *HAGuestStartTask) OnStartBackupGuestComplete( return } } + self.RequestStart(ctx, guest) } diff --git a/pkg/compute/tasks/inter_vpc_network_add_vpc_task.go b/pkg/compute/tasks/inter_vpc_network_add_vpc_task.go new file mode 100644 index 0000000000..0362952930 --- /dev/null +++ b/pkg/compute/tasks/inter_vpc_network_add_vpc_task.go @@ -0,0 +1,108 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type InterVpcNetworkAddVpcTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(InterVpcNetworkAddVpcTask{}) +} + +func (self *InterVpcNetworkAddVpcTask) taskFailed(ctx context.Context, network *models.SInterVpcNetwork, err error) { + network.SetStatus(self.UserCred, api.INTER_VPC_NETWORK_STATUS_ADDVPC_FAILED, err.Error()) + db.OpsLog.LogEvent(network, db.ACT_NETWORK_ADD_VPC, err, self.UserCred) + logclient.AddActionLogWithStartable(self, network, logclient.ACT_NETWORK_ADD_VPC, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *InterVpcNetworkAddVpcTask) taskComplete(ctx context.Context, network *models.SInterVpcNetwork) { + network.SetStatus(self.GetUserCred(), api.INTER_VPC_NETWORK_STATUS_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, network, logclient.ACT_NETWORK_ADD_VPC, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkAddVpcTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + interVpcNetwork := obj.(*models.SInterVpcNetwork) + vpcId, err := self.Params.GetString("vpc_id") + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, `self.Params.GetString("vpc_id")`)) + return + } + _vpc, err := models.VpcManager.FetchById(vpcId) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, `models.VpcManager.FetchById(vpcId)`)) + return + } + vpc := _vpc.(*models.SVpc) + iVpc, err := vpc.GetIVpc() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, ` vpc.GetIVpc()`)) + return + } + + iVpcNetwork, err := interVpcNetwork.GetICloudInterVpcNetwork() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, "GetICloudInterVpcNetwork()")) + return + } + + joinVpcOPts := cloudprovider.SVpcJointInterVpcNetworkOption{ + InterVpcNetworkId: iVpcNetwork.GetId(), + NetworkAuthorityOwnerId: iVpcNetwork.GetAuthorityOwnerId(), + } + err = iVpc.ProposeJoinICloudInterVpcNetwork(&joinVpcOPts) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrapf(err, "iVpc.ProposeJoinICloudInterVpcNetwork(%s)", jsonutils.Marshal(joinVpcOPts).String())) + return + } + + addVpcOpts := cloudprovider.SInterVpcNetworkAttachVpcOption{ + VpcId: iVpc.GetId(), + VpcRegionId: iVpc.GetRegion().GetId(), + VpcAuthorityOwnerId: iVpc.GetAuthorityOwnerId(), + } + err = iVpcNetwork.AttachVpc(&addVpcOpts) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrapf(err, " iVpcNetwork.AddVpc(%s)", jsonutils.Marshal(addVpcOpts).String())) + return + } + + self.SetStage("OnSyncInterVpcNetworkComplete", nil) + models.StartResourceSyncStatusTask(ctx, self.GetUserCred(), interVpcNetwork, "InterVpcNetworkSyncstatusTask", self.GetTaskId()) +} + +func (self *InterVpcNetworkAddVpcTask) OnSyncInterVpcNetworkComplete(ctx context.Context, network *models.SInterVpcNetwork, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkAddVpcTask) OnSyncInterVpcNetworkCompleteFailed(ctx context.Context, network *models.SInterVpcNetwork, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data) +} diff --git a/pkg/compute/tasks/inter_vpc_network_create_task.go b/pkg/compute/tasks/inter_vpc_network_create_task.go new file mode 100644 index 0000000000..caaecb4ef5 --- /dev/null +++ b/pkg/compute/tasks/inter_vpc_network_create_task.go @@ -0,0 +1,75 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type InterVpcNetworkCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(InterVpcNetworkCreateTask{}) +} + +func (self *InterVpcNetworkCreateTask) taskFailed(ctx context.Context, network *models.SInterVpcNetwork, err error) { + network.SetStatus(self.UserCred, api.INTER_VPC_NETWORK_STATUS_CREATE_FAILED, err.Error()) + db.OpsLog.LogEvent(network, db.ACT_CREATE, err, self.UserCred) + logclient.AddActionLogWithStartable(self, network, logclient.ACT_CREATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *InterVpcNetworkCreateTask) taskComplete(ctx context.Context, network *models.SInterVpcNetwork) { + network.SetStatus(self.GetUserCred(), api.INTER_VPC_NETWORK_STATUS_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, network, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + interVpcNetwork := obj.(*models.SInterVpcNetwork) + provider, err := interVpcNetwork.GetProvider() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrapf(err, "GetProvider")) + return + } + opts := &cloudprovider.SInterVpcNetworkCreateOptions{ + Name: interVpcNetwork.Name, + Desc: interVpcNetwork.Description, + } + + inetwork, err := provider.CreateICloudInterVpcNetwork(opts) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, "provider.CreateICloudInterVpcNetwork()")) + return + } + err = interVpcNetwork.SyncWithCloudInterVpcNetwork(ctx, self.UserCred, inetwork) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, "snetwork.SyncWithCloudInterVpcNetwork()")) + return + } + self.taskComplete(ctx, interVpcNetwork) +} diff --git a/pkg/compute/tasks/inter_vpc_network_delete_task.go b/pkg/compute/tasks/inter_vpc_network_delete_task.go new file mode 100644 index 0000000000..993c09478e --- /dev/null +++ b/pkg/compute/tasks/inter_vpc_network_delete_task.go @@ -0,0 +1,73 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type InterVpcNetworkDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(InterVpcNetworkDeleteTask{}) +} + +func (self *InterVpcNetworkDeleteTask) taskFailed(ctx context.Context, network *models.SInterVpcNetwork, err error) { + network.SetStatus(self.UserCred, api.INTER_VPC_NETWORK_STATUS_DELETE_FAILED, err.Error()) + db.OpsLog.LogEvent(network, db.ACT_DELETE, err, self.UserCred) + logclient.AddActionLogWithStartable(self, network, logclient.ACT_DELETE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *InterVpcNetworkDeleteTask) taskComplete(ctx context.Context, network *models.SInterVpcNetwork) { + logclient.AddActionLogWithStartable(self, network, logclient.ACT_DELETE, nil, self.UserCred, true) + network.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + interVpcNetwork := obj.(*models.SInterVpcNetwork) + provider, err := interVpcNetwork.GetProvider() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrapf(err, "GetProvider")) + return + } + if len(interVpcNetwork.ExternalId) == 0 { + self.taskComplete(ctx, interVpcNetwork) + return + } + inetwork, err := provider.GetICloudInterVpcNetworkById(interVpcNetwork.ExternalId) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, "provider.GetICloudInterVpcNetworkById()")) + return + } + err = inetwork.Delete() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, "inetwork.Delete")) + return + } + self.taskComplete(ctx, interVpcNetwork) +} diff --git a/pkg/compute/tasks/inter_vpc_network_remove_vpc_task.go b/pkg/compute/tasks/inter_vpc_network_remove_vpc_task.go new file mode 100644 index 0000000000..17c760ff94 --- /dev/null +++ b/pkg/compute/tasks/inter_vpc_network_remove_vpc_task.go @@ -0,0 +1,100 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type InterVpcNetworkRemoveVpcTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(InterVpcNetworkRemoveVpcTask{}) +} + +func (self *InterVpcNetworkRemoveVpcTask) taskFailed(ctx context.Context, network *models.SInterVpcNetwork, err error) { + network.SetStatus(self.UserCred, api.INTER_VPC_NETWORK_STATUS_REMOVEVPC_FAILED, err.Error()) + db.OpsLog.LogEvent(network, db.ACT_NETWORK_REMOVE_VPC, err, self.UserCred) + logclient.AddActionLogWithStartable(self, network, logclient.ACT_NETWORK_REMOVE_VPC, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *InterVpcNetworkRemoveVpcTask) taskComplete(ctx context.Context, network *models.SInterVpcNetwork) { + network.SetStatus(self.GetUserCred(), api.INTER_VPC_NETWORK_STATUS_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, network, logclient.ACT_NETWORK_REMOVE_VPC, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkRemoveVpcTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + interVpcNetwork := obj.(*models.SInterVpcNetwork) + vpcId, err := self.Params.GetString("vpc_id") + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, `self.Params.GetString("vpc_id")`)) + return + } + + _vpc, err := models.VpcManager.FetchById(vpcId) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, `models.VpcManager.FetchById(vpcId)`)) + return + } + vpc := _vpc.(*models.SVpc) + iVpc, err := vpc.GetIVpc() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, ` vpc.GetIVpc()`)) + return + } + + iVpcNetwork, err := interVpcNetwork.GetICloudInterVpcNetwork() + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrap(err, "GetICloudInterVpcNetwork()")) + return + } + + removeVpcOpts := cloudprovider.SInterVpcNetworkDetachVpcOption{ + VpcId: iVpc.GetId(), + VpcRegionId: iVpc.GetRegion().GetId(), + VpcAuthorityOwnerId: iVpc.GetAuthorityOwnerId(), + } + + err = iVpcNetwork.DetachVpc(&removeVpcOpts) + if err != nil { + self.taskFailed(ctx, interVpcNetwork, errors.Wrapf(err, "iVpcNetwork.RemoveVpc(%s)", jsonutils.Marshal(removeVpcOpts).String())) + return + } + + self.SetStage("OnSyncInterVpcNetworkComplete", nil) + models.StartResourceSyncStatusTask(ctx, self.GetUserCred(), interVpcNetwork, "InterVpcNetworkSyncstatusTask", self.GetTaskId()) +} + +func (self *InterVpcNetworkRemoveVpcTask) OnSyncInterVpcNetworkComplete(ctx context.Context, network *models.SInterVpcNetwork, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkRemoveVpcTask) OnSyncInterVpcNetworkCompleteFailed(ctx context.Context, network *models.SInterVpcNetwork, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data) +} diff --git a/pkg/compute/tasks/inter_vpc_network_routeset_updatestatus_task.go b/pkg/compute/tasks/inter_vpc_network_routeset_updatestatus_task.go new file mode 100644 index 0000000000..b5e61d4c52 --- /dev/null +++ b/pkg/compute/tasks/inter_vpc_network_routeset_updatestatus_task.go @@ -0,0 +1,101 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type InterVpcNetworkUpdateRoutesetTask struct { + taskman.STask +} + +func (self *InterVpcNetworkUpdateRoutesetTask) taskFailed(ctx context.Context, network *models.SInterVpcNetwork, err error) { + network.SetStatus(self.GetUserCred(), api.INTER_VPC_NETWORK_STATUS_UPDATEROUTE_FAILED, err.Error()) + db.OpsLog.LogEvent(network, db.ACT_NETWORK_MODIFY_ROUTE, err, self.GetUserCred()) + logclient.AddActionLogWithContext(ctx, network, logclient.ACT_NETWORK_MODIFY_ROUTE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func init() { + taskman.RegisterTask(InterVpcNetworkUpdateRoutesetTask{}) +} + +func (self *InterVpcNetworkUpdateRoutesetTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + network := obj.(*models.SInterVpcNetwork) + action, err := self.Params.GetString("action") + if err != nil { + self.taskFailed(ctx, network, errors.Wrapf(err, "self.Params.GetString(action)")) + return + } + + routeSetId, err := self.Params.GetString("inter_vpc_network_route_set_id") + if err != nil { + self.taskFailed(ctx, network, errors.Wrapf(err, "self.Params.GetString(inter_vpc_network_route_set_id)")) + return + } + + _routeSet, err := models.InterVpcNetworkRouteSetManager.FetchById(routeSetId) + if err != nil { + self.taskFailed(ctx, network, errors.Wrapf(err, "InterVpcNetworkRouteSetManager.FetchById(routeSetId)")) + return + } + routeSet := _routeSet.(*models.SInterVpcNetworkRouteSet) + + iNetwork, err := network.GetICloudInterVpcNetwork() + if err != nil { + self.taskFailed(ctx, network, errors.Wrap(err, "GetICloudInterVpcNetwork()")) + return + } + + switch action { + case "enable": + err := iNetwork.EnableRouteEntry(routeSet.ExternalId) + if err != nil { + self.taskFailed(ctx, network, errors.Wrapf(err, "iNetwork.EnableRouteEntry(%s)", routeSet.ExternalId)) + return + } + case "disable": + err := iNetwork.DisableRouteEntry(routeSet.ExternalId) + if err != nil { + self.taskFailed(ctx, network, errors.Wrapf(err, "iNetwork.DisableRouteEntry(%s)", routeSet.ExternalId)) + return + } + default: + self.taskFailed(ctx, network, errors.Wrapf(err, "invalid InterVpcNetworkRoute update action")) + return + } + logclient.AddActionLogWithContext(ctx, network, logclient.ACT_NETWORK_MODIFY_ROUTE, err, self.UserCred, true) + + self.SetStage("OnSyncInterVpcNetworkComplete", nil) + models.StartResourceSyncStatusTask(ctx, self.GetUserCred(), network, "InterVpcNetworkSyncstatusTask", self.GetTaskId()) +} + +func (self *InterVpcNetworkUpdateRoutesetTask) OnSyncInterVpcNetworkComplete(ctx context.Context, network *models.SInterVpcNetwork, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *InterVpcNetworkUpdateRoutesetTask) OnSyncInterVpcNetworkCompleteFailed(ctx context.Context, network *models.SInterVpcNetwork, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data) +} diff --git a/pkg/compute/tasks/inter_vpc_network_syncstatus_task.go b/pkg/compute/tasks/inter_vpc_network_syncstatus_task.go new file mode 100644 index 0000000000..01cc1cd7af --- /dev/null +++ b/pkg/compute/tasks/inter_vpc_network_syncstatus_task.go @@ -0,0 +1,67 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type InterVpcNetworkSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(InterVpcNetworkSyncstatusTask{}) +} + +func (self *InterVpcNetworkSyncstatusTask) taskFail(ctx context.Context, peer *models.SInterVpcNetwork, err error) { + peer.SetStatus(self.UserCred, api.VPC_PEERING_CONNECTION_STATUS_UNKNOWN, err.Error()) + db.OpsLog.LogEvent(peer, db.ACT_SYNC_STATUS, err, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, peer, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *InterVpcNetworkSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + snetwork := obj.(*models.SInterVpcNetwork) + inetwork, err := snetwork.GetICloudInterVpcNetwork() + if err != nil { + self.taskFail(ctx, snetwork, errors.Wrap(err, "GetICloudInterVpcNetwork()")) + return + } + + err = snetwork.SyncWithCloudInterVpcNetwork(ctx, self.UserCred, inetwork) + if err != nil { + self.taskFail(ctx, snetwork, errors.Wrap(err, "snetwork.SyncWithCloudInterVpcNetwork()")) + return + } + + result := snetwork.SyncInterVpcNetworkRouteSets(ctx, self.UserCred, inetwork) + if result.IsError() { + self.taskFail(ctx, snetwork, errors.Wrapf(result.AllError(), " snetwork.SyncInterVpcNetworkRouteSets")) + return + } + + logclient.AddActionLogWithStartable(self, snetwork, logclient.ACT_SYNC_STATUS, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/route_table_syncstatus_task.go b/pkg/compute/tasks/route_table_syncstatus_task.go index c1e46f65e1..b735d9dc46 100644 --- a/pkg/compute/tasks/route_table_syncstatus_task.go +++ b/pkg/compute/tasks/route_table_syncstatus_task.go @@ -37,8 +37,8 @@ func init() { func (self *RouteTableSyncStatusTask) taskFailed(ctx context.Context, routeTable *models.SRouteTable, err error) { routeTable.SetStatus(self.GetUserCred(), api.ROUTE_TABLE_UNKNOWN, err.Error()) - db.OpsLog.LogEvent(routeTable, db.ACT_CREATE, routeTable.GetShortDesc(ctx), self.GetUserCred()) - logclient.AddActionLogWithContext(ctx, routeTable, logclient.ACT_UPDATE, err, self.UserCred, false) + db.OpsLog.LogEvent(routeTable, db.ACT_SYNC_STATUS, err, self.GetUserCred()) + logclient.AddActionLogWithContext(ctx, routeTable, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } diff --git a/pkg/compute/tasks/route_table_update_task.go b/pkg/compute/tasks/route_table_update_task.go index bd61394967..f02118b678 100644 --- a/pkg/compute/tasks/route_table_update_task.go +++ b/pkg/compute/tasks/route_table_update_task.go @@ -34,7 +34,7 @@ type RouteTableUpdateTask struct { func (self *RouteTableUpdateTask) taskFailed(ctx context.Context, routeTable *models.SRouteTable, err error) { routeTable.SetStatus(self.GetUserCred(), api.ROUTE_TABLE_UPDATEFAILED, err.Error()) - db.OpsLog.LogEvent(routeTable, db.ACT_CREATE, routeTable.GetShortDesc(ctx), self.GetUserCred()) + db.OpsLog.LogEvent(routeTable, db.ACT_UPDATE, err, self.GetUserCred()) logclient.AddActionLogWithContext(ctx, routeTable, logclient.ACT_UPDATE, err, self.UserCred, false) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } @@ -79,26 +79,33 @@ func (self *RouteTableUpdateTask) OnInit(ctx context.Context, obj db.IStandalone switch action { case "create": err = iRouteTable.CreateRoute(cloudRouteSet) + if err != nil { + self.taskFailed(ctx, routeTable, errors.Wrapf(err, "iRouteTable.CreateRoute(%s)", jsonutils.Marshal(cloudRouteSet).String())) + return + } case "update": err = iRouteTable.UpdateRoute(cloudRouteSet) + if err != nil { + self.taskFailed(ctx, routeTable, errors.Wrapf(err, "iRouteTable.UpdateRoute(%s)", jsonutils.Marshal(cloudRouteSet).String())) + return + } case "delete": err = iRouteTable.RemoveRoute(cloudRouteSet) + if err != nil { + self.taskFailed(ctx, routeTable, errors.Wrapf(err, "iRouteTable.RemoveRoute(%s)", jsonutils.Marshal(cloudRouteSet).String())) + return + } default: self.taskFailed(ctx, routeTable, errors.Wrapf(err, "invalid routetable update action")) return } - - if err != nil { - self.taskFailed(ctx, routeTable, errors.Wrapf(err, "iRouteTable.CreateRoute(%s)", jsonutils.Marshal(cloudRouteSet).String())) - return - } + logclient.AddActionLogWithContext(ctx, routeTable, logclient.ACT_UPDATE, nil, self.UserCred, true) self.SetStage("OnSyncRouteTableComplete", nil) models.StartResourceSyncStatusTask(ctx, self.GetUserCred(), routeTable, "RouteTableSyncStatusTask", self.GetTaskId()) } func (self *RouteTableUpdateTask) OnSyncRouteTableComplete(ctx context.Context, routeTable *models.SRouteTable, data jsonutils.JSONObject) { - routeTable.SetStatus(self.GetUserCred(), api.DNS_ZONE_STATUS_AVAILABLE, "") self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/vpc_peering_connection_create_task.go b/pkg/compute/tasks/vpc_peering_connection_create_task.go index 3d96a61e5b..e762349c93 100644 --- a/pkg/compute/tasks/vpc_peering_connection_create_task.go +++ b/pkg/compute/tasks/vpc_peering_connection_create_task.go @@ -38,8 +38,8 @@ func init() { func (self *VpcPeeringConnectionCreateTask) taskFailed(ctx context.Context, peer *models.SVpcPeeringConnection, err error) { peer.SetStatus(self.UserCred, api.VPC_PEERING_CONNECTION_STATUS_CREATE_FAILED, err.Error()) - db.OpsLog.LogEvent(peer, db.ACT_ALLOCATE_FAIL, err, self.UserCred) - logclient.AddActionLogWithStartable(self, peer, logclient.ACT_ALLOCATE, err, self.UserCred, false) + db.OpsLog.LogEvent(peer, db.ACT_CREATE, err, self.UserCred) + logclient.AddActionLogWithStartable(self, peer, logclient.ACT_CREATE, err, self.UserCred, false) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } diff --git a/pkg/compute/tasks/vpc_peering_connection_delete_task.go b/pkg/compute/tasks/vpc_peering_connection_delete_task.go index cb5e28f2e1..c3ded2565f 100644 --- a/pkg/compute/tasks/vpc_peering_connection_delete_task.go +++ b/pkg/compute/tasks/vpc_peering_connection_delete_task.go @@ -38,7 +38,7 @@ func init() { func (self *VpcPeeringConnectionDeleteTask) taskFailed(ctx context.Context, peer *models.SVpcPeeringConnection, err error) { peer.SetStatus(self.UserCred, api.VPC_PEERING_CONNECTION_STATUS_DELETE_FAILED, err.Error()) - db.OpsLog.LogEvent(peer, db.ACT_DELOCATE_FAIL, err, self.UserCred) + db.OpsLog.LogEvent(peer, db.ACT_DELETE, err, self.UserCred) logclient.AddActionLogWithStartable(self, peer, logclient.ACT_DELETE, err, self.UserCred, false) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } diff --git a/pkg/hostman/guestman/guestman.go b/pkg/hostman/guestman/guestman.go index b94a0ae9ff..514f05454c 100644 --- a/pkg/hostman/guestman/guestman.go +++ b/pkg/hostman/guestman/guestman.go @@ -801,7 +801,7 @@ func (m *SGuestManager) StartDriveMirror(ctx context.Context, params interface{} if err := guest.SaveDesc(mirrorParams.Desc); err != nil { return nil, err } - task := NewDriveMirrorTask(ctx, guest, mirrorParams.NbdServerUri, "top", nil) + task := NewDriveMirrorTask(ctx, guest, mirrorParams.NbdServerUri, "top", true, nil) task.Start() return nil, nil } diff --git a/pkg/hostman/guestman/guesttasks.go b/pkg/hostman/guestman/guesttasks.go index 802c1ea2f0..4a303cdb65 100644 --- a/pkg/hostman/guestman/guesttasks.go +++ b/pkg/hostman/guestman/guesttasks.go @@ -1071,15 +1071,17 @@ func (s *SGuestSnapshotDeleteTask) onResumeSucc(res string) { type SDriveMirrorTask struct { *SKVMGuestInstance - ctx context.Context - nbdUri string - onSucc func() - syncMode string - index int + ctx context.Context + nbdUri string + onSucc func() + syncMode string + index int + blockReplication bool } func NewDriveMirrorTask( - ctx context.Context, s *SKVMGuestInstance, nbdUri, syncMode string, onSucc func(), + ctx context.Context, s *SKVMGuestInstance, nbdUri, syncMode string, + blockReplication bool, onSucc func(), ) *SDriveMirrorTask { return &SDriveMirrorTask{ SKVMGuestInstance: s, @@ -1087,6 +1089,7 @@ func NewDriveMirrorTask( nbdUri: nbdUri, syncMode: syncMode, onSucc: onSucc, + blockReplication: blockReplication, } } @@ -1094,17 +1097,34 @@ func (s *SDriveMirrorTask) Start() { s.startMirror("") } +func (s *SDriveMirrorTask) supportBlockReplication() bool { + c := make(chan bool) + s.Monitor.HumanMonitorCommand("help drive_mirror", func(res string) { + if strings.Index(res, "[-c]") > 0 { + c <- true + } else { + c <- false + } + }) + return <-c +} + func (s *SDriveMirrorTask) startMirror(res string) { log.Infof("drive mirror results:%s", res) if len(res) > 0 { hostutils.TaskFailed(s.ctx, res) return } + var blockReplication = false + if s.blockReplication && s.supportBlockReplication() { + blockReplication = true + log.Infof("mirror block replication supported") + } disks, _ := s.Desc.GetArray("disks") if s.index < len(disks) { target := fmt.Sprintf("%s:exportname=drive_%d", s.nbdUri, s.index) s.Monitor.DriveMirror(s.startMirror, fmt.Sprintf("drive_%d", s.index), - target, s.syncMode, true) + target, s.syncMode, true, blockReplication) s.index += 1 } else { if s.onSucc != nil { diff --git a/pkg/hostman/guestman/qemu-kvm.go b/pkg/hostman/guestman/qemu-kvm.go index ba8634151e..15174f2c7b 100644 --- a/pkg/hostman/guestman/qemu-kvm.go +++ b/pkg/hostman/guestman/qemu-kvm.go @@ -515,18 +515,6 @@ func (s *SKVMGuestInstance) SyncMirrorJobFailed(reason string) { } } -func (s *SKVMGuestInstance) OnSlaveStartedWithNbdServer(nbdServerPort int64) { - params := jsonutils.NewDict() - params.Set("nbd_server_port", jsonutils.NewInt(nbdServerPort)) - _, err := modules.Servers.PerformAction( - hostutils.GetComputeSession(context.Background()), - s.GetId(), "slave-started", params, - ) - if err != nil { - log.Errorf("Server %s perform resume guest got error %s", s.GetId(), err) - } -} - func (s *SKVMGuestInstance) onMonitorConnected(ctx context.Context) { log.Infof("Monitor connected ...") s.Monitor.GetVersion(func(v string) { @@ -592,14 +580,14 @@ func (s *SKVMGuestInstance) startDiskBackupMirror(ctx context.Context) { cb := func(res string) { log.Infof("On backup mirror server(%s) resume start", s.Id) } s.Monitor.SimpleCommand("cont", cb) } - NewDriveMirrorTask(ctx, s, nbdUri, "top", onSucc).Start() + NewDriveMirrorTask(ctx, s, nbdUri, "top", true, onSucc).Start() } } func (s *SKVMGuestInstance) startQemuBuiltInNbdServer(ctx context.Context) { - nbdServerPort := s.manager.GetFreePortByBase(BUILT_IN_NBD_SERVER_PORT_BASE) - var onNbdServerStarted = func(res string) { - if ctx != nil && len(appctx.AppContextTaskId(ctx)) > 0 { + if ctx != nil && len(appctx.AppContextTaskId(ctx)) > 0 { + nbdServerPort := s.manager.GetFreePortByBase(BUILT_IN_NBD_SERVER_PORT_BASE) + var onNbdServerStarted = func(res string) { if len(res) > 0 { log.Errorf("Start Qemu Builtin nbd server error %s", res) hostutils.TaskFailed(ctx, res) @@ -608,11 +596,9 @@ func (s *SKVMGuestInstance) startQemuBuiltInNbdServer(ctx context.Context) { res.Set("nbd_server_port", jsonutils.NewInt(int64(nbdServerPort))) hostutils.TaskComplete(ctx, res) } - } else { - s.OnSlaveStartedWithNbdServer(int64(nbdServerPort)) } + s.Monitor.StartNbdServer(nbdServerPort, true, true, onNbdServerStarted) } - s.Monitor.StartNbdServer(nbdServerPort, true, true, onNbdServerStarted) } func (s *SKVMGuestInstance) clearCgroup(pid int) { diff --git a/pkg/hostman/host_health/health_manager.go b/pkg/hostman/host_health/health_manager.go index f0367bb9d4..06978f83cd 100644 --- a/pkg/hostman/host_health/health_manager.go +++ b/pkg/hostman/host_health/health_manager.go @@ -17,9 +17,11 @@ package host_health import ( "context" "fmt" + "os" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/hostman/guestman" "yunion.io/x/onecloud/pkg/hostman/options" @@ -83,6 +85,8 @@ func (m *SHostHealthManager) OnUnhealth() { if m.onHostDown == SHUTDOWN_SERVERS { m.shutdownServers() } + utils.DumpAllGoroutineStack(log.Logger().Out) + os.Exit(1) } func (m *SHostHealthManager) SetOnHostDown(onHostDown string) { diff --git a/pkg/hostman/hostdeployer/apis/deploy.pb.go b/pkg/hostman/hostdeployer/apis/deploy.pb.go index 4ca6e888cd..57f9af55b8 100644 --- a/pkg/hostman/hostdeployer/apis/deploy.pb.go +++ b/pkg/hostman/hostdeployer/apis/deploy.pb.go @@ -6,11 +6,12 @@ package apis import ( context "context" fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - math "math" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/hostman/hostinfo/hostinfo.go b/pkg/hostman/hostinfo/hostinfo.go index 5c35988976..9b57210c19 100644 --- a/pkg/hostman/hostinfo/hostinfo.go +++ b/pkg/hostman/hostinfo/hostinfo.go @@ -148,10 +148,28 @@ func (h *SHostInfo) IsHugepagesEnabled() bool { return h.enableHugePages || options.HostOptions.HugepagesOption == "native" } +/* In this order init host service: + * 1. prepare env, fix environment variable path + * 2. detect hostinfo, fill host capability and custom host field + * 3. prepare hostbridge, start openvswitch service + * 4. parse host config, config ip address + * 5. check is ovn support, setup ovn chassis + */ func (h *SHostInfo) Init() error { if err := h.prepareEnv(); err != nil { return err } + + log.Infof("Start detectHostInfo") + if err := h.detectHostInfo(); err != nil { + return err + } + + if err := hostbridge.Prepare(options.HostOptions.BridgeDriver); err != nil { + log.Errorln(err) + return err + } + log.Infof("Start parseConfig") if err := h.parseConfig(); err != nil { return err @@ -162,15 +180,6 @@ func (h *SHostInfo) Init() error { } } - log.Infof("Start detectHostInfo") - if err := h.detectHostInfo(); err != nil { - return err - } - if err := hostbridge.Prepare(options.HostOptions.BridgeDriver); err != nil { - log.Errorln(err) - return err - } - return nil } @@ -1599,12 +1608,17 @@ func (h *SHostInfo) stop() { } func (h *SHostInfo) unregister() { - h.stopped = true - _, err := modules.Hosts.PerformAction( - h.GetSession(), h.HostId, "offline", nil) - if err != nil { - log.Errorln(err) + for { + _, err := modules.Hosts.PerformAction( + h.GetSession(), h.HostId, "offline", nil) + if err != nil { + log.Errorf("put host offline failed: %s", err) + time.Sleep(time.Second * 1) + } else { + break + } } + h.stopped = true } func (h *SHostInfo) OnCatalogChanged(catalog mcclient.KeystoneServiceCatalogV3) { diff --git a/pkg/hostman/monitor/hmp.go b/pkg/hostman/monitor/hmp.go index 69a325f10e..3abdf84f43 100644 --- a/pkg/hostman/monitor/hmp.go +++ b/pkg/hostman/monitor/hmp.go @@ -384,8 +384,11 @@ func (m *HmpMonitor) ReloadDiskBlkdev(device, path string, callback StringCallba m.Query(fmt.Sprintf("reload_disk_snapshot_blkdev -n %s %s", device, path), callback) } -func (m *HmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode string, unmap bool) { +func (m *HmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode string, unmap, blockReplication bool) { cmd := "drive_mirror -n" + if blockReplication { + cmd += " -c" + } if syncMode == "full" { cmd += " -f" } diff --git a/pkg/hostman/monitor/monitor.go b/pkg/hostman/monitor/monitor.go index 92169089f7..d54fd03d6e 100644 --- a/pkg/hostman/monitor/monitor.go +++ b/pkg/hostman/monitor/monitor.go @@ -60,7 +60,7 @@ type Monitor interface { DeviceAdd(dev string, params map[string]interface{}, callback StringCallback) BlockStream(drive string, callback StringCallback) - DriveMirror(callback StringCallback, drive, target, syncMode string, unmap bool) + DriveMirror(callback StringCallback, drive, target, syncMode string, unmap, blockReplication bool) MigrateSetCapability(capability, state string, callback StringCallback) Migrate(destStr string, copyIncremental, copyFull bool, callback StringCallback) diff --git a/pkg/hostman/monitor/qmp.go b/pkg/hostman/monitor/qmp.go index 6b35b12b3a..d1e064728d 100644 --- a/pkg/hostman/monitor/qmp.go +++ b/pkg/hostman/monitor/qmp.go @@ -692,22 +692,27 @@ func (m *QmpMonitor) ReloadDiskBlkdev(device, path string, callback StringCallba m.Query(cmd, cb) } -func (m *QmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode string, unmap bool) { +func (m *QmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode string, unmap, blockReplication bool) { var ( cb = func(res *Response) { callback(m.actionResult(res)) } - cmd = &Command{ - Execute: "drive-mirror", - Args: map[string]interface{}{ - "device": drive, - "target": target, - "mode": "existing", - "sync": syncMode, - "unmap": unmap, - }, + args = map[string]interface{}{ + "device": drive, + "target": target, + "mode": "existing", + "sync": syncMode, + "unmap": unmap, } ) + if blockReplication { + args["block-replication"] = true + } + cmd := &Command{ + Execute: "drive-mirror", + Args: args, + } + m.Query(cmd, cb) } diff --git a/pkg/hostman/storageman/storage_agent.go b/pkg/hostman/storageman/storage_agent.go index 96168781c0..208cf325aa 100644 --- a/pkg/hostman/storageman/storage_agent.go +++ b/pkg/hostman/storageman/storage_agent.go @@ -358,7 +358,10 @@ func (as *SAgentStorage) waitVmToolsVersion(ctx context.Context, vm *esxi.SVirtu } timeUpper = time.Now().Add(timeout) for vm.GetStatus() == api.VM_RUNNING && time.Now().Before(timeUpper) { - vm.StopVM(ctx, true) + opts := &cloudprovider.ServerStopOptions{ + IsForce: true, + } + vm.StopVM(ctx, opts) time.Sleep(5 * time.Second) } return diff --git a/pkg/keystone/driver/oidc/oidc.go b/pkg/keystone/driver/oidc/oidc.go index 4b79bd1f87..c0ef8ceec4 100644 --- a/pkg/keystone/driver/oidc/oidc.go +++ b/pkg/keystone/driver/oidc/oidc.go @@ -47,7 +47,10 @@ func NewOIDCDriver(idpId, idpName, template, targetDomainId string, conf api.TCo if err != nil { return nil, errors.Wrap(err, "NewBaseIdentityDriver") } - drv := SOIDCDriver{SBaseIdentityDriver: base} + drv := SOIDCDriver{ + SBaseIdentityDriver: base, + isDebug: false, + } drv.SetVirtualObject(&drv) err = drv.prepareConfig() if err != nil { diff --git a/pkg/keystone/models/domains.go b/pkg/keystone/models/domains.go index 128ce5f3c1..57198da3c8 100644 --- a/pkg/keystone/models/domains.go +++ b/pkg/keystone/models/domains.go @@ -205,6 +205,11 @@ func (manager *SDomainManager) ListItemFilter( q = q.In("id", subq.SubQuery()) } + if len(query.IdpEntityId) > 0 { + subq := IdmappingManager.Query("public_id").Equals("local_id", query.IdpEntityId).Equals("entity_type", api.IdMappingEntityDomain) + q = q.Equals("id", subq.SubQuery()) + } + return q, nil } diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index ead4eb60e9..9e2567ce31 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -400,6 +400,11 @@ func (manager *SUserManager) ListItemFilter( q = q.In("id", subq.SubQuery()) } + if len(query.IdpEntityId) > 0 { + subq := IdmappingManager.Query("public_id").Equals("local_id", query.IdpEntityId).Equals("entity_type", api.IdMappingEntityUser) + q = q.Equals("id", subq.SubQuery()) + } + return q, nil } diff --git a/pkg/mcclient/modules/mod_cloudevents.go b/pkg/mcclient/modules/mod_cloudevents.go index 63ecd33180..85cd69086f 100644 --- a/pkg/mcclient/modules/mod_cloudevents.go +++ b/pkg/mcclient/modules/mod_cloudevents.go @@ -23,7 +23,7 @@ var ( func init() { Cloudevents = NewCloudeventManager("cloudevent", "cloudevents", []string{"Action", "Service", "Success", - "Resource_Type", "Cloudprovider_Id", "Manager", "Provider"}, + "Resource_Type", "Cloudprovider_Id", "Manager", "Provider", "Domain", "Domain_Id"}, []string{}) register(&Cloudevents) diff --git a/pkg/mcclient/modules/mod_inter_vpc_network.go b/pkg/mcclient/modules/mod_inter_vpc_network.go new file mode 100644 index 0000000000..eae8372369 --- /dev/null +++ b/pkg/mcclient/modules/mod_inter_vpc_network.go @@ -0,0 +1,29 @@ +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + InterVpcNetworks modulebase.ResourceManager +) + +func init() { + InterVpcNetworks = NewComputeManager("inter_vpc_network", "inter_vpc_networks", + []string{"ID", "Name", "Enabled", "Status", "manager_id", "Public_Scope", "Domain_Id", "Domain"}, + []string{}) + + registerCompute(&InterVpcNetworks) +} diff --git a/pkg/mcclient/modules/mod_inter_vpc_network_routeset.go b/pkg/mcclient/modules/mod_inter_vpc_network_routeset.go new file mode 100644 index 0000000000..89172007da --- /dev/null +++ b/pkg/mcclient/modules/mod_inter_vpc_network_routeset.go @@ -0,0 +1,48 @@ +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +type InterVpcNetworkRouteSetManager struct { + modulebase.ResourceManager +} + +var ( + InterVpcNetworkRouteSets InterVpcNetworkRouteSetManager +) + +func init() { + InterVpcNetworkRouteSets = InterVpcNetworkRouteSetManager{ + NewComputeManager( + "inter_vpc_network_route_set", + "inter_vpc_network_route_sets", + []string{ + "id", + "inter_vpc_network_id", + "name", + "enabled", + "status", + "cidr", + "vpc_id", + "ext_instance_id", + "ext_instance_type", + "ext_instance_region_id", + }, + []string{}, + ), + } + registerCompute(&InterVpcNetworkRouteSets) +} diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 7b3938a904..686fcadb94 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -19,6 +19,8 @@ import ( "io/ioutil" "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" ) type CloudaccountListOptions struct { @@ -759,9 +761,9 @@ func (opts *SVMwareCloudAccountPrepareNetsOptions) Params() (jsonutils.JSONObjec } type SApsaraCloudAccountCreateOptions struct { - SAliyunCloudAccountCreateOptions - - AuthUrl string `help:"Auth Url" positional:"true"` + SCloudAccountCreateBaseOptions + cloudprovider.SApsaraEndpoints + SAccessKeyCredential } func (opts *SApsaraCloudAccountCreateOptions) Params() (jsonutils.JSONObject, error) { diff --git a/pkg/mcclient/options/dbinstances.go b/pkg/mcclient/options/dbinstances.go index 9f8e9eb913..f901883b3c 100644 --- a/pkg/mcclient/options/dbinstances.go +++ b/pkg/mcclient/options/dbinstances.go @@ -22,23 +22,24 @@ import ( ) type DBInstanceCreateOptions struct { - NAME string `help:"DBInstance Name"` - InstanceType string `help:"InstanceType for DBInstance"` - VcpuCount int `help:"Core of cpu for DBInstance"` - VmemSizeMb int `help:"Memory size of DBInstance"` - Port int `help:"Port of DBInstance"` - Category string `help:"Category of DBInstance"` - Network string `help:"Network of DBInstance"` - Address string `help:"Address of DBInstance"` - Engine string `help:"Engine of DBInstance"` - EngineVersion string `help:"EngineVersion of DBInstance Engine"` - StorageType string `help:"StorageTyep of DBInstance"` - Secgroup string `help:"Secgroup name or Id for DBInstance"` - Zone string `help:"ZoneId or name for DBInstance"` - DiskSizeGB int `help:"Storage size for DBInstance"` - Duration string `help:"Duration for DBInstance"` - AllowDelete *bool `help:"not lock dbinstance" ` - Tags []string `help:"Tags info,prefix with 'user:', eg: user:project=default" json:"-"` + NAME string `help:"DBInstance Name"` + InstanceType string `help:"InstanceType for DBInstance"` + VcpuCount int `help:"Core of cpu for DBInstance"` + VmemSizeMb int `help:"Memory size of DBInstance"` + Port int `help:"Port of DBInstance"` + Category string `help:"Category of DBInstance"` + Network string `help:"Network of DBInstance"` + Address string `help:"Address of DBInstance"` + Engine string `help:"Engine of DBInstance"` + EngineVersion string `help:"EngineVersion of DBInstance Engine"` + StorageType string `help:"StorageTyep of DBInstance"` + Secgroup string `help:"Secgroup name or Id for DBInstance"` + Zone string `help:"ZoneId or name for DBInstance"` + DiskSizeGB int `help:"Storage size for DBInstance"` + Duration string `help:"Duration for DBInstance"` + AllowDelete *bool `help:"not lock dbinstance" ` + Tags []string `help:"Tags info,prefix with 'user:', eg: user:project=default" json:"-"` + DBInstancebackupId string `help:"create dbinstance from backup" json:"dbinstancebackup_id"` } func (opts *DBInstanceCreateOptions) Params() (*jsonutils.JSONDict, error) { diff --git a/pkg/mcclient/options/inter_vpc_network.go b/pkg/mcclient/options/inter_vpc_network.go new file mode 100644 index 0000000000..2026507b0a --- /dev/null +++ b/pkg/mcclient/options/inter_vpc_network.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 options + +import "yunion.io/x/jsonutils" + +type InterVpcNetworkListOPtions struct { + BaseListOptions +} + +func (opts *InterVpcNetworkListOPtions) Params() (jsonutils.JSONObject, error) { + return ListStructToParams(opts) +} + +type InterVpcNetworkIdOPtions struct { + ID string `json:"Vpc peering connection ID"` +} + +func (opts *InterVpcNetworkIdOPtions) GetId() string { + return opts.ID +} + +func (opts *InterVpcNetworkIdOPtions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type InterVpcNetworkCreateOPtions struct { + EnabledStatusCreateOptions + ManagerId string +} + +func (opts *InterVpcNetworkCreateOPtions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts).(*jsonutils.JSONDict), nil +} + +type InterVpcNetworkAddVpcOPtions struct { + ID string `json:"Vpc peering connection ID"` + VpcId string `json:"Vpc ID"` +} + +func (opts *InterVpcNetworkAddVpcOPtions) GetId() string { + return opts.ID +} + +func (opts *InterVpcNetworkAddVpcOPtions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts).(*jsonutils.JSONDict), nil +} + +type InterVpcNetworkRemoveVpcOPtions struct { + ID string `json:"Vpc peering connection ID"` + VpcId string `json:"Vpc ID"` +} + +func (opts *InterVpcNetworkRemoveVpcOPtions) GetId() string { + return opts.ID +} + +func (opts *InterVpcNetworkRemoveVpcOPtions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts).(*jsonutils.JSONDict), nil +} diff --git a/pkg/mcclient/options/inter_vpc_network_routeset.go b/pkg/mcclient/options/inter_vpc_network_routeset.go new file mode 100644 index 0000000000..3622b4b557 --- /dev/null +++ b/pkg/mcclient/options/inter_vpc_network_routeset.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 options + +import "yunion.io/x/jsonutils" + +type InterVpcNetworkRouteListOptions struct { + BaseListOptions + InterVpcNetworkId string +} + +func (opts *InterVpcNetworkRouteListOptions) Params() (jsonutils.JSONObject, error) { + return ListStructToParams(opts) +} + +type InterVpcNetworkRouteIdptions struct { + ID string +} + +func (opts *InterVpcNetworkRouteIdptions) GetId() string { + return opts.ID +} + +func (opts *InterVpcNetworkRouteIdptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/mcclient/options/schedtags.go b/pkg/mcclient/options/schedtags.go index c665edd0b7..f471a31354 100644 --- a/pkg/mcclient/options/schedtags.go +++ b/pkg/mcclient/options/schedtags.go @@ -18,6 +18,8 @@ import ( "fmt" "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" ) type SchedtagModelListOptions struct { @@ -43,10 +45,148 @@ type SchedtagSetOptions struct { Schedtag []string `help:"Ids of schedtag"` } -func (o SchedtagSetOptions) Params() (*jsonutils.JSONDict, error) { +func (o SchedtagSetOptions) Params() (jsonutils.JSONObject, error) { params := jsonutils.NewDict() for idx, tag := range o.Schedtag { params.Add(jsonutils.NewString(tag), fmt.Sprintf("schedtag.%d", idx)) } return params, nil } + +type SchedtagListOptions struct { + BaseListOptions + Type string `help:"Filter by resource type"` +} + +func (o SchedtagListOptions) Params() (jsonutils.JSONObject, error) { + params, err := o.BaseListOptions.Params() + if err != nil { + return nil, err + } + + if len(o.Type) > 0 { + params.Add(jsonutils.NewString(o.Type), "resource_type") + } + + return params, nil +} + +type SchedtagShowOptions struct { + ID string `help:"ID or Name of the scheduler tag to show"` +} + +func (o SchedtagShowOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +func (o SchedtagShowOptions) GetId() string { + return o.ID +} + +type SchedtagCreateOptions struct { + NAME string `help:"Name of new schedtag"` + Strategy string `help:"Policy" choices:"require|exclude|prefer|avoid"` + Desc string `help:"Description"` + Scope string `help:"Resource scope" choices:"system|domain|project"` + Type string `help:"Resource type" choices:"hosts|storages|networks|cloudproviders|cloudregions|zones"` +} + +func (o SchedtagCreateOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(o.NAME), "name") + if len(o.Strategy) > 0 { + params.Add(jsonutils.NewString(o.Strategy), "default_strategy") + } + if len(o.Desc) > 0 { + params.Add(jsonutils.NewString(o.Desc), "description") + } + if len(o.Type) > 0 { + params.Add(jsonutils.NewString(o.Type), "resource_type") + } + if len(o.Scope) > 0 { + params.Add(jsonutils.NewString(o.Scope), "scope") + } + + return params, nil +} + +type SchedtagUpdateOptions struct { + ID string `help:"ID or Name of schetag"` + Name string `help:"New name of schedtag"` + Strategy string `help:"Policy" choices:"require|exclude|prefer|avoid"` + Desc string `help:"Description"` + ClearStrategy bool `help:"Clear default schedule policy"` +} + +func (o SchedtagUpdateOptions) GetId() string { + return o.ID +} + +func (o SchedtagUpdateOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + if len(o.Name) > 0 { + params.Add(jsonutils.NewString(o.Name), "name") + } + if len(o.Strategy) > 0 { + params.Add(jsonutils.NewString(o.Strategy), "default_strategy") + } + if len(o.Desc) > 0 { + params.Add(jsonutils.NewString(o.Desc), "description") + } + if o.ClearStrategy { + params.Add(jsonutils.NewString(""), "default_strategy") + } + if params.Size() == 0 { + return nil, fmt.Errorf("No valid data to update") + } + + return params, nil +} + +type SchedtagSetScopeOptions struct { + ID []string `help:"ID or Name of schetag"` + Project string `help:"ID or Name of project"` + Domain string `help:"ID or Name of domain"` + System bool `help:"Set to system scope"` +} + +func (o SchedtagSetScopeOptions) GetIds() []string { + return o.ID +} + +func (o SchedtagSetScopeOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + domainId := o.Domain + projectId := o.Project + if o.System { + domainId = "" + projectId = "" + } + params.Add(jsonutils.NewString(domainId), "domain") + params.Add(jsonutils.NewString(projectId), "project") + return params, nil +} + +type SchedtagSetResource struct { + ID string `help:"ID or Name of schetag"` + Resource []string `help:"Resource id or name"` + UnbindAll bool `help:"Unbind all attached resources"` +} + +func (o SchedtagSetResource) GetId() string { + return o.ID +} + +func (o SchedtagSetResource) Params() (jsonutils.JSONObject, error) { + if o.UnbindAll && len(o.Resource) != 0 { + return nil, fmt.Errorf("Can not use --unbind-all and --resource at same time") + } + + input := new(api.SchedtagSetResourceInput) + + if !o.UnbindAll { + input.ResourceIds = o.Resource + } + + return jsonutils.Marshal(input), nil +} diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 873c0f525a..f679203e1a 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -474,6 +474,8 @@ func (opts *ServerCreateOptionalOptions) OptionalParams() (*computeapi.ServerCre EipBw: opts.EipBw, EipBgpType: opts.EipBgpType, EipChargeType: opts.EipChargeType, + PublicIpBw: opts.PublicIpBw, + PublicIpChargeType: opts.PublicIpChargeType, Eip: opts.Eip, EnableCloudInit: opts.EnableCloudInit, OsType: opts.OsType, @@ -552,8 +554,9 @@ func (opts *ServerCreateOptions) Params() (*computeapi.ServerCreateInput, error) } type ServerStopOptions struct { - ID []string `help:"ID or Name of server" json:"-"` - Force *bool `help:"Stop server forcefully" json:"is_force"` + ID []string `help:"ID or Name of server" json:"-"` + Force *bool `help:"Stop server forcefully" json:"is_force"` + StopCharging *bool `help:"Stop charging when server stop"` } func (o *ServerStopOptions) GetIds() []string { @@ -899,7 +902,11 @@ type ResourceMetadataOptions struct { TAGS []string `help:"Tags info, eg: hypervisor=aliyun、os_type=Linux、os_version"` } -func (opts *ResourceMetadataOptions) Params() (*jsonutils.JSONDict, error) { +func (opts *ResourceMetadataOptions) GetId() string { + return opts.ID +} + +func (opts *ResourceMetadataOptions) Params() (jsonutils.JSONObject, error) { params := jsonutils.NewDict() for _, tag := range opts.TAGS { info := strings.Split(tag, "=") diff --git a/pkg/mcclient/options/vpc.go b/pkg/mcclient/options/vpc.go index 298420ad87..d16a7714a8 100644 --- a/pkg/mcclient/options/vpc.go +++ b/pkg/mcclient/options/vpc.go @@ -27,6 +27,7 @@ type VpcListOptions struct { Region string `help:"ID or Name of region" json:"-"` Globalvpc string `help:"Filter by globalvpc"` DnsZoneId string `help:"Filter by DnsZone"` + InterVpcNetworkId string `help:"Filter by InterVpcNetwork"` ExternalAccessMode string `help:"Filter by external access mode" choices:"distgw|eip|eip-distgw"` } diff --git a/pkg/monitor/alerting/conditions/query.go b/pkg/monitor/alerting/conditions/query.go index 39959853ee..967679ab15 100644 --- a/pkg/monitor/alerting/conditions/query.go +++ b/pkg/monitor/alerting/conditions/query.go @@ -223,11 +223,25 @@ func (c *QueryCondition) NewEvalMatch(context *alerting.EvalContext, series tsdb if alertDetails.GetPointStr { evalMatch.ValueStr = c.jointPointStr(series, evalMatch.ValueStr, valStrArr) } - evalMatch.MeasurementDesc = alertDetails.MeasurementDisplayName - evalMatch.FieldDesc = alertDetails.FieldDescription.DisplayName + c.newRuleDescription(context, alertDetails) return evalMatch, nil } +func (c *QueryCondition) newRuleDescription(context *alerting.EvalContext, alertDetails *monitor.CommonAlertMetricDetails) { + ruleDes := alerting.RuleDescription{ + AlertRecordRule: monitor.AlertRecordRule{ + Metric: fmt.Sprintf("%s.%s", alertDetails.Measurement, alertDetails.Field), + Measurement: alertDetails.Measurement, + MeasurementDesc: alertDetails.MeasurementDisplayName, + Field: alertDetails.Field, + FieldDesc: alertDetails.FieldDescription.DisplayName, + Comparator: alertDetails.Comparator, + Threshold: c.RationalizeValueFromUnit(alertDetails.Threshold, alertDetails.FieldDescription.Unit, ""), + }, + } + context.RuleDescription = &ruleDes +} + func (c *QueryCondition) jointPointStr(series tsdb.TimeSeries, value string, valStrArr []string) string { str := "" for i := 0; i < len(valStrArr); i++ { diff --git a/pkg/monitor/alerting/conditions/suggestrulereducer.go b/pkg/monitor/alerting/conditions/suggestrulereducer.go index b884c48209..d17aff13c3 100644 --- a/pkg/monitor/alerting/conditions/suggestrulereducer.go +++ b/pkg/monitor/alerting/conditions/suggestrulereducer.go @@ -33,8 +33,8 @@ func NewSuggestRuleReducer(t string, duration time.Duration) Reducer { } func (s *suggestRuleReducer) Reduce(series *tsdb.TimeSeries) (*float64, []string) { - if int(s.duration.Seconds()) > len(series.Points) { + /*if int(s.duration.Seconds()) > len(series.Points) { return nil, nil - } + }*/ return s.queryReducer.Reduce(series) } diff --git a/pkg/monitor/alerting/eval_context.go b/pkg/monitor/alerting/eval_context.go index 83472ade9a..9845e8f823 100644 --- a/pkg/monitor/alerting/eval_context.go +++ b/pkg/monitor/alerting/eval_context.go @@ -42,6 +42,7 @@ type EvalContext struct { StartTime time.Time EndTime time.Time Rule *Rule + RuleDescription *RuleDescription NoDataFound bool PrevAlertState monitor.AlertStateType @@ -50,6 +51,10 @@ type EvalContext struct { UserCred mcclient.TokenCredential } +type RuleDescription struct { + monitor.AlertRecordRule +} + // NewEvalContext is the EvalContext constructor. func NewEvalContext(alertCtx context.Context, userCred mcclient.TokenCredential, rule *Rule) *EvalContext { return &EvalContext{ diff --git a/pkg/monitor/alerting/notifier.go b/pkg/monitor/alerting/notifier.go index d3753b5cea..f5cd2eff41 100644 --- a/pkg/monitor/alerting/notifier.go +++ b/pkg/monitor/alerting/notifier.go @@ -17,7 +17,6 @@ package alerting import ( "database/sql" "fmt" - "strings" "time" "yunion.io/x/jsonutils" @@ -143,32 +142,47 @@ func (n *notificationService) getNeededNotifiers(nIds []string, evalCtx *EvalCon } } if shouldNotify { - var matches []*monitor.EvalMatch - if evalCtx.Firing { - matches = evalCtx.EvalMatches - } else { - matches = evalCtx.AlertOkEvalMatches - } - recordCreateInput := monitor.AlertRecordCreateInput{ - StandaloneResourceCreateInput: apis.StandaloneResourceCreateInput{ - GenerateName: evalCtx.Rule.Name, - }, - AlertId: evalCtx.Rule.Id, - Level: evalCtx.Rule.Level, - State: string(evalCtx.Rule.State), - EvalData: matches, - AlertRule: newAlertRecordRule(evalCtx), - } - createData := recordCreateInput.JSON(recordCreateInput) - record, err := db.DoCreate(models.AlertRecordManager, evalCtx.Ctx, evalCtx.UserCred, jsonutils.NewDict(), createData, evalCtx.UserCred) - if err != nil { - log.Errorf("create alert record err:%v", err) - } - record.PostCreate(evalCtx.Ctx, evalCtx.UserCred, evalCtx.UserCred, nil, createData) + n.createAlertRecordWhenNotify(evalCtx) } + if !shouldNotify && evalCtx.shouldUpdateAlertState() && evalCtx.NoDataFound { + n.detachAlertResourceWhenNodata(evalCtx) + } + return result, nil } +func (n *notificationService) createAlertRecordWhenNotify(evalCtx *EvalContext) { + var matches []*monitor.EvalMatch + if evalCtx.Firing { + matches = evalCtx.EvalMatches + } else { + matches = evalCtx.AlertOkEvalMatches + } + recordCreateInput := monitor.AlertRecordCreateInput{ + StandaloneResourceCreateInput: apis.StandaloneResourceCreateInput{ + GenerateName: evalCtx.Rule.Name, + }, + AlertId: evalCtx.Rule.Id, + Level: evalCtx.Rule.Level, + State: string(evalCtx.Rule.State), + EvalData: matches, + AlertRule: newAlertRecordRule(evalCtx), + } + createData := recordCreateInput.JSON(recordCreateInput) + record, err := db.DoCreate(models.AlertRecordManager, evalCtx.Ctx, evalCtx.UserCred, jsonutils.NewDict(), createData, evalCtx.UserCred) + if err != nil { + log.Errorf("create alert record err:%v", err) + } + record.PostCreate(evalCtx.Ctx, evalCtx.UserCred, evalCtx.UserCred, nil, createData) +} + +func (n *notificationService) detachAlertResourceWhenNodata(evalCtx *EvalContext) { + errs := models.CommonAlertManager.DetachAlertResourceByAlertId(evalCtx.Ctx, evalCtx.UserCred, evalCtx.Rule.Id) + if len(errs) != 0 { + log.Errorf("detachAlertResourceWhenNodata err:%#v", errors.NewAggregate(errs)) + } +} + type NotifierPlugin struct { Type string Factory NotifierFactory @@ -205,25 +219,14 @@ func InitNotifier(config NotificationConfig) (Notifier, error) { func newAlertRecordRule(evalCtx *EvalContext) monitor.AlertRecordRule { alertRule := monitor.AlertRecordRule{} + if evalCtx.RuleDescription != nil { + alertRule = evalCtx.RuleDescription.AlertRecordRule + } if evalCtx.Rule.Frequency < 60 { alertRule.Period = fmt.Sprintf("%ds", evalCtx.Rule.Frequency) } else { alertRule.Period = fmt.Sprintf("%dm", evalCtx.Rule.Frequency/60) } - ruleStr := evalCtx.Rule.Message - ruleElementArr := strings.Split(ruleStr, " ") - if len(ruleElementArr) == 3 { - alertRule.Metric = ruleElementArr[0] - alertRule.Comparator = ruleElementArr[1] - alertRule.Threshold = ruleElementArr[2] - } - if len(evalCtx.EvalMatches) != 0 { - alertRule.MeasurementDesc = evalCtx.EvalMatches[0].MeasurementDesc - alertRule.FieldDesc = evalCtx.EvalMatches[0].FieldDesc - } - if len(evalCtx.AlertOkEvalMatches) != 0 { - alertRule.MeasurementDesc = evalCtx.AlertOkEvalMatches[0].MeasurementDesc - alertRule.FieldDesc = evalCtx.AlertOkEvalMatches[0].FieldDesc - } + return alertRule } diff --git a/pkg/monitor/dbinit/suggestrule_dbinit.go b/pkg/monitor/dbinit/suggestrule_dbinit.go index 422df99fe7..8086e5e377 100644 --- a/pkg/monitor/dbinit/suggestrule_dbinit.go +++ b/pkg/monitor/dbinit/suggestrule_dbinit.go @@ -18,20 +18,21 @@ var SnapShotUnusedCreateInput *monitor.SuggestSysRuleCreateInput var InitRuleCreateInputMap = make(map[string]*monitor.SuggestSysRuleCreateInput) func init() { + ignoreTimeFrom := true diskSetting := new(monitor.SSuggestSysAlertSetting) diskSetting.DiskUnused = new(monitor.DiskUnused) - DiskUnusedCreateInput = NewRule("未挂载的云硬盘", "12h", "336h", monitor.DISK_UNUSED, diskSetting) + DiskUnusedCreateInput = NewRule("未挂载的云硬盘", "12h", "336h", monitor.DISK_UNUSED, diskSetting, nil) eipSetting := new(monitor.SSuggestSysAlertSetting) eipSetting.EIPUnused = new(monitor.EIPUnused) - EipUnusedCreateInput = NewRule("未挂载的弹性公网IP", "12h", "336h", monitor.EIP_UNUSED, eipSetting) + EipUnusedCreateInput = NewRule("未挂载的弹性公网IP", "12h", "336h", monitor.EIP_UNUSED, eipSetting, nil) lbSetting := new(monitor.SSuggestSysAlertSetting) lbSetting.LBUnused = new(monitor.LBUnused) - LbUnusedCreateInput = NewRule("未使用的负载均衡实例", "12h", "336h", monitor.LB_UNUSED, lbSetting) + LbUnusedCreateInput = NewRule("未使用的负载均衡实例", "12h", "336h", monitor.LB_UNUSED, lbSetting, nil) OssSecAclCreateInput = NewRule("对象存储权限为开放读、写的存储桶和文件", "12h", "336h", monitor.OSS_SEC_ACL, - new(monitor.SSuggestSysAlertSetting)) + new(monitor.SSuggestSysAlertSetting), &ignoreTimeFrom) redisSetting := new(monitor.SSuggestSysAlertSetting) scaleRule := monitor.Scale{ @@ -43,7 +44,7 @@ func init() { Threshold: 100, } redisSetting.ScaleRule = &monitor.ScaleRule{scaleRule} - RedisUnReasonableCreateInput = NewRule("空闲的redis", "12h", "336h", monitor.REDIS_UNREASONABLE, redisSetting) + RedisUnReasonableCreateInput = NewRule("空闲的redis", "12h", "336h", monitor.REDIS_UNREASONABLE, redisSetting, nil) rdsSetting := new(monitor.SSuggestSysAlertSetting) rdsSetting.ScaleRule = &monitor.ScaleRule{monitor.Scale{ @@ -54,7 +55,7 @@ func init() { EvalType: "<", Threshold: 5, }} - RdsUnReasonableCreateInput = NewRule("空闲的rds", "12h", "336h", monitor.RDS_UNREASONABLE, rdsSetting) + RdsUnReasonableCreateInput = NewRule("空闲的rds", "12h", "336h", monitor.RDS_UNREASONABLE, rdsSetting, nil) ossSetting := new(monitor.SSuggestSysAlertSetting) ossSetting.ScaleRule = &monitor.ScaleRule{monitor.Scale{ @@ -65,7 +66,7 @@ func init() { EvalType: "<", Threshold: 100, }} - OssUnReasonableCreateInput = NewRule("空闲的oss", "12h", "336h", monitor.OSS_UNREASONABLE, ossSetting) + OssUnReasonableCreateInput = NewRule("空闲的oss", "12h", "336h", monitor.OSS_UNREASONABLE, ossSetting, nil) serversetting := new(monitor.SSuggestSysAlertSetting) serversetting.ScaleRule = &monitor.ScaleRule{monitor.Scale{ @@ -76,16 +77,17 @@ func init() { EvalType: "<", Threshold: 5, }} - ScaleDownCreateInput = NewRule("低负载的虚拟机", "12h", "336h", monitor.SCALE_DOWN, serversetting) + ScaleDownCreateInput = NewRule("低负载的虚拟机", "12h", "336h", monitor.SCALE_DOWN, serversetting, nil) SecGroupRuleInCreateInput = NewRule("安全组规则的in规则为全开放的主机", "12h", "336h", - monitor.SECGROUPRULEINSERVER_ALLIN, &monitor.SSuggestSysAlertSetting{}) + monitor.SECGROUPRULEINSERVER_ALLIN, &monitor.SSuggestSysAlertSetting{}, &ignoreTimeFrom) SnapShotUnusedCreateInput = NewRule("未使用的快照", "12h", "336h", monitor.SNAPSHOT_UNUSED, - &monitor.SSuggestSysAlertSetting{}) + &monitor.SSuggestSysAlertSetting{}, nil) } -func NewRule(name, period, timeFrom string, typ monitor.SuggestDriverType, setting *monitor.SSuggestSysAlertSetting) *monitor. +func NewRule(name, period, timeFrom string, typ monitor.SuggestDriverType, setting *monitor.SSuggestSysAlertSetting, + ignore *bool) *monitor. SuggestSysRuleCreateInput { rule := new(monitor.SuggestSysRuleCreateInput) enable := false @@ -95,5 +97,6 @@ func NewRule(name, period, timeFrom string, typ monitor.SuggestDriverType, setti rule.TimeFrom = timeFrom rule.Setting = setting rule.Enabled = &enable + rule.IgnoreTimeFrom = ignore return rule } diff --git a/pkg/monitor/models/commonalert.go b/pkg/monitor/models/commonalert.go index c4f6f67a12..d278d3836e 100644 --- a/pkg/monitor/models/commonalert.go +++ b/pkg/monitor/models/commonalert.go @@ -1020,15 +1020,20 @@ func (alert *SCommonAlert) StartDetachTask(ctx context.Context, userCred mcclien func (alert *SCommonAlert) DetachAlertResourceOnDisable(ctx context.Context, userCred mcclient.TokenCredential) (errs []error) { - resources, err := GetAlertResourceManager().getResourceFromAlertId(alert.Id) + return CommonAlertManager.DetachAlertResourceByAlertId(ctx, userCred, alert.Id) +} + +func (manager *SCommonAlertManager) DetachAlertResourceByAlertId(ctx context.Context, + userCred mcclient.TokenCredential, alertId string) (errs []error) { + resources, err := GetAlertResourceManager().getResourceFromAlertId(alertId) if err != nil { errs = append(errs, errors.Wrap(err, "getResourceFromAlert error")) return } for _, resource := range resources { - err := resource.DetachAlert(ctx, userCred, alert.Id) + err := resource.DetachAlert(ctx, userCred, alertId) if err != nil { - errs = append(errs, errors.Wrapf(err, "resource:%s DetachAlert:%s err", resource.Id, alert.Id)) + errs = append(errs, errors.Wrapf(err, "resource:%s DetachAlert:%s err", resource.Id, alertId)) } } return diff --git a/pkg/monitor/models/suggestsysalert.go b/pkg/monitor/models/suggestsysalert.go index fabf259b75..57a9aa79ff 100644 --- a/pkg/monitor/models/suggestsysalert.go +++ b/pkg/monitor/models/suggestsysalert.go @@ -241,17 +241,6 @@ func (self *SSuggestSysAlert) GetType() monitor.SuggestDriverType { return monitor.SuggestDriverType(self.Type) } -func (self *SSuggestSysAlert) GetShowName() string { - rule, _ := SuggestSysRuleManager.GetRules(self.GetType()) - var showName string - if len(rule) != 0 { - showName = fmt.Sprintf("%s-%s", self.Name, rule[0].Name) - } else { - showName = fmt.Sprintf("%s-%s", self.Name, self.Type) - } - return showName -} - func (self *SSuggestSysAlert) getMoreDetails(out monitor.SuggestSysAlertDetails) monitor.SuggestSysAlertDetails { err := self.ResMeta.Unmarshal(&out) if err != nil { @@ -261,11 +250,20 @@ func (self *SSuggestSysAlert) getMoreDetails(out monitor.SuggestSysAlertDetails) out.Account = self.Cloudaccount out.ResType = string(drv.GetResourceType()) out.RuleName = strings.ToLower(string(drv.GetType())) - out.ShowName = self.GetShowName() + out.ShowName = self.Name out.Suggest = string(drv.GetSuggest()) + out.ResName = SuggestSysAlertManager.getOriName(self.Name, self.Type) return out } +func (self *SSuggestSysAlertManager) getOriName(name, typ string) string { + lastIndex := strings.LastIndex(name, fmt.Sprintf("-%s", typ)) + if lastIndex == -1 || lastIndex == 0 { + lastIndex = len(name) + } + return name[:lastIndex] +} + func (manager *SSuggestSysAlertManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { var err error q, err = manager.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field) @@ -363,7 +361,8 @@ func (self *SSuggestSysAlert) AllowPerformIgnore(ctx context.Context, userCred m return db.IsProjectAllowPerform(userCred, self, "ignore") } -func (self *SSuggestSysAlert) GetSuggestConfig(scope rbacutils.TRbacScope, domainId string, projectId string) (*SSuggestSysRuleConfig, error) { +func (self *SSuggestSysAlert) GetSuggestConfig(scope rbacutils.TRbacScope, domainId string, projectId string, + batchIgnore bool) (*SSuggestSysRuleConfig, error) { if scope == "" { scope = rbacutils.ScopeSystem } @@ -377,7 +376,10 @@ func (self *SSuggestSysAlert) GetSuggestConfig(scope rbacutils.TRbacScope, domai } else if scope == rbacutils.ScopeProject { scopeId = projectId } - q := SuggestSysRuleConfigManager.Query().Equals("type", drvType).Equals("resource_id", resId).Equals("resource_type", resType) + q := SuggestSysRuleConfigManager.Query().Equals("type", drvType).Equals("resource_type", resType) + if !batchIgnore { + q = q.Equals("resource_id", resId) + } q = SuggestSysRuleConfigManager.FilterByScope(q, scope, scopeId) configs := make([]SSuggestSysRuleConfig, 0) if err := db.FetchModelObjects(SuggestSysRuleConfigManager, q, &configs); err != nil { @@ -394,7 +396,7 @@ func (self *SSuggestSysAlert) PerformIgnore(ctx context.Context, userCred mcclie if data.Scope == "" { data.Scope = string(rbacutils.ScopeSystem) } - config, err := self.GetSuggestConfig(rbacutils.TRbacScope(data.Scope), data.ProjectDomainId, data.ProjectId) + config, err := self.GetSuggestConfig(rbacutils.TRbacScope(data.Scope), data.ProjectDomainId, data.ProjectId, data.BatchIgnore) if err != nil { return nil, err } @@ -403,14 +405,20 @@ func (self *SSuggestSysAlert) PerformIgnore(ctx context.Context, userCred mcclie resType := drv.GetResourceType() if config == nil { createData := new(monitor.SuggestSysRuleConfigCreateInput) - createData.Name = self.GetShowName() + createData.Name = self.Name createData.ScopedResourceCreateInput = data.ScopedResourceCreateInput createData.Type = &drvType createData.ResourceType = &resType - createData.ResourceId = &self.ResId + if !data.BatchIgnore { + createData.ResourceId = &self.ResId + } + ownerId, err := SuggestSysAlertManager.FetchOwnerId(ctx, jsonutils.Marshal(&data)) + if err != nil { + return nil, errors.Wrap(err, "SuggestSysAlertManager FetchOwnerId error") + } createData.IgnoreAlert = true data := createData.JSON(createData) - conf, err := db.DoCreate(SuggestSysRuleConfigManager, ctx, userCred, nil, data, userCred) + conf, err := db.DoCreate(SuggestSysRuleConfigManager, ctx, userCred, nil, data, ownerId) if err != nil { return nil, err } diff --git a/pkg/monitor/models/suggestsysrule.go b/pkg/monitor/models/suggestsysrule.go index dcce5cd652..d352645538 100644 --- a/pkg/monitor/models/suggestsysrule.go +++ b/pkg/monitor/models/suggestsysrule.go @@ -40,6 +40,10 @@ import ( "yunion.io/x/onecloud/pkg/util/stringutils2" ) +const ( + SUGGESTRULE_METADATA_IGNORETIME = "ignore_time_from" +) + var ( SuggestSysRuleManager *SSuggestSysRuleManager ) @@ -135,7 +139,6 @@ func (man *SSuggestSysRuleManager) ValidateCreateData( ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data monitor.SuggestSysRuleCreateInput) (monitor.SuggestSysRuleCreateInput, error) { if data.Period == "" { - // default 30s data.Period = "12h" } else { data.Period = parseDuration(data.Period) @@ -244,9 +247,33 @@ func (self *SSuggestSysRule) getMoreDetails(out monitor.SuggestSysRuleDetails) m out.Enabled = self.GetEnabled() self.Period = showDuration(self.Period) self.TimeFrom = showDuration(self.TimeFrom) + ignore, _ := strconv.ParseBool(self.getIgnoreTimeFrom()) + if ignore { + self.TimeFrom = "" + } + self.getMetricDetails(&out) return out } +func (self *SSuggestSysRule) getMetricDetails(out *monitor.SuggestSysRuleDetails) { + if out.Setting.ScaleRule != nil { + scaleRule := *out.Setting.ScaleRule + commonAlertMetricDetails := make([]*monitor.CommonAlertMetricDetails, len(scaleRule)) + for i, rule := range scaleRule { + metricDetails := monitor.CommonAlertMetricDetails{ + Comparator: rule.EvalType, + Threshold: rule.Threshold, + DB: rule.Database, + Measurement: rule.Measurement, + Field: rule.Field, + } + getMetricDescriptionDetails(&metricDetails) + commonAlertMetricDetails[i] = &metricDetails + } + out.CommonAlertMetricDetails = commonAlertMetricDetails + } +} + func (self *SSuggestSysRule) GetExtraDetails( ctx context.Context, userCred mcclient.TokenCredential, @@ -259,6 +286,10 @@ func (self *SSuggestSysRule) GetExtraDetails( // after create, update Cronjob's info func (self *SSuggestSysRule) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { self.SStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + ignore, err := data.GetString("ignore_time_from") + if err == nil { + self.setIgnoreTimeFrom(ctx, userCred, ignore) + } self.updateCronjob() } @@ -266,9 +297,22 @@ func (self *SSuggestSysRule) PostCreate(ctx context.Context, userCred mcclient.T func (self *SSuggestSysRule) PostUpdate( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + ignore, err := data.GetString("ignore_time_from") + if err == nil { + self.setIgnoreTimeFrom(ctx, userCred, ignore) + } self.updateCronjob() } +func (self *SSuggestSysRule) setIgnoreTimeFrom(ctx context.Context, userCred mcclient.TokenCredential, + ignore string) error { + return self.SetMetadata(ctx, SUGGESTRULE_METADATA_IGNORETIME, ignore, userCred) +} + +func (self *SSuggestSysRule) getIgnoreTimeFrom() string { + return self.GetMetadata(SUGGESTRULE_METADATA_IGNORETIME, nil) +} + func (self *SSuggestSysRule) updateCronjob() { cronman.GetCronJobManager().Remove(self.Type) if self.Enabled.Bool() { @@ -529,6 +573,10 @@ func (man *SSuggestSysRuleManager) initUpdateDefaultRule(rule ruleInfo) error { if err != nil { return errors.Wrap(err, "initUpdateDefaultRule error") } + if ruleCreateInput.IgnoreTimeFrom != nil { + suggestRule.setIgnoreTimeFrom(context.Background(), auth.AdminCredential(), + strconv.FormatBool(*ruleCreateInput.IgnoreTimeFrom)) + } } } return nil @@ -624,19 +672,19 @@ func showDuration(dur string) string { func showDuration_(dur int64, sign string) string { var durUp, durSign int64 var upSign, durStr string - if sign == "s" && dur > 60 { + if sign == "s" && dur >= 60 { upSign = "m" durUp = dur / 60 durSign = dur % 60 durStr = showDuration_(durUp, upSign) } - if sign == "m" && dur > 60 { + if sign == "m" && dur >= 60 { upSign = "h" durUp = dur / 60 durSign = dur % 60 durStr = showDuration_(durUp, upSign) } - if sign == "h" && dur > 24 { + if sign == "h" && dur >= 24 { upSign = "d" durUp = dur / 24 durSign = dur % 24 diff --git a/pkg/monitor/models/suggestsysruleconfig.go b/pkg/monitor/models/suggestsysruleconfig.go index 531bd59338..61c1839de8 100644 --- a/pkg/monitor/models/suggestsysruleconfig.go +++ b/pkg/monitor/models/suggestsysruleconfig.go @@ -196,7 +196,8 @@ func (man *SSuggestSysRuleConfigManager) AllowGetPropertySupportTypes(ctx contex return true } -func (man *SSuggestSysRuleConfigManager) GetPropertySupportTypes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*monitor.SuggestSysRuleConfigSupportTypes, error) { +func (man *SSuggestSysRuleConfigManager) GetPropertySupportTypes(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*monitor.SuggestSysRuleConfigSupportTypes, error) { ret := &monitor.SuggestSysRuleConfigSupportTypes{ Types: make([]monitor.SuggestDriverType, 0), ResourceTypes: make([]string, 0), @@ -209,21 +210,70 @@ func (man *SSuggestSysRuleConfigManager) GetPropertySupportTypes(ctx context.Con return ret, nil } +func (man *SSuggestSysRuleConfigManager) AllowGetPropertyTypeInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (man *SSuggestSysRuleConfigManager) GetPropertyTypeInfo(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*monitor.SuggestSysRuleConfigTypeInfo, error) { + searchInput := new(monitor.SuggestSysRuleConfigListInput) + err := query.Unmarshal(searchInput) + if err != nil { + return nil, errors.Errorf("GetPropertyTypeInfo Unmarshal error:%v", err) + } + if searchInput.Type == nil { + return nil, httperrors.NewInputParameterError("SuggestSysRuleConfig type is empty") + } + ownerId, err := man.FetchOwnerId(ctx, query) + if err != nil { + return nil, errors.Errorf("SSuggestSysRuleConfigManager FetchOwnerId error:%v", err) + } + if ownerId == nil { + return nil, httperrors.NewInputParameterError("project or domain is empty") + } + configs, err := man.getConfigsOfBatchType(rbacutils.TRbacScope(searchInput.Scope), ownerId, string(*searchInput.Type)) + if err != nil { + return nil, errors.Errorf("SSuggestSysRuleConfigManager getConfigsOfBatchType error:%v", err) + } + typeInfo := new(monitor.SuggestSysRuleConfigTypeInfo) + if len(configs) != 0 { + typeInfo.Name = configs[0].Name + } + return typeInfo, nil +} + +func (man *SSuggestSysRuleConfigManager) getConfigsQueryByScope(scope rbacutils.TRbacScope, + ownerId mcclient.IIdentityProvider) *sqlchemy.SQuery { + query := man.Query() + switch scope { + case rbacutils.ScopeSystem: + query = query.IsNullOrEmpty("domain_id").IsNullOrEmpty("tenant_id") + case rbacutils.ScopeDomain: + query = query.Equals("domain_id", ownerId.GetProjectDomainId()).IsNullOrEmpty("tenant_id") + case rbacutils.ScopeProject: + query = query.Equals("tenant_id", ownerId.GetProjectId()) + } + return query +} + +func (man *SSuggestSysRuleConfigManager) getConfigsOfBatchType(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, Typ string) ([]SSuggestSysRuleConfig, error) { + query := man.getConfigsQueryByScope(scope, ownerId) + query = query.Equals("type", Typ).IsNullOrEmpty("resource_id") + configs := make([]SSuggestSysRuleConfig, 0) + if err := db.FetchModelObjects(man, query, &configs); err != nil { + return nil, err + } + return configs, nil +} + func (man *SSuggestSysRuleConfigManager) NamespaceScope() rbacutils.TRbacScope { return rbacutils.ScopeNone } func (man *SSuggestSysRuleConfigManager) GetConfigsByScope(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, ignoreAlert bool) ([]SSuggestSysRuleConfig, error) { - q := man.Query().Equals("ignore_alert", ignoreAlert) + q := man.getConfigsQueryByScope(scope, userCred) + q = q.Equals("ignore_alert", ignoreAlert) configs := make([]SSuggestSysRuleConfig, 0) - switch scope { - case rbacutils.ScopeSystem: - q = q.IsNullOrEmpty("domain_id").IsNullOrEmpty("tenant_id") - case rbacutils.ScopeDomain: - q = q.Equals("domain_id", userCred.GetProjectDomainId()).IsNullOrEmpty("tenant_id") - case rbacutils.ScopeProject: - q = q.Equals("tenant_id", userCred.GetProjectId()) - } if err := db.FetchModelObjects(man, q, &configs); err != nil { return nil, err } @@ -351,6 +401,9 @@ func (conf *SSuggestSysRuleConfig) getMoreColumns(out monitor.SuggestSysRuleConf out.RuleId = rule.GetId() out.Rule = rule.GetName() out.RuleEnabled = rule.GetEnabled() + if len(conf.ResourceId) != 0 { + out.ResName = SuggestSysAlertManager.getOriName(conf.Name, conf.Type) + } } return out } diff --git a/pkg/monitor/service/service.go b/pkg/monitor/service/service.go index 23917f7c32..f1602eccc3 100644 --- a/pkg/monitor/service/service.go +++ b/pkg/monitor/service/service.go @@ -65,7 +65,6 @@ func StartService() { cron := cronman.InitCronJobManager(true, opts.CronJobWorkerCount) suggestsysdrivers.InitSuggestSysRuleCronjob() - cron.AddJobAtIntervalsWithStartRun("InitScopeSuggestConfigs", time.Duration(opts.InitScopeSuggestConfigIntervalSeconds)*time.Second, models.SuggestSysRuleConfigManager.InitScopeConfigs, true) cron.AddJobAtIntervalsWithStartRun("InitAlertResourceAdminRoleUsers", time.Duration(opts.InitAlertResourceAdminRoleUsersIntervalSeconds)*time.Second, models.GetAlertResourceManager().GetAdminRoleUsers, true) cron.Start() defer cron.Stop() diff --git a/pkg/monitor/suggestsysdrivers/common.go b/pkg/monitor/suggestsysdrivers/common.go index f0ee32cf10..4db76ff73d 100644 --- a/pkg/monitor/suggestsysdrivers/common.go +++ b/pkg/monitor/suggestsysdrivers/common.go @@ -77,8 +77,13 @@ func DealAlertData(drvType monitor.SuggestDriverType, oldAlerts []models.SSugges delete(oldMap, res_id) } else { //新增的alert - _, err := db.DoCreate(models.SuggestSysAlertManager, context.Background(), adminCredential, nil, newAlert, - adminCredential) + ownerId, err := models.SuggestSysAlertManager.FetchOwnerId(context.Background(), newAlert) + if err != nil { + log.Errorf("create SuggestSysAlert FetchOwnerId param:%v. error:%v", newAlert, err) + continue + } + _, err = db.DoCreate(models.SuggestSysAlertManager, context.Background(), adminCredential, nil, newAlert, + ownerId) if err != nil { log.Errorf("create new suggest alert %v error: %v", newAlert, err) } @@ -154,6 +159,7 @@ func getSuggestSysAlertFromJson(obj jsonutils.JSONObject, rule models.ISuggestSy suggestSysAlert.Cloudaccount = val } suggestSysAlert.Type = string(rule.GetType()) + suggestSysAlert.Name = fmt.Sprintf("%s-%s", suggestSysAlert.Name, suggestSysAlert.Type) suggestSysAlert.ResMeta = obj suggestSysAlert.Action = string(rule.GetAction()) suggestSysAlert.Status = monitor.SUGGEST_ALERT_READY diff --git a/pkg/monitor/suggestsysdrivers/osssecacl.go b/pkg/monitor/suggestsysdrivers/osssecacl.go index 4cee48e192..3f816b14bf 100644 --- a/pkg/monitor/suggestsysdrivers/osssecacl.go +++ b/pkg/monitor/suggestsysdrivers/osssecacl.go @@ -145,7 +145,7 @@ func (drv *OssSecAcl) getBucketsByAcl() ([]jsonutils.JSONObject, error) { Description: acl, }, } - suggestSysAlert.Name = GenerateName(suggestSysAlert.Name, string(drv.GetType())) + //suggestSysAlert.Name = GenerateName(suggestSysAlert.Name, string(drv.GetType())) suggestSysAlert.Problem = jsonutils.Marshal(&problems) bucketArr = append(bucketArr, jsonutils.Marshal(suggestSysAlert)) } diff --git a/pkg/monitor/suggestsysdrivers/secgroupruleinserver.go b/pkg/monitor/suggestsysdrivers/secgroupruleinserver.go index cc7525a3a0..377e141051 100644 --- a/pkg/monitor/suggestsysdrivers/secgroupruleinserver.go +++ b/pkg/monitor/suggestsysdrivers/secgroupruleinserver.go @@ -2,7 +2,6 @@ package suggestsysdrivers import ( "context" - "fmt" "strings" "yunion.io/x/jsonutils" @@ -61,7 +60,7 @@ func (drv *SecGroupRuleInServer) GetLatestAlerts(rule *models.SSuggestSysRule, if err != nil { return nil, err } - suggestSysAlert.Name = fmt.Sprintf("%s-%s", suggestSysAlert.Name, string(drv.GetType())) + //suggestSysAlert.Name = fmt.Sprintf("%s-%s", suggestSysAlert.Name, string(drv.GetType())) suggestSysAlert.Amount = 0 secGroupRuleInServerArr = append(secGroupRuleInServerArr, jsonutils.Marshal(suggestSysAlert)) } diff --git a/pkg/multicloud/aliyun/aliyun.go b/pkg/multicloud/aliyun/aliyun.go index 135a88037a..bbce69c3f4 100644 --- a/pkg/multicloud/aliyun/aliyun.go +++ b/pkg/multicloud/aliyun/aliyun.go @@ -59,6 +59,20 @@ const ( ALIYUN_STS_API_VERSION = "2015-04-01" ALIYUN_PVTZ_API_VERSION = "2018-01-01" ALIYUN_ALIDNS_API_VERSION = "2015-01-09" + ALIYUN_CBN_API_VERSION = "2017-09-12" +) + +var ( + // https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.18.675f2b8cu8CN5K#concept-zt4-cvy-5db + OSS_FINANCE_REGION_MAP = map[string]string{ + "cn-hzfinance": "cn-hangzhou", + "cn-shanghai-finance-1-pub": "cn-shanghai-finance-1", + "cn-szfinance": "cn-shenzhen-finance-1", + + "cn-hzjbp": "cn-hangzhou", + "cn-shanghai-finance-1": "cn-shanghai-finance-1", + "cn-shenzhen-finance-1": "cn-shenzhen-finance-1", + } ) type AliyunClientConfig struct { @@ -263,6 +277,14 @@ func (self *SAliyunClient) alidnsRequest(apiName string, params map[string]strin return jsonRequest(cli, "alidns.aliyuncs.com", ALIYUN_ALIDNS_API_VERSION, apiName, params, self.debug) } +func (self *SAliyunClient) cbnRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return jsonRequest(cli, "cbn.aliyuncs.com", ALIYUN_CBN_API_VERSION, apiName, params, self.debug) +} + func (self *SAliyunClient) fetchRegions() error { body, err := self.ecsRequest("DescribeRegions", map[string]string{"AcceptLanguage": "zh-CN"}) if err != nil { @@ -318,6 +340,10 @@ func (client *SAliyunClient) getOssClient(regionId string) (*oss.Client, error) } func (self *SAliyunClient) getRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) { + _id, ok := OSS_FINANCE_REGION_MAP[id] + if ok { + id = _id + } for i := 0; i < len(self.iregions); i += 1 { if self.iregions[i].GetId() == id { return self.iregions[i], nil @@ -493,6 +519,7 @@ func (region *SAliyunClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_EVENT, cloudprovider.CLOUD_CAPABILITY_CLOUDID, cloudprovider.CLOUD_CAPABILITY_DNSZONE, + cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK, } return caps } diff --git a/pkg/multicloud/aliyun/bucket.go b/pkg/multicloud/aliyun/bucket.go index 8fe4e95436..0b7968a5ee 100644 --- a/pkg/multicloud/aliyun/bucket.go +++ b/pkg/multicloud/aliyun/bucket.go @@ -86,7 +86,7 @@ func (b *SBucket) GetStorageClass() string { } func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { - return []cloudprovider.SBucketAccessUrl{ + ret := []cloudprovider.SBucketAccessUrl{ { Url: fmt.Sprintf("%s.%s", b.Name, b.region.getOSSExternalDomain()), Description: "ExtranetEndpoint", @@ -97,6 +97,31 @@ func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { Description: "IntranetEndpoint", }, } + + osscli, err := b.region.GetOssClient() + if err != nil { + return ret + } + info, err := osscli.GetBucketInfo(b.Name) + if err != nil { + return ret + } + ret = []cloudprovider.SBucketAccessUrl{} + if len(info.BucketInfo.ExtranetEndpoint) > 0 { + ret = append(ret, cloudprovider.SBucketAccessUrl{ + Url: info.BucketInfo.ExtranetEndpoint, + Description: "ExtranetEndpoint", + Primary: true, + }) + } + if len(info.BucketInfo.IntranetEndpoint) > 0 { + ret = append(ret, cloudprovider.SBucketAccessUrl{ + Url: info.BucketInfo.IntranetEndpoint, + Description: "IntranetEndpoint", + Primary: true, + }) + } + return ret } func (b *SBucket) GetStats() cloudprovider.SBucketStats { diff --git a/pkg/multicloud/aliyun/cloud_enterprise_network.go b/pkg/multicloud/aliyun/cloud_enterprise_network.go new file mode 100644 index 0000000000..54b2fe997d --- /dev/null +++ b/pkg/multicloud/aliyun/cloud_enterprise_network.go @@ -0,0 +1,393 @@ +// 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 aliyun + +import ( + "strconv" + "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/multicloud" +) + +type SCen struct { + multicloud.SResourceBase + client *SAliyunClient + Status string `json:"Status"` + ProtectionLevel string `json:"ProtectionLevel"` + CenID string `json:"CenId"` + CreationTime string `json:"CreationTime"` + CenBandwidthPackageIds CenBandwidthPackageIds `json:"CenBandwidthPackageIds"` + Name string `json:"Name"` +} + +type SCens struct { + TotalCount int `json:"TotalCount"` + RequestID string `json:"RequestId"` + PageSize int `json:"PageSize"` + PageNumber int `json:"PageNumber"` + Cens sCens `json:"Cens"` +} + +type CenBandwidthPackageIds struct { + CenBandwidthPackageID []string `json:"CenBandwidthPackageId"` +} + +type sCens struct { + Cen []SCen `json:"Cen"` +} + +type SCenChildInstances struct { + PageNumber int `json:"PageNumber"` + ChildInstances sCenChildInstances `json:"ChildInstances"` + TotalCount int `json:"TotalCount"` + PageSize int `json:"PageSize"` + RequestID string `json:"RequestId"` +} + +type SCenChildInstance struct { + Status string `json:"Status"` + ChildInstanceOwnerID string `json:"ChildInstanceOwnerId"` + ChildInstanceID string `json:"ChildInstanceId"` + ChildInstanceRegionID string `json:"ChildInstanceRegionId"` + CenID string `json:"CenId"` + ChildInstanceType string `json:"ChildInstanceType"` +} + +type sCenChildInstances struct { + ChildInstance []SCenChildInstance `json:"ChildInstance"` +} + +type SCenAttachInstanceInput struct { + InstanceType string + InstanceId string + InstanceRegion string + ChildInstanceOwnerId string +} + +func (client *SAliyunClient) DescribeCens(pageNumber int, pageSize int) (SCens, error) { + scens := SCens{} + params := map[string]string{} + params["Action"] = "DescribeCens" + params["PageNumber"] = strconv.Itoa(pageNumber) + params["PageSize"] = strconv.Itoa(pageSize) + resp, err := client.cbnRequest("DescribeCens", params) + if err != nil { + return scens, errors.Wrap(err, "DescribeCens") + } + err = resp.Unmarshal(&scens) + if err != nil { + return scens, errors.Wrap(err, "resp.Unmarshal") + } + return scens, nil +} + +func (client *SAliyunClient) GetAllCens() ([]SCen, error) { + pageNumber := 0 + sCen := []SCen{} + for { + pageNumber++ + cens, err := client.DescribeCens(pageNumber, 20) + if err != nil { + return nil, errors.Wrapf(err, "client.DescribeCens(%d, 20)", pageNumber) + } + sCen = append(sCen, cens.Cens.Cen...) + if len(sCen) >= cens.TotalCount { + break + } + } + for i := 0; i < len(sCen); i++ { + sCen[i].client = client + } + return sCen, nil +} + +func (client *SAliyunClient) CreateCen(opts *cloudprovider.SInterVpcNetworkCreateOptions) (string, error) { + params := map[string]string{} + params["Name"] = opts.Name + params["Description"] = opts.Desc + resp, err := client.cbnRequest("CreateCen", params) + if err != nil { + return "", errors.Wrap(err, "CreateCen") + } + type CentId struct { + CenId string `json:"CenId"` + } + centId := CentId{} + err = resp.Unmarshal(¢Id) + if err != nil { + return "", errors.Wrap(err, "resp.Unmarshal") + } + return centId.CenId, nil +} + +func (client *SAliyunClient) DeleteCen(id string) error { + params := map[string]string{} + params["CenId"] = id + _, err := client.cbnRequest("DeleteCen", params) + if err != nil { + return errors.Wrap(err, "DeleteCen") + } + return nil +} + +func (client *SAliyunClient) DescribeCenAttachedChildInstances(cenId string, pageNumber int, pageSize int) (SCenChildInstances, error) { + scenChilds := SCenChildInstances{} + params := map[string]string{} + params["CenId"] = cenId + params["PageNumber"] = strconv.Itoa(pageNumber) + params["PageSize"] = strconv.Itoa(pageSize) + resp, err := client.cbnRequest("DescribeCenAttachedChildInstances", params) + if err != nil { + return scenChilds, errors.Wrap(err, "DescribeCenAttachedChildInstances") + } + err = resp.Unmarshal(&scenChilds) + if err != nil { + return scenChilds, errors.Wrap(err, "resp.Unmarshal") + } + return scenChilds, nil +} + +func (client *SAliyunClient) GetAllCenAttachedChildInstances(cenId string) ([]SCenChildInstance, error) { + pageNumber := 0 + scenChilds := []SCenChildInstance{} + for { + pageNumber++ + cenChilds, err := client.DescribeCenAttachedChildInstances(cenId, pageNumber, 20) + if err != nil { + return nil, errors.Wrapf(err, "client.DescribeCens(%d, 20)", pageNumber) + } + scenChilds = append(scenChilds, cenChilds.ChildInstances.ChildInstance...) + if len(scenChilds) >= cenChilds.TotalCount { + break + } + } + return scenChilds, nil +} + +func (client *SAliyunClient) AttachCenChildInstance(cenId string, instance SCenAttachInstanceInput) error { + params := map[string]string{} + params["CenId"] = cenId + params["ChildInstanceId"] = instance.InstanceId + params["ChildInstanceRegionId"] = instance.InstanceRegion + params["ChildInstanceType"] = instance.InstanceType + params["ChildInstanceOwnerId"] = instance.ChildInstanceOwnerId + _, err := client.cbnRequest("AttachCenChildInstance", params) + if err != nil { + return errors.Wrap(err, "AttachCenChildInstance") + } + return nil +} + +func (client *SAliyunClient) DetachCenChildInstance(cenId string, instance SCenAttachInstanceInput) error { + params := map[string]string{} + params["CenId"] = cenId + params["ChildInstanceId"] = instance.InstanceId + params["ChildInstanceRegionId"] = instance.InstanceRegion + params["ChildInstanceType"] = instance.InstanceType + params["ChildInstanceOwnerId"] = instance.ChildInstanceOwnerId + _, err := client.cbnRequest("DetachCenChildInstance", params) + if err != nil { + return errors.Wrap(err, "DetachCenChildInstance") + } + return nil +} + +func (self *SCen) GetId() string { + return self.CenID +} + +func (self *SCen) GetName() string { + return self.Name +} + +func (self *SCen) GetGlobalId() string { + return self.GetId() +} + +func (self *SCen) GetStatus() string { + switch self.Status { + case "Creating": + return api.INTER_VPC_NETWORK_STATUS_CREATING + case "Active": + return api.INTER_VPC_NETWORK_STATUS_AVAILABLE + case "Deleting": + return api.INTER_VPC_NETWORK_STATUS_DELETING + default: + return api.INTER_VPC_NETWORK_STATUS_UNKNOWN + } +} + +func (self *SCen) Refresh() error { + scens, err := self.client.GetAllCens() + if err != nil { + return errors.Wrap(err, "self.client.GetAllCens()") + } + for i := range scens { + if scens[i].CenID == self.CenID { + return jsonutils.Update(self, scens[i]) + } + } + return cloudprovider.ErrNotFound +} + +func (self *SCen) GetAuthorityOwnerId() string { + return self.client.ownerId +} + +func (self *SCen) GetICloudVpcIds() ([]string, error) { + childs, err := self.client.GetAllCenAttachedChildInstances(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "self.client.GetAllCenAttachedChildInstances(self.GetId())") + } + vpcIds := []string{} + for i := range childs { + if childs[i].ChildInstanceType == "VPC" { + vpcIds = append(vpcIds, childs[i].ChildInstanceID) + } + } + return vpcIds, nil +} + +func (self *SCen) AttachVpc(opts *cloudprovider.SInterVpcNetworkAttachVpcOption) error { + instance := SCenAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: opts.VpcId, + InstanceRegion: opts.VpcRegionId, + ChildInstanceOwnerId: opts.VpcAuthorityOwnerId, + } + err := self.client.AttachCenChildInstance(self.GetId(), instance) + if err != nil { + return errors.Wrapf(err, "self.client.AttachCenChildInstance(%s,%s)", self.GetId(), jsonutils.Marshal(opts).String()) + } + return nil +} + +func (self *SCen) DetachVpc(opts *cloudprovider.SInterVpcNetworkDetachVpcOption) error { + instance := SCenAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: opts.VpcId, + InstanceRegion: opts.VpcRegionId, + ChildInstanceOwnerId: opts.VpcAuthorityOwnerId, + } + err := self.client.DetachCenChildInstance(self.GetId(), instance) + if err != nil { + return errors.Wrapf(err, "self.client.DetachCenChildInstance(%s,%s)", self.GetId(), jsonutils.Marshal(opts).String()) + } + return nil +} + +func (self *SCen) Delete() error { + err := self.client.DeleteCen(self.GetId()) + if err != nil { + return errors.Wrapf(err, "self.client.DeleteCen(%s)", self.GetId()) + } + return nil +} + +func (self *SCen) GetInstanceRouteEntries() ([]SCenRouteEntry, error) { + childInstance, err := self.client.GetAllCenAttachedChildInstances(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "self.client.GetAllCenAttachedChildInstances(self.GetId())") + } + result := []SCenRouteEntry{} + for i := range childInstance { + routes, err := self.client.GetAllCenChildInstanceRouteEntries(self.GetId(), childInstance[i].ChildInstanceID, childInstance[i].ChildInstanceRegionID, childInstance[i].ChildInstanceType) + if err != nil { + return nil, errors.Wrap(err, "self.client.GetAllCenChildInstanceRouteEntries(self.GetId(), childInstance[i].ChildInstanceID, childInstance[i].ChildInstanceRegionID, childInstance[i].ChildInstanceType)") + } + for j := range routes { + // CEN 类型的路由是通过CEN从其他vpc/vbr的路由表中传播过来的 + // 只关注路由的发源 + if routes[j].Type != "CEN" { + routes[j].ChildInstance = &childInstance[i] + result = append(result, routes[j]) + } + } + } + return result, nil +} + +func (self *SCen) GetIRoutes() ([]cloudprovider.ICloudInterVpcNetworkRoute, error) { + result := []cloudprovider.ICloudInterVpcNetworkRoute{} + routeEntries, err := self.GetInstanceRouteEntries() + if err != nil { + return nil, errors.Wrap(err, "self.GetInstanceRouteEntries()") + } + for i := range routeEntries { + result = append(result, &routeEntries[i]) + } + return result, nil +} + +func (self *SCen) EnableRouteEntry(routeId string) error { + idContent := strings.Split(routeId, ":") + if len(idContent) != 2 { + return errors.Wrapf(cloudprovider.ErrNotSupported, "invalid aliyun generated cenRouteId %s", routeId) + } + routeTable := idContent[0] + cidr := idContent[1] + routeEntries, err := self.GetInstanceRouteEntries() + if err != nil { + return errors.Wrap(err, "self.GetInstanceRouteEntries()") + } + routeEntry := SCenRouteEntry{} + for i := range routeEntries { + if routeEntries[i].RouteTableID == routeTable && routeEntries[i].DestinationCidrBlock == cidr { + routeEntry = routeEntries[i] + break + } + } + if routeEntry.GetEnabled() { + return nil + } + err = self.client.PublishRouteEntries(self.GetId(), routeEntry.GetInstanceId(), routeTable, routeEntry.GetInstanceRegionId(), routeEntry.GetInstanceType(), cidr) + if err != nil { + return errors.Wrap(err, "self.client.PublishRouteEntries()") + } + return nil +} + +func (self *SCen) DisableRouteEntry(routeId string) error { + idContent := strings.Split(routeId, ":") + if len(idContent) != 2 { + return errors.Wrapf(cloudprovider.ErrNotSupported, "invalid aliyun generated cenRouteId %s", routeId) + } + routeTable := idContent[0] + cidr := idContent[1] + routeEntries, err := self.GetInstanceRouteEntries() + if err != nil { + return errors.Wrap(err, "self.GetInstanceRouteEntries()") + } + routeEntry := SCenRouteEntry{} + for i := range routeEntries { + if routeEntries[i].RouteTableID == routeTable && routeEntries[i].DestinationCidrBlock == cidr { + routeEntry = routeEntries[i] + break + } + } + if !routeEntry.GetEnabled() { + return nil + } + err = self.client.WithdrawPublishedRouteEntries(self.GetId(), routeEntry.GetInstanceId(), routeTable, routeEntry.GetInstanceRegionId(), routeEntry.GetInstanceType(), cidr) + if err != nil { + return errors.Wrap(err, "self.client.PublishRouteEntries()") + } + return nil +} diff --git a/pkg/multicloud/aliyun/cloud_enterprise_network_route.go b/pkg/multicloud/aliyun/cloud_enterprise_network_route.go new file mode 100644 index 0000000000..ee293486c9 --- /dev/null +++ b/pkg/multicloud/aliyun/cloud_enterprise_network_route.go @@ -0,0 +1,223 @@ +// 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 aliyun + +import ( + "strconv" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SCenRouteEntries struct { + PageNumber int `json:"PageNumber"` + TotalCount int `json:"TotalCount"` + PageSize int `json:"PageSize"` + RequestID string `json:"RequestId"` + CenRouteEntries CenRouteEntries `json:"CenRouteEntries"` +} +type CenRouteMapRecord struct { + RouteMapID string `json:"RouteMapId"` + RegionID string `json:"RegionId"` +} +type CenRouteMapRecords struct { + CenRouteMapRecord []CenRouteMapRecord `json:"CenRouteMapRecord"` +} +type AsPaths struct { + AsPath []string `json:"AsPath"` +} +type Communities struct { + Community []string `json:"Community"` +} +type Conflicts struct { + Conflict []Conflict `json:"Conflict"` +} + +type Conflict struct { + DestinationCidrBlock string `json:"DestinationCidrBlock"` + InstanceId string `json:"InstanceId"` + InstanceType string `json:"InstanceType"` + RegionId string `json:"RegionId"` + Status string `json:"Status"` +} + +type SCenRouteEntry struct { + ChildInstance *SCenChildInstance + NextHopInstanceID string `json:"NextHopInstanceId,omitempty"` + Status string `json:"Status"` + OperationalMode bool `json:"OperationalMode"` + CenRouteMapRecords CenRouteMapRecords `json:"CenRouteMapRecords"` + AsPaths AsPaths `json:"AsPaths"` + Communities Communities `json:"Communities"` + Type string `json:"Type"` + NextHopType string `json:"NextHopType"` + NextHopRegionID string `json:"NextHopRegionId,omitempty"` + RouteTableID string `json:"RouteTableId"` + DestinationCidrBlock string `json:"DestinationCidrBlock"` + Conflicts Conflicts `json:"Conflicts"` + PublishStatus string `json:"PublishStatus,omitempty"` +} +type CenRouteEntries struct { + CenRouteEntry []SCenRouteEntry `json:"CenRouteEntry"` +} + +func (client *SAliyunClient) DescribeCenChildInstanceRouteEntries(cenId string, childInstanceId string, childInstanceRegion string, childInstanceType string, pageNumber int, pageSize int) (SCenRouteEntries, error) { + routeEntries := SCenRouteEntries{} + params := map[string]string{} + params["CenId"] = cenId + params["ChildInstanceId"] = childInstanceId + params["ChildInstanceRegionId"] = childInstanceRegion + params["ChildInstanceType"] = childInstanceType + params["Action"] = "DescribeCenChildInstanceRouteEntries" + params["PageNumber"] = strconv.Itoa(pageNumber) + params["PageSize"] = strconv.Itoa(pageSize) + resp, err := client.cbnRequest("DescribeCenChildInstanceRouteEntries", params) + if err != nil { + return routeEntries, errors.Wrapf(err, `client.cbnRequest("DescribeCenChildInstanceRouteEntries", %s)`, jsonutils.Marshal(params).String()) + } + err = resp.Unmarshal(&routeEntries) + if err != nil { + return routeEntries, errors.Wrapf(err, "[%s].Unmarshal(&routeEntries)", resp.String()) + } + return routeEntries, nil +} + +func (client *SAliyunClient) GetAllCenChildInstanceRouteEntries(cenId, childInstanceId, childInstanceRegion, childInstanceType string) ([]SCenRouteEntry, error) { + pageNumber := 0 + srouteEntries := []SCenRouteEntry{} + for { + pageNumber++ + routeEntries, err := client.DescribeCenChildInstanceRouteEntries(cenId, childInstanceId, childInstanceRegion, childInstanceType, pageNumber, 20) + if err != nil { + return nil, errors.Wrap(err, "client.DescribeCenChildInstanceRouteEntries(cenId, childInstanceId, pageNumber, 20)") + } + srouteEntries = append(srouteEntries, routeEntries.CenRouteEntries.CenRouteEntry...) + if len(srouteEntries) >= routeEntries.TotalCount { + break + } + } + return srouteEntries, nil +} + +func (client *SAliyunClient) PublishRouteEntries(cenId, childInstanceId, routeTableId, childInstanceRegion, childInstanceType, cidr string) error { + params := map[string]string{} + params["CenId"] = cenId + params["ChildInstanceId"] = childInstanceId + params["ChildInstanceRouteTableId"] = routeTableId + params["ChildInstanceRegionId"] = childInstanceRegion + params["ChildInstanceType"] = childInstanceType + params["DestinationCidrBlock"] = cidr + params["Action"] = "PublishRouteEntries" + _, err := client.cbnRequest("PublishRouteEntries", params) + if err != nil { + return errors.Wrapf(err, `client.cbnRequest("PublishRouteEntries", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (client *SAliyunClient) WithdrawPublishedRouteEntries(cenId, childInstanceId, routeTableId, childInstanceRegion, childInstanceType, cidr string) error { + params := map[string]string{} + params["CenId"] = cenId + params["ChildInstanceId"] = childInstanceId + params["ChildInstanceRouteTableId"] = routeTableId + params["ChildInstanceRegionId"] = childInstanceRegion + params["ChildInstanceType"] = childInstanceType + params["DestinationCidrBlock"] = cidr + params["Action"] = "WithdrawPublishedRouteEntries" + _, err := client.cbnRequest("WithdrawPublishedRouteEntries", params) + if err != nil { + return errors.Wrapf(err, `client.cbnRequest("WithdrawPublishedRouteEntries", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SCenRouteEntry) GetId() string { + return self.RouteTableID + ":" + self.DestinationCidrBlock +} + +func (self *SCenRouteEntry) GetName() string { + return self.GetId() +} + +func (self *SCenRouteEntry) GetGlobalId() string { + return self.GetId() +} + +func (self *SCenRouteEntry) GetStatus() string { + if len(self.Conflicts.Conflict) > 0 { + return api.ROUTE_ENTRY_STATUS_CONFLICT + } + return api.ROUTE_ENTRY_STATUS_AVAILIABLE +} + +func (self *SCenRouteEntry) Refresh() error { + return nil +} + +func (self *SCenRouteEntry) IsEmulated() bool { + return false +} + +func (self *SCenRouteEntry) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SCenRouteEntry) GetCidr() string { + return self.DestinationCidrBlock +} + +func (self *SCenRouteEntry) GetNextHopType() string { + switch self.NextHopType { + case "VPC": + return api.Next_HOP_TYPE_VPC + case "VBR": + return api.Next_HOP_TYPE_VBR + default: + return "" + } +} + +func (self *SCenRouteEntry) GetNextHop() string { + return self.NextHopInstanceID +} + +func (self *SCenRouteEntry) GetNextHopRegion() string { + return self.NextHopRegionID +} + +func (self *SCenRouteEntry) GetEnabled() bool { + if self.PublishStatus == "Published" { + return true + } + return false +} + +func (self *SCenRouteEntry) GetRouteTableId() string { + return self.RouteTableID +} + +func (self *SCenRouteEntry) GetInstanceId() string { + return self.ChildInstance.ChildInstanceID +} + +func (self *SCenRouteEntry) GetInstanceType() string { + return self.ChildInstance.ChildInstanceType +} + +func (self *SCenRouteEntry) GetInstanceRegionId() string { + return self.ChildInstance.ChildInstanceRegionID +} diff --git a/pkg/multicloud/aliyun/dbinstance_backup.go b/pkg/multicloud/aliyun/dbinstance_backup.go index b548cb4785..8abc1d8394 100644 --- a/pkg/multicloud/aliyun/dbinstance_backup.go +++ b/pkg/multicloud/aliyun/dbinstance_backup.go @@ -19,8 +19,7 @@ import ( "strings" "time" - "github.com/coredns/coredns/plugin/pkg/log" - + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" @@ -325,3 +324,33 @@ func (region *SRegion) waitBackupCreateComplete(instanceId, jobId string) (strin } return "", fmt.Errorf("failed to found backup job %s backupid", jobId) } + +func (self *SDBInstanceBackup) GetBackupMethod() cloudprovider.TBackupMethod { + return cloudprovider.TBackupMethod(self.BackupMethod) +} + +func (self *SDBInstanceBackup) CreateICloudDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + rdsId, err := self.region.CreateDBInstanceByBackup(self.BackupId, opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateDBInstanceByBackup") + } + return self.region.GetDBInstanceDetail(rdsId) +} + +func (self *SRegion) CreateDBInstanceByBackup(backupId string, opts *cloudprovider.SManagedDBInstanceCreateConfig) (string, error) { + params := map[string]string{ + "DBInstanceId": opts.RdsId, + "DBInstanceStorageType": opts.StorageType, + "PayType": "Postpaid", + "BackupId": backupId, + } + resp, err := self.rdsRequest("CloneDBInstance", params) + if err != nil { + return "", errors.Wrapf(err, "rdsRequest") + } + rdsId, err := resp.GetString("DBInstanceId") + if err != nil { + return "", fmt.Errorf("missing DBInstanceId after CloneDBInstance") + } + return rdsId, nil +} diff --git a/pkg/multicloud/aliyun/eip.go b/pkg/multicloud/aliyun/eip.go index e7e61f7eed..b6956c483a 100644 --- a/pkg/multicloud/aliyun/eip.go +++ b/pkg/multicloud/aliyun/eip.go @@ -172,7 +172,6 @@ func (self *SEipAddress) GetAssociationType() string { case EIP_INTANNCE_TYPE_SLB: return api.EIP_ASSOCIATE_TYPE_LOADBALANCER default: - log.Fatalf("unsupported type: %s", self.InstanceType) return "unsupported" } } diff --git a/pkg/multicloud/aliyun/elasticcache_instance.go b/pkg/multicloud/aliyun/elasticcache_instance.go index 0b3c425852..11638c126e 100644 --- a/pkg/multicloud/aliyun/elasticcache_instance.go +++ b/pkg/multicloud/aliyun/elasticcache_instance.go @@ -242,7 +242,7 @@ func (self *SElasticcache) GetVpcId() string { } func (self *SElasticcache) GetZoneId() string { - zone, err := self.region.getZoneById(self.ZoneID) + zone, err := self.region.getZoneById(transZoneIdToEcsZoneId(self.region, "redis", self.ZoneID)) if err != nil { log.Errorf("failed to find zone for elasticcache %s error: %v", self.GetId(), err) return "" @@ -613,7 +613,7 @@ func (self *SRegion) CreateIElasticcaches(ec *cloudprovider.SCloudElasticCacheIn } if len(ec.ZoneIds) > 0 { - params["ZoneId"] = ec.ZoneIds[0] + params["ZoneId"] = transZoneIdFromEcsZoneId(self, "redis", ec.ZoneIds[0]) } if len(ec.PrivateIpAddress) > 0 { @@ -882,7 +882,8 @@ func (self *SElasticcache) GetSecurityGroupIds() ([]string, error) { func (self *SElasticcache) GetICloudElasticcacheAccount(accountId string) (cloudprovider.ICloudElasticcacheAccount, error) { segs := strings.Split(accountId, "/") if len(segs) < 2 { - return nil, errors.Wrap(fmt.Errorf("%s", accountId), "elasticcache.GetICloudElasticcacheAccount invalid account id ") + log.Debugf("elasticcache.GetICloudElasticcacheAccount invalid account id %s", accountId) + return nil, errors.Wrap(cloudprovider.ErrNotFound, "invalid account id") } return self.GetICloudElasticcacheAccountByName(segs[1]) diff --git a/pkg/multicloud/aliyun/instance.go b/pkg/multicloud/aliyun/instance.go index 58506d12ea..ac70bac3ec 100644 --- a/pkg/multicloud/aliyun/instance.go +++ b/pkg/multicloud/aliyun/instance.go @@ -454,8 +454,8 @@ func (self *SInstance) StartVM(ctx context.Context) error { return cloudprovider.ErrTimeout } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { - err := self.host.zone.region.StopVM(self.InstanceId, isForce) +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce, opts.StopCharging) if err != nil { return err } @@ -657,7 +657,7 @@ func (self *SRegion) doStartVM(instanceId string) error { return self.instanceOperation(instanceId, "StartInstance", nil) } -func (self *SRegion) doStopVM(instanceId string, isForce bool) error { +func (self *SRegion) doStopVM(instanceId string, isForce, stopCharging bool) error { params := make(map[string]string) if isForce { params["ForceStop"] = "true" @@ -665,6 +665,9 @@ func (self *SRegion) doStopVM(instanceId string, isForce bool) error { params["ForceStop"] = "false" } params["StoppedMode"] = "KeepCharging" + if stopCharging { + params["StoppedMode"] = "StopCharging" + } return self.instanceOperation(instanceId, "StopInstance", params) } @@ -711,7 +714,7 @@ func (self *SRegion) StartVM(instanceId string) error { // return self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*5, time.Second*180) // 3 minutes to timeout } -func (self *SRegion) StopVM(instanceId string, isForce bool) error { +func (self *SRegion) StopVM(instanceId string, isForce, stopCharging bool) error { status, err := self.GetInstanceStatus(instanceId) if err != nil { log.Errorf("Fail to get instance status on StopVM: %s", err) @@ -724,7 +727,7 @@ func (self *SRegion) StopVM(instanceId string, isForce bool) error { log.Errorf("StopVM: vm status is %s expect %s", status, InstanceStatusRunning) return cloudprovider.ErrInvalidStatus } - return self.doStopVM(instanceId, isForce) + return self.doStopVM(instanceId, isForce, stopCharging) // if err != nil { // return err // } diff --git a/pkg/multicloud/aliyun/loadbalancer.go b/pkg/multicloud/aliyun/loadbalancer.go index c393e829e0..56221b2c6c 100644 --- a/pkg/multicloud/aliyun/loadbalancer.go +++ b/pkg/multicloud/aliyun/loadbalancer.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -133,7 +134,7 @@ func (lb *SLoadbalancer) GetNetworkIds() []string { } func (lb *SLoadbalancer) GetZoneId() string { - zone, err := lb.region.getZoneById(lb.MasterZoneId) + zone, err := lb.region.getZoneById(transZoneIdToEcsZoneId(lb.region, "elb", lb.MasterZoneId)) if err != nil { log.Errorf("failed to find zone for lb %s error: %v", lb.LoadBalancerName, err) return "" @@ -414,3 +415,67 @@ func (lb *SLoadbalancer) GetProjectId() string { func (lb *SLoadbalancer) SetMetadata(tags map[string]string, replace bool) error { return lb.region.SetResourceTags("slb", "instance", []string{lb.LoadBalancerId}, tags, replace) } + +// mapping aliyun finance zoneId to aliyun finance ecs zoneId +func transZoneIdToEcsZoneId(region *SRegion, service, zoneId string) string { + if region.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV { + switch service { + case "elb", "redis": + if utils.IsInStringArray(zoneId, []string{"cn-hangzhou-finance-b", "cn-hangzhou-finance-c", "cn-hangzhou-finance-d"}) { + return strings.Replace(zoneId, "-finance", "", -1) + } + default: + return zoneId + } + } + + return zoneId +} + +// mapping aliyun finance ecs zoneId to dest service zone id +func transZoneIdFromEcsZoneId(region *SRegion, service, zoneId string) string { + if region.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV { + switch service { + case "elb", "redis": + if utils.IsInStringArray(zoneId, []string{"cn-hangzhou-b", "cn-hangzhou-c", "cn-hangzhou-d"}) { + return strings.Replace(zoneId, "cn-hangzhou", "cn-hangzhou-finance", -1) + } + default: + return zoneId + } + } + + return zoneId +} + +// mapping aliyun finance regionId to aliyun finance ecs regionId +func transRegionIdToEcsRegionId(region *SRegion, service string) string { + if region.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV { + switch service { + case "redis": + if region.GetId() == "cn-hangzhou-finance" { + return "cn-hangzhou" + } + default: + return region.GetId() + } + } + + return region.GetId() +} + +// mapping aliyun finance regionId from aliyun finance ecs regionId +func transRegionIdFromEcsRegionId(region *SRegion, service string) string { + if region.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV { + switch service { + case "redis": + if region.GetId() == "cn-hangzhou" { + return "cn-hangzhou-finance" + } + default: + return region.GetId() + } + } + + return region.GetId() +} diff --git a/pkg/multicloud/aliyun/provider/provider.go b/pkg/multicloud/aliyun/provider/provider.go index e282701ad6..dfeeeeefb3 100644 --- a/pkg/multicloud/aliyun/provider/provider.go +++ b/pkg/multicloud/aliyun/provider/provider.go @@ -16,6 +16,7 @@ package provider import ( "context" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" @@ -174,6 +175,33 @@ func (self *SAliyunProviderFactory) ValidateUpdateCloudaccountCredential(ctx con return output, nil } +func validateClientCloudenv(client *aliyun.SAliyunClient) error { + regions := client.GetIRegions() + if len(regions) == 0 { + return nil + } + + isFinanceAccount := false + for i := range regions { + if strings.Contains(regions[i].GetId(), "-finance") { + isFinanceAccount = true + break + } + } + + if isFinanceAccount { + if regions[0].GetCloudEnv() != "FinanceCloud" { + return errors.Wrap(httperrors.ErrInvalidCredential, "aksk is aliyun finance account") + } + } else { + if regions[0].GetCloudEnv() == "FinanceCloud" { + return errors.Wrap(httperrors.ErrInvalidCredential, "aksk is not aliyun finance account") + } + } + + return nil +} + func (self *SAliyunProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig) (cloudprovider.ICloudProvider, error) { client, err := aliyun.NewAliyunClient( aliyun.NewAliyunClientConfig( @@ -185,6 +213,12 @@ func (self *SAliyunProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig if err != nil { return nil, err } + + err = validateClientCloudenv(client) + if err != nil { + return nil, errors.Wrap(err, "validateClientCloudenv") + } + return &SAliyunProvider{ SBaseProvider: cloudprovider.NewBaseProvider(self), client: client, @@ -372,3 +406,44 @@ func (self *SAliyunProvider) CreateICloudDnsZone(opts *cloudprovider.SDnsZoneCre return self.client.CreatePublicICloudDnsZone(opts) } } + +func (self *SAliyunProvider) GetICloudInterVpcNetworks() ([]cloudprovider.ICloudInterVpcNetwork, error) { + scens, err := self.client.GetAllCens() + if err != nil { + return nil, errors.Wrap(err, "self.client.GetAllCens()") + } + + iVpcNetworks := []cloudprovider.ICloudInterVpcNetwork{} + for i := range scens { + iVpcNetworks = append(iVpcNetworks, &scens[i]) + } + return iVpcNetworks, nil + +} +func (self *SAliyunProvider) GetICloudInterVpcNetworkById(id string) (cloudprovider.ICloudInterVpcNetwork, error) { + iVpcNetwork, err := self.GetICloudInterVpcNetworks() + if err != nil { + return nil, errors.Wrap(err, "self.GetICloudInterVpcNetworks()") + } + for i := range iVpcNetwork { + if iVpcNetwork[i].GetId() == id { + return iVpcNetwork[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} +func (self *SAliyunProvider) CreateICloudInterVpcNetwork(opts *cloudprovider.SInterVpcNetworkCreateOptions) (cloudprovider.ICloudInterVpcNetwork, error) { + cenId, err := self.client.CreateCen(opts) + if err != nil { + return nil, errors.Wrapf(err, "self.client.CreateCen(%s)", jsonutils.Marshal(opts).String()) + } + ivpcNetwork, err := self.GetICloudInterVpcNetworkById(cenId) + if err != nil { + return nil, errors.Wrapf(err, "self.GetICloudInterVpcNetworkById(%s)", cenId) + } + return ivpcNetwork, nil +} + +func (self *SAliyunProvider) GetCloudRegionExternalIdPrefix() string { + return self.client.GetAccessEnv() + "/" +} diff --git a/pkg/multicloud/aliyun/region.go b/pkg/multicloud/aliyun/region.go index 31516c0503..64812b82e2 100644 --- a/pkg/multicloud/aliyun/region.go +++ b/pkg/multicloud/aliyun/region.go @@ -91,9 +91,23 @@ func (self *SRegion) getOSSInternalDomain() string { return getOSSInternalDomain(self.RegionId) } +func (self *SRegion) getRegionId() string { + if self.client.cloudEnv == ALIYUN_FINANCE_CLOUDENV { + switch self.RegionId { + case "cn-hangzhou": + return "cn-hzfinance" + case "cn-shanghai-finance-1": + return "cn-shanghai-finance-1-pub" + case "cn-shenzhen-finance-1": + return "cn-szfinance" + } + } + return self.RegionId +} + func (self *SRegion) GetOssClient() (*oss.Client, error) { if self.ossClient == nil { - cli, err := self.client.getOssClient(self.RegionId) + cli, err := self.client.getOssClient(self.getRegionId()) if err != nil { return nil, errors.Wrap(err, "self.client.getOssClient") } @@ -137,6 +151,10 @@ func (self *SRegion) kvsRequest(action string, params map[string]string) (jsonut return nil, err } + if _, ok := params["RegionId"]; ok { + params["RegionId"] = transRegionIdFromEcsRegionId(self, "redis") + } + return jsonRequest(client, "r-kvstore.aliyuncs.com", ALIYUN_API_VERSION_KVS, action, params, self.client.debug) } @@ -893,7 +911,7 @@ func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbala params["RegionId"] = region.RegionId params["LoadBalancerName"] = loadbalancer.Name if len(loadbalancer.ZoneID) > 0 { - params["MasterZoneId"] = loadbalancer.ZoneID + params["MasterZoneId"] = transZoneIdFromEcsZoneId(region, "elb", loadbalancer.ZoneID) } if len(loadbalancer.VpcID) > 0 { @@ -990,9 +1008,7 @@ func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) { } ret := make([]cloudprovider.ICloudBucket, 0) for i := range iBuckets { - loc := iBuckets[i].GetLocation() - // remove oss- prefix - if loc[4:] != region.GetId() { + if iBuckets[i].GetIRegion().GetId() != region.GetId() { continue } ret = append(ret, iBuckets[i]) diff --git a/pkg/multicloud/aliyun/routetable.go b/pkg/multicloud/aliyun/routetable.go index 5d1679ba3e..76da554d4a 100644 --- a/pkg/multicloud/aliyun/routetable.go +++ b/pkg/multicloud/aliyun/routetable.go @@ -58,7 +58,7 @@ func (route *SRouteEntry) GetGlobalId() string { } func (route *SRouteEntry) GetStatus() string { - return "" + return api.ROUTE_ENTRY_STATUS_AVAILIABLE } func (route *SRouteEntry) Refresh() error { @@ -180,7 +180,6 @@ func (self *SRouteTable) GetType() cloudprovider.RouteTableType { return cloudprovider.RouteTableTypeCustom default: return cloudprovider.RouteTableTypeSystem - } } @@ -199,7 +198,7 @@ func (self *SRouteTable) GetIRoutes() ([]cloudprovider.ICloudRoute, error) { } func (self *SRouteTable) GetStatus() string { - return "" + return api.ROUTE_TABLE_AVAILABLE } func (self *SRouteTable) IsEmulated() bool { diff --git a/pkg/multicloud/aliyun/shell/cen_route.go b/pkg/multicloud/aliyun/shell/cen_route.go new file mode 100644 index 0000000000..ea260d2fd7 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/cen_route.go @@ -0,0 +1,69 @@ +// 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 +// PageSizeations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CenRouteListOptions struct { + CENID string + CHILDINSTANCEID string + CHILDINSTANCEREGION string + CHILDINSTANCETYPE string + } + shellutils.R(&CenRouteListOptions{}, "cen-route-list", "List cloud enterprise network route", func(cli *aliyun.SRegion, args *CenRouteListOptions) error { + routes, e := cli.GetClient().GetAllCenChildInstanceRouteEntries(args.CENID, args.CHILDINSTANCEID, args.CHILDINSTANCEREGION, args.CHILDINSTANCETYPE) + if e != nil { + return e + } + printList(routes, len(routes), 1, len(routes), []string{}) + return nil + }) + + type PublishCenRouteOptions struct { + CENID string + CHILDINSTANCEID string + CHILDINSTANCEREGION string + CHILDINSTANCETYPE string + ROUTETABLEID string + CIDR string + } + shellutils.R(&PublishCenRouteOptions{}, "cen-route-publish", "publish cloud enterprise network route", func(cli *aliyun.SRegion, args *PublishCenRouteOptions) error { + e := cli.GetClient().PublishRouteEntries(args.CENID, args.CHILDINSTANCEID, args.ROUTETABLEID, args.CHILDINSTANCEREGION, args.CHILDINSTANCETYPE, args.CIDR) + if e != nil { + return e + } + return nil + }) + + type WithDrawCenRouteOptions struct { + CENID string + CHILDINSTANCEID string + CHILDINSTANCEREGION string + CHILDINSTANCETYPE string + ROUTETABLEID string + CIDR string + } + shellutils.R(&PublishCenRouteOptions{}, "cen-route-withdraw", "withdraw cloud enterprise network route", func(cli *aliyun.SRegion, args *PublishCenRouteOptions) error { + e := cli.GetClient().WithdrawPublishedRouteEntries(args.CENID, args.CHILDINSTANCEID, args.ROUTETABLEID, args.CHILDINSTANCEREGION, args.CHILDINSTANCETYPE, args.CIDR) + if e != nil { + return e + } + return nil + }) +} diff --git a/pkg/multicloud/aliyun/shell/cloud_enterprise_network.go b/pkg/multicloud/aliyun/shell/cloud_enterprise_network.go new file mode 100644 index 0000000000..afadea7d9b --- /dev/null +++ b/pkg/multicloud/aliyun/shell/cloud_enterprise_network.go @@ -0,0 +1,115 @@ +// 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 +// PageSizeations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CenListOptions struct { + PageSize int `help:"page size"` + PageNumber int `help:"page PageNumber"` + } + shellutils.R(&CenListOptions{}, "cen-list", "List cloud enterprise network", func(cli *aliyun.SRegion, args *CenListOptions) error { + scens, e := cli.GetClient().DescribeCens(args.PageNumber, args.PageSize) + if e != nil { + return e + } + printList(scens.Cens.Cen, scens.TotalCount, args.PageNumber, args.PageSize, []string{}) + return nil + }) + + type CenChildListOptions struct { + ID string `help:"cen id"` + PageSize int `help:"page size"` + PageNumber int `help:"page PageNumber"` + } + shellutils.R(&CenChildListOptions{}, "cen-child-list", "List cloud enterprise network childs", func(cli *aliyun.SRegion, args *CenChildListOptions) error { + schilds, e := cli.GetClient().DescribeCenAttachedChildInstances(args.ID, args.PageNumber, args.PageSize) + if e != nil { + return e + } + printList(schilds.ChildInstances.ChildInstance, schilds.TotalCount, args.PageNumber, args.PageSize, []string{}) + return nil + }) + + type CenCreateOptions struct { + Name string + Description string + } + shellutils.R(&CenCreateOptions{}, "cen-create", "Create cloud enterprise network", func(cli *aliyun.SRegion, args *CenCreateOptions) error { + opts := cloudprovider.SInterVpcNetworkCreateOptions{} + opts.Name = args.Name + opts.Desc = args.Description + scen, e := cli.GetClient().CreateCen(&opts) + if e != nil { + return e + } + print(scen) + return nil + }) + + type CenDeleteOptions struct { + ID string + } + shellutils.R(&CenDeleteOptions{}, "cen-delete", "delete cloud enterprise network", func(cli *aliyun.SRegion, args *CenDeleteOptions) error { + e := cli.GetClient().DeleteCen(args.ID) + if e != nil { + return e + } + return nil + }) + + type CenAddVpcOptions struct { + ID string + VpcId string + VpcRegionId string + } + shellutils.R(&CenAddVpcOptions{}, "cen-add-vpc", "add vpc to cloud enterprise network", func(cli *aliyun.SRegion, args *CenAddVpcOptions) error { + instance := aliyun.SCenAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: args.VpcId, + InstanceRegion: args.VpcRegionId, + } + + e := cli.GetClient().AttachCenChildInstance(args.ID, instance) + if e != nil { + return e + } + return nil + }) + + type CenRemoveVpcOptions struct { + ID string + VpcId string + VpcRegionId string + } + shellutils.R(&CenRemoveVpcOptions{}, "cen-remove-vpc", "remove vpc to cloud enterprise network", func(cli *aliyun.SRegion, args *CenRemoveVpcOptions) error { + instance := aliyun.SCenAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: args.VpcId, + InstanceRegion: args.VpcRegionId, + } + + e := cli.GetClient().DetachCenChildInstance(args.ID, instance) + if e != nil { + return e + } + return nil + }) +} diff --git a/pkg/multicloud/aliyun/shell/instance.go b/pkg/multicloud/aliyun/shell/instance.go index c9172d250e..477af327c9 100644 --- a/pkg/multicloud/aliyun/shell/instance.go +++ b/pkg/multicloud/aliyun/shell/instance.go @@ -116,11 +116,12 @@ func init() { }) type InstanceStopOptions struct { - ID string `help:"instance ID"` - Force bool `help:"Force stop instance"` + ID string `help:"instance ID"` + Force bool `help:"Force stop instance"` + StopCharging bool `help:"Stop Charging"` } shellutils.R(&InstanceStopOptions{}, "instance-stop", "Stop a instance", func(cli *aliyun.SRegion, args *InstanceStopOptions) error { - err := cli.StopVM(args.ID, args.Force) + err := cli.StopVM(args.ID, args.Force, args.StopCharging) if err != nil { return err } diff --git a/pkg/multicloud/aliyun/shell/region.go b/pkg/multicloud/aliyun/shell/region.go index 66500c960e..175ce8061f 100644 --- a/pkg/multicloud/aliyun/shell/region.go +++ b/pkg/multicloud/aliyun/shell/region.go @@ -16,10 +16,12 @@ package shell import ( "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/multicloud/test" "yunion.io/x/onecloud/pkg/util/shellutils" ) func init() { + test.TestShell() type RegionListOptions struct { } shellutils.R(&RegionListOptions{}, "region-list", "List regions", func(cli *aliyun.SRegion, args *RegionListOptions) error { diff --git a/pkg/multicloud/aliyun/vpc.go b/pkg/multicloud/aliyun/vpc.go index e70118f242..23f1e8bd83 100644 --- a/pkg/multicloud/aliyun/vpc.go +++ b/pkg/multicloud/aliyun/vpc.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud" @@ -290,3 +291,35 @@ func (self *SVpc) GetINatGateways() ([]cloudprovider.ICloudNatGateway, error) { } return inats, nil } + +func (self *SVpc) GetAuthorityOwnerId() string { + return self.region.client.ownerId +} + +func (self *SRegion) GrantInstanceToCen(opts *cloudprovider.SVpcJointInterVpcNetworkOption, instance SCenAttachInstanceInput) error { + params := make(map[string]string) + params["CenId"] = opts.InterVpcNetworkId + params["CenOwnerId"] = opts.NetworkAuthorityOwnerId + + params["InstanceId"] = instance.InstanceId + params["InstanceType"] = instance.InstanceType + params["RegionId"] = instance.InstanceRegion + _, err := self.vpcRequest("GrantInstanceToCen", params) + if err != nil { + return errors.Wrapf(err, `self.vpcRequest("GrantInstanceToCen", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SVpc) ProposeJoinICloudInterVpcNetwork(opts *cloudprovider.SVpcJointInterVpcNetworkOption) error { + instance := SCenAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: self.GetId(), + InstanceRegion: self.region.GetId(), + } + err := self.region.GrantInstanceToCen(opts, instance) + if err != nil { + return errors.Wrapf(err, "self.region.GrantInstanceToCen(%s,%s)", self.GetId(), jsonutils.Marshal(opts).String()) + } + return nil +} diff --git a/pkg/multicloud/aliyun/zone.go b/pkg/multicloud/aliyun/zone.go index a320b9d511..43a1b039bf 100644 --- a/pkg/multicloud/aliyun/zone.go +++ b/pkg/multicloud/aliyun/zone.go @@ -141,7 +141,15 @@ func (self *SZone) GetId() string { func (self *SZone) GetName() string { if self.region.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV && !strings.Contains(self.LocalName, "金融") { - return fmt.Sprintf("%s %s %s", CLOUD_PROVIDER_ALIYUN_CN, self.LocalName, "金融云") + i := strings.Index(self.LocalName, "可用") + var localname string + if i >= 0 { + localname = self.LocalName[0:i] + "金融云 " + self.LocalName[i:] + } else { + localname = self.LocalName + " 金融云" + } + + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_ALIYUN_CN, localname) } else { return fmt.Sprintf("%s %s", CLOUD_PROVIDER_ALIYUN_CN, self.LocalName) } diff --git a/pkg/multicloud/apsara/apsara.go b/pkg/multicloud/apsara/apsara.go new file mode 100644 index 0000000000..7f520afd30 --- /dev/null +++ b/pkg/multicloud/apsara/apsara.go @@ -0,0 +1,487 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + v "yunion.io/x/pkg/util/version" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +const ( + CLOUD_PROVIDER_APSARA = api.CLOUD_PROVIDER_APSARA + CLOUD_PROVIDER_APSARA_CN = "阿里云专有云" + + APSARA_API_VERSION = "2014-05-26" + APSARA_API_VERSION_VPC = "2016-04-28" + APSARA_API_VERSION_LB = "2014-05-15" + APSARA_API_VERSION_KVS = "2015-01-01" + + APSARA_API_VERSION_TRIAL = "2017-12-04" + + APSARA_BSS_API_VERSION = "2017-12-14" + + APSARA_RAM_API_VERSION = "2015-05-01" + APSARA_API_VERION_RDS = "2014-08-15" + APSARA_RM_API_VERSION = "2020-03-31" + APSARA_STS_API_VERSION = "2015-04-01" + + APSARA_PRODUCT_METRICS = "metrics" + APSARA_PRODUCT_RDS = "rds" + APSARA_PRODUCT_VPC = "vpc" + APSARA_PRODUCT_KVSTORE = "r-kvstore" + APSARA_PRODUCT_SLB = "slb" + APSARA_PRODUCT_ECS = "ecs" + APSARA_PRODUCT_ACTION_TRIAL = "actiontrail" + APSARA_PRODUCT_STS = "sts" + APSARA_PRODUCT_RAM = "ram" + APSARA_PRODUCT_RESOURCE_MANAGER = "resourcemanager" +) + +type ApsaraClientConfig struct { + cpcfg cloudprovider.ProviderConfig + accessKey string + accessSecret string + endpoints cloudprovider.SApsaraEndpoints + debug bool +} + +func NewApsaraClientConfig(accessKey, accessSecret string, endpoints cloudprovider.SApsaraEndpoints) *ApsaraClientConfig { + cfg := &ApsaraClientConfig{ + accessKey: accessKey, + accessSecret: accessSecret, + endpoints: endpoints, + } + return cfg +} + +func (cfg *ApsaraClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *ApsaraClientConfig { + cfg.cpcfg = cpcfg + return cfg +} + +func (cfg *ApsaraClientConfig) Debug(debug bool) *ApsaraClientConfig { + cfg.debug = debug + return cfg +} + +func (cfg ApsaraClientConfig) Copy() ApsaraClientConfig { + return cfg +} + +type SApsaraClient struct { + *ApsaraClientConfig + + ownerId string + ownerName string + + iregions []cloudprovider.ICloudRegion + iBuckets []cloudprovider.ICloudBucket +} + +func NewApsaraClient(cfg *ApsaraClientConfig) (*SApsaraClient, error) { + client := SApsaraClient{ + ApsaraClientConfig: cfg, + } + err := client.fetchRegions() + if err != nil { + return nil, errors.Wrap(err, "fetchRegions") + } + if len(client.endpoints.OssEndpoint) > 0 { + err = client.fetchBuckets() + if err != nil { + return nil, errors.Wrapf(err, "fetchBuckets") + } + } + if client.debug { + log.Debugf("ClientID: %s ClientName: %s", client.ownerId, client.ownerName) + } + return &client, nil +} + +func productRequest(client *sdk.Client, product, domain, apiVersion, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + return jsonRequest(client, domain, apiVersion, apiName, params, debug) +} + +func jsonRequest(client *sdk.Client, domain, apiVersion, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + if debug { + log.Debugf("request %s %s %s %s", domain, apiVersion, apiName, params) + } + for i := 1; i < 4; i++ { + resp, err := _jsonRequest(client, domain, apiVersion, apiName, params) + retry := false + if err != nil { + for _, code := range []string{ + "InvalidAccessKeyId.NotFound", + } { + if strings.Contains(err.Error(), code) { + return nil, err + } + } + for _, code := range []string{"404 Not Found", "EntityNotExist.Role", "EntityNotExist.Group"} { + if strings.Contains(err.Error(), code) { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error()) + } + } + for _, code := range []string{ + "EOF", + "i/o timeout", + "TLS handshake timeout", + "connection reset by peer", + "server misbehaving", + "SignatureNonceUsed", + "InvalidInstance.NotSupported", + "try later", + "BackendServer.configuring", + "Another operation is being performed", //Another operation is being performed on the DB instance or the DB instance is faulty(赋予RDS账号权限) + } { + if strings.Contains(err.Error(), code) { + retry = true + break + } + } + } + if retry { + if debug { + log.Debugf("Retry %d...", i) + } + time.Sleep(time.Second * time.Duration(i*10)) + continue + } + if debug { + log.Debugf("Response: %s", resp) + } + return resp, err + } + return nil, fmt.Errorf("timeout for request %s params: %s", apiName, params) +} + +func _jsonRequest(client *sdk.Client, domain string, version string, apiName string, params map[string]string) (jsonutils.JSONObject, error) { + req := requests.NewCommonRequest() + req.Domain = domain + req.Version = version + req.ApiName = apiName + if params != nil { + for k, v := range params { + req.QueryParams[k] = v + } + } + req.Scheme = "http" + //req.Scheme = "https" + req.GetHeaders()["User-Agent"] = "vendor/yunion-OneCloud@" + v.Get().GitVersion + + resp, err := processCommonRequest(client, req) + if err != nil { + log.Errorf("request %s error %s with params %s", apiName, err, params) + return nil, err + } + body, err := jsonutils.Parse(resp.GetHttpContentBytes()) + if err != nil { + log.Errorf("parse json fail %s", err) + return nil, err + } + //{"Code":"InvalidInstanceType.ValueNotSupported","HostId":"ecs.apsaracs.com","Message":"The specified instanceType beyond the permitted range.","RequestId":"0042EE30-0EDF-48A7-A414-56229D4AD532"} + //{"Code":"200","Message":"successful","PageNumber":1,"PageSize":50,"RequestId":"BB4C970C-0E23-48DC-A3B0-EB21FFC70A29","RouterTableList":{"RouterTableListType":[{"CreationTime":"2017-03-19T13:37:40Z","Description":"","ResourceGroupId":"rg-acfmwie3cqoobmi","RouteTableId":"vtb-j6c60lectdi80rk5xz43g","RouteTableName":"","RouteTableType":"System","RouterId":"vrt-j6c00qrol733dg36iq4qj","RouterType":"VRouter","VSwitchIds":{"VSwitchId":["vsw-j6c3gig5ub4fmi2veyrus"]},"VpcId":"vpc-j6c86z3sh8ufhgsxwme0q"}]},"Success":true,"TotalCount":1} + if body.Contains("Code") { + code, _ := body.GetString("Code") + if len(code) > 0 && !utils.IsInStringArray(code, []string{"200"}) { + return nil, fmt.Errorf(body.String()) + } + } + return body, nil +} + +func (self *SApsaraClient) getDefaultClient() (*sdk.Client, error) { + regionId := "" + if len(self.iregions) > 0 { + regionId = self.iregions[0].GetId() + } + transport := httputils.GetTransport(true) + transport.Proxy = self.cpcfg.ProxyFunc + client, err := sdk.NewClientWithOptions( + regionId, + &sdk.Config{ + HttpTransport: transport, + }, + &credentials.BaseCredential{ + AccessKeyId: self.accessKey, + AccessKeySecret: self.accessSecret, + }, + ) + return client, err +} + +func (self *SApsaraClient) rmRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return productRequest(cli, APSARA_PRODUCT_RESOURCE_MANAGER, self.endpoints.ResourcemanagerEndpoint, APSARA_RM_API_VERSION, apiName, params, self.debug) +} + +func (self *SApsaraClient) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return productRequest(cli, APSARA_PRODUCT_ECS, self.endpoints.EcsEndpoint, APSARA_API_VERSION, apiName, params, self.debug) +} + +func (self *SApsaraClient) trialRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return productRequest(cli, APSARA_PRODUCT_ACTION_TRIAL, self.endpoints.ActionTrailEndpoint, APSARA_API_VERSION_TRIAL, apiName, params, self.debug) +} + +func (self *SApsaraClient) fetchRegions() error { + body, err := self.ecsRequest("DescribeRegions", map[string]string{"AcceptLanguage": "zh-CN"}) + if err != nil { + log.Errorf("fetchRegions fail %s", err) + return err + } + + regions := make([]SRegion, 0) + err = body.Unmarshal(®ions, "Regions", "Region") + if err != nil { + log.Errorf("unmarshal json error %s", err) + return err + } + self.iregions = make([]cloudprovider.ICloudRegion, len(regions)) + for i := 0; i < len(regions); i += 1 { + regions[i].client = self + self.iregions[i] = ®ions[i] + } + return nil +} + +// https://help.apsara.com/document_detail/31837.html?spm=a2c4g.11186623.2.6.XqEgD1 +func (client *SApsaraClient) getOssClient(regionId string) (*oss.Client, error) { + // NOTE + // + // oss package as of version 20181116160301-c6838fdc33ed does not + // respect http.ProxyFromEnvironment. + // + // The ClientOption Proxy, AuthProxy lacks the feature NO_PROXY has + // which can be used to whitelist ips, domains from http_proxy, + // https_proxy setting + // oss use no timeout client so as to send/download large files + httpClient := client.cpcfg.AdaptiveTimeoutHttpClient() + cliOpts := []oss.ClientOption{ + oss.HTTPClient(httpClient), + } + cli, err := oss.New(client.endpoints.OssEndpoint, client.accessKey, client.accessSecret, cliOpts...) + if err != nil { + return nil, errors.Wrap(err, "oss.New") + } + return cli, nil +} + +func (self *SApsaraClient) getRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) { + for i := 0; i < len(self.iregions); i += 1 { + if self.iregions[i].GetId() == id { + return self.iregions[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SApsaraClient) invalidateIBuckets() { + self.iBuckets = nil +} + +func (self *SApsaraClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) { + if len(self.endpoints.OssEndpoint) == 0 { + return nil, fmt.Errorf("empty oss endpoint") + } + if self.iBuckets == nil { + err := self.fetchBuckets() + if err != nil { + return nil, errors.Wrap(err, "fetchBuckets") + } + } + return self.iBuckets, nil +} + +func (self *SApsaraClient) fetchBuckets() error { + osscli, err := self.getOssClient("") + if err != nil { + return errors.Wrap(err, "self.getOssClient") + } + result, err := osscli.ListBuckets() + if err != nil { + return errors.Wrap(err, "oss.ListBuckets") + } + + self.ownerId = result.Owner.ID + self.ownerName = result.Owner.DisplayName + + ret := make([]cloudprovider.ICloudBucket, 0) + for _, bInfo := range result.Buckets { + regionId := bInfo.Location[4:] + region, err := self.getRegionByRegionId(regionId) + if err != nil { + log.Errorf("cannot find bucket's region %s", regionId) + continue + } + b := SBucket{ + region: region.(*SRegion), + Name: bInfo.Name, + Location: bInfo.Location, + CreationDate: bInfo.CreationDate, + StorageClass: bInfo.StorageClass, + } + ret = append(ret, &b) + } + self.iBuckets = ret + return nil +} + +func (self *SApsaraClient) GetRegions() []SRegion { + regions := make([]SRegion, len(self.iregions)) + for i := 0; i < len(regions); i += 1 { + region := self.iregions[i].(*SRegion) + regions[i] = *region + } + return regions +} + +func (self *SApsaraClient) GetProvider() string { + return self.cpcfg.Vendor +} + +func (self *SApsaraClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + err := self.fetchRegions() + if err != nil { + return nil, err + } + subAccount := cloudprovider.SSubAccount{} + subAccount.Name = self.cpcfg.Name + subAccount.Account = self.accessKey + subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL + return []cloudprovider.SSubAccount{subAccount}, nil +} + +func (self *SApsaraClient) GetAccountId() string { + return self.cpcfg.EcsEndpoint +} + +func (self *SApsaraClient) GetIRegions() []cloudprovider.ICloudRegion { + return self.iregions +} + +func (self *SApsaraClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { + for i := 0; i < len(self.iregions); i += 1 { + if self.iregions[i].GetGlobalId() == id { + return self.iregions[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SApsaraClient) GetRegion(regionId string) *SRegion { + for i := 0; i < len(self.iregions); i += 1 { + if self.iregions[i].GetId() == regionId { + return self.iregions[i].(*SRegion) + } + } + return nil +} + +func (self *SApsaraClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + for i := 0; i < len(self.iregions); i += 1 { + ihost, err := self.iregions[i].GetIHostById(id) + if err == nil { + return ihost, nil + } else if err != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SApsaraClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { + for i := 0; i < len(self.iregions); i += 1 { + ihost, err := self.iregions[i].GetIVpcById(id) + if err == nil { + return ihost, nil + } else if err != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SApsaraClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + for i := 0; i < len(self.iregions); i += 1 { + ihost, err := self.iregions[i].GetIStorageById(id) + if err == nil { + return ihost, nil + } else if err != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SApsaraClient) GetIProjects() ([]cloudprovider.ICloudProject, error) { + pageSize, pageNumber := 50, 1 + resourceGroups := []SResourceGroup{} + for { + parts, total, err := self.GetResourceGroups(pageNumber, pageSize) + if err != nil { + return nil, errors.Wrap(err, "GetResourceGroups") + } + resourceGroups = append(resourceGroups, parts...) + if len(resourceGroups) >= total { + break + } + pageNumber += 1 + } + ret := []cloudprovider.ICloudProject{} + for i := range resourceGroups { + ret = append(ret, &resourceGroups[i]) + } + return ret, nil +} + +func (region *SApsaraClient) GetCapabilities() []string { + caps := []string{ + cloudprovider.CLOUD_CAPABILITY_PROJECT, + cloudprovider.CLOUD_CAPABILITY_COMPUTE, + cloudprovider.CLOUD_CAPABILITY_NETWORK, + cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, + cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, + cloudprovider.CLOUD_CAPABILITY_RDS, + cloudprovider.CLOUD_CAPABILITY_CACHE, + } + return caps +} diff --git a/pkg/multicloud/apsara/bucket.go b/pkg/multicloud/apsara/bucket.go new file mode 100644 index 0000000000..77b152008a --- /dev/null +++ b/pkg/multicloud/apsara/bucket.go @@ -0,0 +1,458 @@ +// 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 apsara + +import ( + "context" + "fmt" + "io" + "net/http" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SBucket struct { + multicloud.SBaseBucket + + region *SRegion + + Name string + Location string + CreationDate time.Time + StorageClass string +} + +func (b *SBucket) GetProjectId() string { + return "" +} + +func (b *SBucket) GetGlobalId() string { + return b.Name +} + +func (b *SBucket) GetName() string { + return b.Name +} + +func (b *SBucket) GetAcl() cloudprovider.TBucketACLType { + acl := cloudprovider.ACLPrivate + osscli, err := b.region.GetOssClient() + if err != nil { + log.Errorf("b.region.GetOssClient fail %s", err) + return acl + } + aclResp, err := osscli.GetBucketACL(b.Name) + if err != nil { + log.Errorf("osscli.GetBucketACL fail %s", err) + return acl + } + acl = cloudprovider.TBucketACLType(aclResp.ACL) + return acl +} + +func (b *SBucket) GetLocation() string { + return b.Location +} + +func (b *SBucket) GetIRegion() cloudprovider.ICloudRegion { + return b.region +} + +func (b *SBucket) GetCreateAt() time.Time { + return b.CreationDate +} + +func (b *SBucket) GetStorageClass() string { + return b.StorageClass +} + +func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { + return []cloudprovider.SBucketAccessUrl{ + { + Url: fmt.Sprintf("%s.%s", b.Name, b.region.client.endpoints.OssEndpoint), + Description: "ExtranetEndpoint", + Primary: true, + }, + } +} + +func (b *SBucket) GetStats() cloudprovider.SBucketStats { + stats, err := cloudprovider.GetIBucketStats(b) + if err != nil { + log.Errorf("GetStats fail %s", err) + } + return stats +} + +func (b *SBucket) SetAcl(aclStr cloudprovider.TBucketACLType) error { + osscli, err := b.region.GetOssClient() + if err != nil { + log.Errorf("b.region.GetOssClient fail %s", err) + return errors.Wrap(err, "b.region.GetOssClient") + } + acl, err := str2Acl(string(aclStr)) + if err != nil { + return errors.Wrap(err, "str2Acl") + } + err = osscli.SetBucketACL(b.Name, acl) + if err != nil { + return errors.Wrap(err, "SetBucketACL") + } + return nil +} + +func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) { + result := cloudprovider.SListObjectResult{} + osscli, err := b.region.GetOssClient() + if err != nil { + return result, errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return result, errors.Wrap(err, "Bucket") + } + opts := make([]oss.Option, 0) + if len(prefix) > 0 { + opts = append(opts, oss.Prefix(prefix)) + } + if len(delimiter) > 0 { + opts = append(opts, oss.Delimiter(delimiter)) + } + if len(marker) > 0 { + opts = append(opts, oss.Marker(marker)) + } + if maxCount > 0 { + opts = append(opts, oss.MaxKeys(maxCount)) + } + oResult, err := bucket.ListObjects(opts...) + if err != nil { + return result, errors.Wrap(err, "ListObjects") + } + result.Objects = make([]cloudprovider.ICloudObject, 0) + for _, object := range oResult.Objects { + obj := &SObject{ + bucket: b, + SBaseCloudObject: cloudprovider.SBaseCloudObject{ + StorageClass: object.StorageClass, + Key: object.Key, + SizeBytes: object.Size, + ETag: object.ETag, + LastModified: object.LastModified, + }, + } + result.Objects = append(result.Objects, obj) + } + if oResult.CommonPrefixes != nil { + result.CommonPrefixes = make([]cloudprovider.ICloudObject, len(oResult.CommonPrefixes)) + for i, commPrefix := range oResult.CommonPrefixes { + result.CommonPrefixes[i] = &SObject{ + bucket: b, + SBaseCloudObject: cloudprovider.SBaseCloudObject{Key: commPrefix}, + } + } + } + result.IsTruncated = oResult.IsTruncated + result.NextMarker = oResult.NextMarker + return result, nil +} + +func metaOpts(opts []oss.Option, meta http.Header) []oss.Option { + for k, v := range meta { + if len(v) == 0 { + continue + } + switch http.CanonicalHeaderKey(k) { + case cloudprovider.META_HEADER_CONTENT_TYPE: + opts = append(opts, oss.ContentType(v[0])) + case cloudprovider.META_HEADER_CONTENT_MD5: + opts = append(opts, oss.ContentMD5(v[0])) + case cloudprovider.META_HEADER_CONTENT_LANGUAGE: + opts = append(opts, oss.ContentLanguage(v[0])) + case cloudprovider.META_HEADER_CONTENT_ENCODING: + opts = append(opts, oss.ContentEncoding(v[0])) + case cloudprovider.META_HEADER_CONTENT_DISPOSITION: + opts = append(opts, oss.ContentDisposition(v[0])) + case cloudprovider.META_HEADER_CACHE_CONTROL: + opts = append(opts, oss.CacheControl(v[0])) + default: + opts = append(opts, oss.Meta(http.CanonicalHeaderKey(k), v[0])) + } + } + return opts +} + +func (b *SBucket) PutObject(ctx context.Context, key string, input io.Reader, sizeBytes int64, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { + osscli, err := b.region.GetOssClient() + if err != nil { + return errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return errors.Wrap(err, "Bucket") + } + opts := make([]oss.Option, 0) + if sizeBytes > 0 { + opts = append(opts, oss.ContentLength(sizeBytes)) + } + if meta != nil { + opts = metaOpts(opts, meta) + } + if len(cannedAcl) == 0 { + cannedAcl = b.GetAcl() + } + acl, err := str2Acl(string(cannedAcl)) + if err != nil { + return errors.Wrap(err, "") + } + opts = append(opts, oss.ObjectACL(acl)) + if len(storageClassStr) > 0 { + storageClass, err := str2StorageClass(storageClassStr) + if err != nil { + return errors.Wrap(err, "str2StorageClass") + } + opts = append(opts, oss.ObjectStorageClass(storageClass)) + } + return bucket.PutObject(key, input, opts...) +} + +func (b *SBucket) NewMultipartUpload(ctx context.Context, key string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) (string, error) { + osscli, err := b.region.GetOssClient() + if err != nil { + return "", errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return "", errors.Wrap(err, "Bucket") + } + opts := make([]oss.Option, 0) + if meta != nil { + opts = metaOpts(opts, meta) + } + if len(cannedAcl) == 0 { + cannedAcl = b.GetAcl() + } + acl, err := str2Acl(string(cannedAcl)) + if err != nil { + return "", errors.Wrap(err, "str2Acl") + } + opts = append(opts, oss.ObjectACL(acl)) + if len(storageClassStr) > 0 { + storageClass, err := str2StorageClass(storageClassStr) + if err != nil { + return "", errors.Wrap(err, "str2StorageClass") + } + opts = append(opts, oss.ObjectStorageClass(storageClass)) + } + result, err := bucket.InitiateMultipartUpload(key, opts...) + if err != nil { + return "", errors.Wrap(err, "bucket.InitiateMultipartUpload") + } + return result.UploadID, nil +} + +func (b *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error) { + osscli, err := b.region.GetOssClient() + if err != nil { + return "", errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return "", errors.Wrap(err, "Bucket") + } + imur := oss.InitiateMultipartUploadResult{ + Bucket: b.Name, + Key: key, + UploadID: uploadId, + } + part, err := bucket.UploadPart(imur, input, partSize, partIndex) + if err != nil { + return "", errors.Wrap(err, "bucket.UploadPart") + } + if b.region.client.debug { + log.Debugf("upload part key:%s uploadId:%s partIndex:%d etag:%s", key, uploadId, partIndex, part.ETag) + } + return part.ETag, nil +} + +func (b *SBucket) CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error { + osscli, err := b.region.GetOssClient() + if err != nil { + return errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return errors.Wrap(err, "Bucket") + } + imur := oss.InitiateMultipartUploadResult{ + Bucket: b.Name, + Key: key, + UploadID: uploadId, + } + parts := make([]oss.UploadPart, len(partEtags)) + for i := range partEtags { + parts[i] = oss.UploadPart{ + PartNumber: i + 1, + ETag: partEtags[i], + } + } + result, err := bucket.CompleteMultipartUpload(imur, parts) + if err != nil { + return errors.Wrap(err, "bucket.CompleteMultipartUpload") + } + if b.region.client.debug { + log.Debugf("CompleteMultipartUpload bucket:%s key:%s etag:%s location:%s", result.Bucket, result.Key, result.ETag, result.Location) + } + return nil +} + +func (b *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error { + osscli, err := b.region.GetOssClient() + if err != nil { + return errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return errors.Wrap(err, "Bucket") + } + imur := oss.InitiateMultipartUploadResult{ + Bucket: b.Name, + Key: key, + UploadID: uploadId, + } + err = bucket.AbortMultipartUpload(imur) + if err != nil { + return errors.Wrap(err, "AbortMultipartUpload") + } + return nil +} + +func (b *SBucket) DeleteObject(ctx context.Context, key string) error { + osscli, err := b.region.GetOssClient() + if err != nil { + return errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return errors.Wrap(err, "Bucket") + } + err = bucket.DeleteObject(key) + if err != nil { + return errors.Wrap(err, "DeleteObject") + } + return nil +} + +func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) { + if method != "GET" && method != "PUT" && method != "DELETE" { + return "", errors.Error("unsupported method") + } + osscli, err := b.region.GetOssClient() + if err != nil { + return "", errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return "", errors.Wrap(err, "Bucket") + } + urlStr, err := bucket.SignURL(key, oss.HTTPMethod(method), int64(expire/time.Second)) + if err != nil { + return "", errors.Wrap(err, "SignURL") + } + return urlStr, nil +} + +func (b *SBucket) CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { + osscli, err := b.region.GetOssClient() + if err != nil { + return errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return errors.Wrap(err, "Bucket") + } + opts := make([]oss.Option, 0) + if meta != nil { + opts = metaOpts(opts, meta) + } + if len(cannedAcl) == 0 { + cannedAcl = b.GetAcl() + } + acl, err := str2Acl(string(cannedAcl)) + if err != nil { + return errors.Wrap(err, "str2Acl") + } + opts = append(opts, oss.ObjectACL(acl)) + if len(storageClassStr) > 0 { + storageClass, err := str2StorageClass(storageClassStr) + if err != nil { + return errors.Wrap(err, "str2StorageClass") + } + opts = append(opts, oss.ObjectStorageClass(storageClass)) + } + _, err = bucket.CopyObjectFrom(srcBucket, srcKey, destKey, opts...) + if err != nil { + return errors.Wrap(err, "CopyObjectFrom") + } + return nil +} + +func (b *SBucket) GetObject(ctx context.Context, key string, rangeOpt *cloudprovider.SGetObjectRange) (io.ReadCloser, error) { + osscli, err := b.region.GetOssClient() + if err != nil { + return nil, errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return nil, errors.Wrap(err, "Bucket") + } + opts := make([]oss.Option, 0) + if rangeOpt != nil { + opts = append(opts, oss.NormalizedRange(rangeOpt.String())) + } + output, err := bucket.GetObject(key, opts...) + if err != nil { + return nil, errors.Wrap(err, "bucket.GetObject") + } + return output, nil +} + +func (b *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partNumber int, srcBucket string, srcKey string, srcOffset int64, srcLength int64) (string, error) { + osscli, err := b.region.GetOssClient() + if err != nil { + return "", errors.Wrap(err, "GetOssClient") + } + bucket, err := osscli.Bucket(b.Name) + if err != nil { + return "", errors.Wrap(err, "Bucket") + } + imur := oss.InitiateMultipartUploadResult{ + Bucket: b.Name, + Key: key, + UploadID: uploadId, + } + opts := make([]oss.Option, 0) + part, err := bucket.UploadPartCopy(imur, srcBucket, srcKey, srcOffset, srcLength, partNumber, opts...) + if err != nil { + return "", errors.Wrap(err, "bucket.UploadPartCopy") + } + return part.ETag, nil +} diff --git a/pkg/multicloud/apsara/charge.go b/pkg/multicloud/apsara/charge.go new file mode 100644 index 0000000000..cb148ab82c --- /dev/null +++ b/pkg/multicloud/apsara/charge.go @@ -0,0 +1,42 @@ +// 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 apsara + +import ( + "time" + + api "yunion.io/x/onecloud/pkg/apis/billing" +) + +func convertChargeType(ct TChargeType) string { + switch ct { + case PrePaidInstanceChargeType: + return api.BILLING_TYPE_PREPAID + case PostPaidInstanceChargeType, PostPaidDBInstanceChargeType: + return api.BILLING_TYPE_POSTPAID + default: + return "" + } +} + +func convertExpiredAt(expired time.Time) time.Time { + if !expired.IsZero() { + now := time.Now() + if expired.Sub(now) < time.Hour*24*365*6 { + return expired + } + } + return time.Time{} +} diff --git a/pkg/multicloud/apsara/dbinstance.go b/pkg/multicloud/apsara/dbinstance.go new file mode 100644 index 0000000000..9c15170631 --- /dev/null +++ b/pkg/multicloud/apsara/dbinstance.go @@ -0,0 +1,797 @@ +// 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 apsara + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + 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" + "yunion.io/x/onecloud/pkg/util/rand" +) + +type SReadOnlyDBInstanceIds struct { + ReadOnlyDBInstanceId []string +} + +type SDBInstanceId struct { + DBInstanceId []string +} + +type SDBInstanceExtra struct { + DBInstanceId SDBInstanceId +} + +type SDBInstance struct { + multicloud.SDBInstanceBase + + netInfo []SDBInstanceNetwork + + region *SRegion + + AccountMaxQuantity int + AccountType string + CanTempUpgrade bool + Category string + AvailabilityValue string + DBInstanceDescription string + DBInstanceId string + ConnectionMode string + ConnectionString string + CurrentKernelVersion string + DBInstanceCPU int + CreateTime time.Time + DBInstanceClass string + DBInstanceClassType string + DBInstanceNetType string + DBInstanceStatus string + DBInstanceType string + DBInstanceDiskUsed int64 + DBInstanceStorage int + DBInstanceStorageType string + MasterInstanceId string + DBInstanceMemory int + DBMaxQuantity int + IPType string + LatestKernelVersion string + DispenseMode string + Engine string + EngineVersion string + ExpireTime time.Time + InstanceNetworkType string + LockMode string + LockReason string + MutriORsignle bool + MaintainTime string + MaxConnections int + MaxIOPS int + Port int + PayType TChargeType + ReadOnlyDBInstanceIds SReadOnlyDBInstanceIds + RegionId string + ResourceGroupId string + VSwitchId string + VpcCloudInstanceId string + VpcId string + ZoneId string + Extra SDBInstanceExtra + SecurityIPList string + SecurityIPMode string + SupportCreateSuperAccount string + SupportUpgradeAccountType string + TempUpgradeTimeEnd time.Time + TempUpgradeTimeStart time.Time +} + +func (rds *SDBInstance) GetName() string { + if len(rds.DBInstanceDescription) > 0 { + return rds.DBInstanceDescription + } + return rds.DBInstanceId +} + +func (rds *SDBInstance) GetId() string { + return rds.DBInstanceId +} + +func (rds *SDBInstance) GetGlobalId() string { + return rds.GetId() +} + +// Creating 创建中 +// Running 使用中 +// Deleting 删除中 +// Rebooting 重启中 +// DBInstanceClassChanging 升降级中 +// TRANSING 迁移中 +// EngineVersionUpgrading 迁移版本中 +// TransingToOthers 迁移数据到其他RDS中 +// GuardDBInstanceCreating 生产灾备实例中 +// Restoring 备份恢复中 +// Importing 数据导入中 +// ImportingFromOthers 从其他RDS实例导入数据中 +// DBInstanceNetTypeChanging 内外网切换中 +// GuardSwitching 容灾切换中 +// INS_CLONING 实例克隆中 +func (rds *SDBInstance) GetStatus() string { + switch rds.DBInstanceStatus { + case "Creating", "GuardDBInstanceCreating", "DBInstanceNetTypeChanging", "GuardSwitching", "NET_CREATING", "NET_DELETING": + return api.DBINSTANCE_DEPLOYING + case "DBInstanceClassChanging": + return api.DBINSTANCE_CHANGE_CONFIG + case "Running": + return api.DBINSTANCE_RUNNING + case "Deleting": + return api.DBINSTANCE_DELETING + case "Rebooting": + return api.DBINSTANCE_REBOOTING + case "TRANSING", "EngineVersionUpgrading", "TransingToOthers": + return api.DBINSTANCE_MIGRATING + case "Restoring": + return api.DBINSTANCE_RESTORING + case "Importing", "ImportingFromOthers": + return api.DBINSTANCE_IMPORTING + case "INS_CLONING": + return api.DBINSTANCE_CLONING + default: + log.Errorf("Unknown dbinstance status %s", rds.DBInstanceStatus) + return api.DBINSTANCE_UNKNOWN + } +} + +func (rds *SDBInstance) GetBillingType() string { + return convertChargeType(rds.PayType) +} + +func (rds *SDBInstance) GetExpiredAt() time.Time { + return rds.ExpireTime +} + +func (rds *SDBInstance) GetCreatedAt() time.Time { + return rds.CreateTime +} + +func (rds *SDBInstance) GetStorageType() string { + return rds.DBInstanceStorageType +} + +func (rds *SDBInstance) GetEngine() string { + switch rds.Engine { + case "MySQL": + return api.DBINSTANCE_TYPE_MYSQL + case "SQLServer": + return api.DBINSTANCE_TYPE_SQLSERVER + case "PostgreSQL": + return api.DBINSTANCE_TYPE_POSTGRESQL + case "PPAS": + return api.DBINSTANCE_TYPE_PPAS + case "MariaDB": + return api.DBINSTANCE_TYPE_MARIADB + } + return rds.Engine +} + +func (rds *SDBInstance) GetEngineVersion() string { + return rds.EngineVersion +} + +func (rds *SDBInstance) GetInstanceType() string { + return rds.DBInstanceClass +} + +func (rds *SDBInstance) GetCategory() string { + switch rds.Category { + case "Basic": + return api.ALIYUN_DBINSTANCE_CATEGORY_BASIC + case "HighAvailability": + return api.ALIYUN_DBINSTANCE_CATEGORY_HA + case "AlwaysOn": + return api.ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON + case "Finance": + return api.ALIYUN_DBINSTANCE_CATEGORY_FINANCE + } + return rds.Category +} + +func (rds *SDBInstance) GetVcpuCount() int { + if rds.DBInstanceCPU == 0 { + rds.Refresh() + } + return rds.DBInstanceCPU +} + +func (rds *SDBInstance) GetVmemSizeMB() int { + if rds.DBInstanceMemory == 0 { + rds.Refresh() + } + return rds.DBInstanceMemory +} + +func (rds *SDBInstance) GetDiskSizeGB() int { + if rds.DBInstanceStorage == 0 { + rds.Refresh() + } + return rds.DBInstanceStorage +} + +func (rds *SDBInstance) GetPort() int { + if rds.Port == 0 { + rds.Refresh() + } + return rds.Port +} + +func (rds *SDBInstance) GetMaintainTime() string { + return rds.MaintainTime +} + +func (rds *SDBInstance) GetIVpcId() string { + return rds.VpcId +} + +func (rds *SDBInstance) Refresh() error { + instance, err := rds.region.GetDBInstanceDetail(rds.DBInstanceId) + if err != nil { + return err + } + return jsonutils.Update(rds, instance) +} + +func (rds *SDBInstance) getZoneId(index int) string { + zoneId := rds.getZone(index) + if len(zoneId) > 0 { + zone, err := rds.region.getZoneById(zoneId) + if err != nil { + log.Errorf("failed to found zone %s for rds %s", zoneId, rds.GetName()) + return "" + } + return zone.GetGlobalId() + } + return "" +} + +func (rds *SDBInstance) GetZone1Id() string { + return rds.getZoneId(1) +} + +func (rds *SDBInstance) GetZone2Id() string { + return rds.getZoneId(2) +} + +func (rds *SDBInstance) GetZone3Id() string { + return rds.getZoneId(3) +} + +func (rds *SDBInstance) getZone(index int) string { + zoneStr := strings.Replace(rds.ZoneId, ")", "", -1) + zoneInfo := strings.Split(zoneStr, ",") + if len(zoneInfo) < index { + return "" + } + zone := zoneInfo[index-1] + zoneCode := zone[len(zone)-1] + if strings.HasPrefix(rds.ZoneId, fmt.Sprintf("%s-", rds.RegionId)) { + return fmt.Sprintf("%s-%s", rds.RegionId, string(zoneCode)) + } + return fmt.Sprintf("%s%s", rds.RegionId, string(zoneCode)) +} + +func (rds *SDBInstance) GetDBNetworks() ([]cloudprovider.SDBInstanceNetwork, error) { + netInfo, err := rds.region.GetDBInstanceNetInfo(rds.DBInstanceId) + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstanceNetInfo") + } + networks := []cloudprovider.SDBInstanceNetwork{} + for _, net := range netInfo { + if net.IPType == "Private" { + network := cloudprovider.SDBInstanceNetwork{} + network.IP = net.IPAddress + network.NetworkId = net.VSwitchId + networks = append(networks, network) + } + } + return []cloudprovider.SDBInstanceNetwork{}, nil +} + +func (rds *SDBInstance) fetchNetInfo() error { + if len(rds.netInfo) > 0 { + return nil + } + netInfo, err := rds.region.GetDBInstanceNetInfo(rds.DBInstanceId) + if err != nil { + return errors.Wrap(err, "GetDBInstanceNetInfo") + } + rds.netInfo = netInfo + return nil +} + +func (rds *SDBInstance) GetInternalConnectionStr() string { + err := rds.fetchNetInfo() + if err != nil { + log.Errorf("failed to fetch netInfo error: %v", err) + return "" + } + + for _, net := range rds.netInfo { + if net.IPType != "Public" { + return net.ConnectionString + } + } + return "" +} + +func (rds *SDBInstance) GetConnectionStr() string { + err := rds.fetchNetInfo() + if err != nil { + log.Errorf("failed to fetch netInfo error: %v", err) + return "" + } + + for _, net := range rds.netInfo { + if net.IPType == "Public" { + return net.ConnectionString + } + } + return "" +} + +func (region *SRegion) GetDBInstances(ids []string, offset int, limit int) ([]SDBInstance, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + body, err := region.rdsRequest("DescribeDBInstances", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "GetDBInstances") + } + instances := []SDBInstance{} + err = body.Unmarshal(&instances, "Items", "DBInstance") + if err != nil { + return nil, 0, errors.Wrapf(err, "GetDBInstances.Unmarshal") + } + total, _ := body.Int("TotalRecordCount") + return instances, int(total), nil +} + +func (region *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { + rds, err := region.GetDBInstanceDetail(instanceId) + if err != nil { + return nil, err + } + rds.region = region + return rds, nil +} + +func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { + instances := []SDBInstance{} + for { + part, total, err := region.GetDBInstances([]string{}, len(instances), 50) + if err != nil { + return nil, err + } + instances = append(instances, part...) + if len(instances) >= total { + break + } + } + idbinstances := []cloudprovider.ICloudDBInstance{} + for i := 0; i < len(instances); i++ { + instances[i].region = region + idbinstances = append(idbinstances, &instances[i]) + } + return idbinstances, nil +} + +func (region *SRegion) GetDBInstanceDetail(instanceId string) (*SDBInstance, error) { + if len(instanceId) == 0 { + return nil, cloudprovider.ErrNotFound + } + params := map[string]string{} + params["RegionId"] = region.RegionId + params["DBInstanceId"] = instanceId + body, err := region.rdsRequest("DescribeDBInstanceAttribute", params) + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstanceDetail") + } + instances := []SDBInstance{} + err = body.Unmarshal(&instances, "Items", "DBInstanceAttribute") + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstanceDetail.Unmarshal") + } + if len(instances) == 1 { + instances[0].region = region + return &instances[0], nil + } + if len(instances) == 0 { + return nil, cloudprovider.ErrNotFound + } + return nil, cloudprovider.ErrDuplicateId +} + +func (region *SRegion) DeleteDBInstance(instanceId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["DBInstanceId"] = instanceId + _, err := region.rdsRequest("DeleteDBInstance", params) + return err +} + +type SDBInstanceWeight struct { +} + +type SDBInstanceWeights struct { + DBInstanceWeight []SDBInstanceWeight +} + +type SsecurityIPGroup struct { +} + +type SSecurityIPGroups struct { + securityIPGroup []SsecurityIPGroup +} + +type SDBInstanceNetwork struct { + ConnectionString string + ConnectionStringType string + DBInstanceWeights SDBInstanceWeights + IPAddress string + IPType string + Port int + SecurityIPGroups SSecurityIPGroups + Upgradeable string + VPCId string + VSwitchId string +} + +func (network *SDBInstanceNetwork) GetGlobalId() string { + return network.IPAddress +} + +func (network *SDBInstanceNetwork) GetINetworkId() string { + return network.VSwitchId +} + +func (network *SDBInstanceNetwork) GetIP() string { + return network.IPAddress +} + +func (region *SRegion) GetDBInstanceNetInfo(instanceId string) ([]SDBInstanceNetwork, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["DBInstanceId"] = instanceId + body, err := region.rdsRequest("DescribeDBInstanceNetInfo", params) + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstanceNetwork") + } + networks := []SDBInstanceNetwork{} + err = body.Unmarshal(&networks, "DBInstanceNetInfos", "DBInstanceNetInfo") + if err != nil { + return nil, err + } + return networks, nil +} + +func (rds *SDBInstance) GetIDBInstanceParameters() ([]cloudprovider.ICloudDBInstanceParameter, error) { + parameters, err := rds.region.GetDBInstanceParameters(rds.DBInstanceId) + if err != nil { + return nil, err + } + iparameters := []cloudprovider.ICloudDBInstanceParameter{} + for i := 0; i < len(parameters); i++ { + iparameters = append(iparameters, ¶meters[i]) + } + return iparameters, nil +} + +func (region *SRegion) GetIDBInstanceBackupById(backupId string) (cloudprovider.ICloudDBInstanceBackup, error) { + backups, err := region.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrap(err, "region.GetIDBInstanceBackups") + } + for _, backup := range backups { + if backup.GetGlobalId() == backupId { + return backup, nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (rds *SDBInstance) Reboot() error { + return rds.region.RebootDBInstance(rds.DBInstanceId) +} + +func (rds *SDBInstance) Delete() error { + return rds.region.DeleteDBInstance(rds.DBInstanceId) +} + +func (region *SRegion) RebootDBInstance(instanceId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["DBInstanceId"] = instanceId + _, err := region.rdsRequest("RestartDBInstance", params) + return err +} + +func (rds *SDBInstance) GetIDBInstanceDatabases() ([]cloudprovider.ICloudDBInstanceDatabase, error) { + databases := []SDBInstanceDatabase{} + for { + parts, total, err := rds.region.GetDBInstanceDatabases(rds.DBInstanceId, "", len(databases), 500) + if err != nil { + return nil, err + } + databases = append(databases, parts...) + if len(databases) >= total { + break + } + } + + idatabase := []cloudprovider.ICloudDBInstanceDatabase{} + for i := 0; i < len(databases); i++ { + databases[i].instance = rds + idatabase = append(idatabase, &databases[i]) + } + return idatabase, nil +} + +func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstanceAccount, error) { + accounts := []SDBInstanceAccount{} + for { + parts, total, err := rds.region.GetDBInstanceAccounts(rds.DBInstanceId, len(accounts), 50) + if err != nil { + return nil, err + } + accounts = append(accounts, parts...) + if len(accounts) >= total { + break + } + } + + iaccounts := []cloudprovider.ICloudDBInstanceAccount{} + for i := 0; i < len(accounts); i++ { + accounts[i].instance = rds + iaccounts = append(iaccounts, &accounts[i]) + } + return iaccounts, nil +} + +func (rds *SDBInstance) ChangeConfig(cxt context.Context, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { + return rds.region.ChangeDBInstanceConfig(rds.DBInstanceId, string(rds.PayType), desc) +} + +func (region *SRegion) ChangeDBInstanceConfig(instanceId, payType string, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "PayType": payType, + "DBInstanceClass": desc.InstanceType, + "DBInstanceStorage": fmt.Sprintf("%d", desc.DiskSizeGB), + } + + _, err := region.rdsRequest("ModifyDBInstanceSpec", params) + if err != nil { + return errors.Wrap(err, "region.rdsRequest.ModifyDBInstanceSpec") + } + return nil +} + +func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + params := map[string]string{ + "RegionId": region.RegionId, + "Engine": desc.Engine, + "EngineVersion": desc.EngineVersion, + "DBInstanceStorage": fmt.Sprintf("%d", desc.DiskSizeGB), + "DBInstanceNetType": "Intranet", + "PayType": "Postpaid", + "SecurityIPList": "0.0.0.0/0", + "DBInstanceDescription": desc.Name, + "InstanceNetworkType": "VPC", + "VPCId": desc.VpcId, + "VSwitchId": desc.NetworkId, + "DBInstanceStorageType": desc.StorageType, + "DBInstanceClass": desc.InstanceType, + "ZoneId": desc.ZoneId, + "ClientToken": utils.GenRequestId(20), + } + switch desc.Category { + case api.ALIYUN_DBINSTANCE_CATEGORY_HA: + params["Category"] = "HighAvailability" + case api.ALIYUN_DBINSTANCE_CATEGORY_BASIC: + params["Category"] = "Basic" + case api.ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON: + params["Category"] = "AlwaysOn" + case api.ALIYUN_DBINSTANCE_CATEGORY_FINANCE: + params["Category"] = "Finance" + } + + if len(desc.Address) > 0 { + params["PrivateIpAddress"] = desc.Address + } + if len(desc.ProjectId) > 0 { + params["ResourceGroupId"] = desc.ProjectId + } + if desc.BillingCycle != nil { + params["PayType"] = "Prepaid" + if desc.BillingCycle.GetMonths() > 0 { + params["Period"] = "Month" + params["UsedTime"] = fmt.Sprintf("%d", desc.BillingCycle.GetMonths()) + } else { + params["Period"] = "Year" + params["UsedTime"] = fmt.Sprintf("%d", desc.BillingCycle.GetYears()) + } + params["AutoRenew"] = "False" + } + + action := "CreateDBInstance" + if len(desc.MasterInstanceId) > 0 { + action = "CreateReadOnlyDBInstance" + params["DBInstanceId"] = desc.MasterInstanceId + } + + resp, err := region.rdsRequest(action, params) + if err != nil { + return nil, errors.Wrapf(err, "rdsRequest") + } + instanceId, err := resp.GetString("DBInstanceId") + if err != nil { + return nil, errors.Wrap(err, `resp.GetString("DBInstanceId")`) + } + region.SetResourceTags("rds", "INSTANCE", []string{instanceId}, desc.Tags, false) + return region.GetIDBInstanceById(instanceId) +} + +func (rds *SDBInstance) GetMasterInstanceId() string { + if len(rds.MasterInstanceId) > 0 { + return rds.MasterInstanceId + } + rds.Refresh() + return rds.MasterInstanceId +} + +func (region *SRegion) OpenPublicConnection(instanceId string) error { + rds, err := region.GetDBInstanceDetail(instanceId) + if err != nil { + return err + } + params := map[string]string{ + "RegionId": region.RegionId, + "ConnectionStringPrefix": rds.DBInstanceId + rand.String(3), + "DBInstanceId": rds.DBInstanceId, + "Port": fmt.Sprintf("%d", rds.Port), + } + _, err = rds.region.rdsRequest("AllocateInstancePublicConnection", params) + if err != nil { + return errors.Wrap(err, "rdsRequest(AllocateInstancePublicConnection)") + } + return nil +} + +func (rds *SDBInstance) OpenPublicConnection() error { + if url := rds.GetConnectionStr(); len(url) == 0 { + err := rds.region.OpenPublicConnection(rds.DBInstanceId) + if err != nil { + return err + } + rds.netInfo = []SDBInstanceNetwork{} + } + return nil +} + +func (region *SRegion) ClosePublicConnection(instanceId string) error { + netInfo, err := region.GetDBInstanceNetInfo(instanceId) + if err != nil { + return errors.Wrap(err, "GetDBInstanceNetInfo") + } + + for _, net := range netInfo { + if net.IPType == "Public" { + params := map[string]string{ + "RegionId": region.RegionId, + "CurrentConnectionString": net.ConnectionString, + "DBInstanceId": instanceId, + } + _, err = region.rdsRequest("ReleaseInstancePublicConnection", params) + if err != nil { + return errors.Wrap(err, "rdsRequest(ReleaseInstancePublicConnection)") + } + + } + } + return nil + +} + +func (rds *SDBInstance) ClosePublicConnection() error { + return rds.region.ClosePublicConnection(rds.DBInstanceId) +} + +func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { + if len(conf.OriginDBInstanceExternalId) == 0 { + conf.OriginDBInstanceExternalId = rds.DBInstanceId + } + return rds.region.RecoveryDBInstanceFromBackup(conf.OriginDBInstanceExternalId, rds.DBInstanceId, conf.BackupId, conf.Databases) +} + +func (region *SRegion) RecoveryDBInstanceFromBackup(srcId, destId string, backupId string, databases map[string]string) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": srcId, + "TargetDBInstanceId": destId, + "BackupId": backupId, + "DbNames": jsonutils.Marshal(databases).String(), + } + _, err := region.rdsRequest("RecoveryDBInstance", params) + if err != nil { + return errors.Wrap(err, "rdsRequest.RecoveryDBInstance") + } + return nil +} + +func (rds *SDBInstance) GetProjectId() string { + return rds.ResourceGroupId +} + +func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return rds.region.CreateDBInstanceDatabae(rds.DBInstanceId, conf.CharacterSet, conf.Name, conf.Description) +} + +func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { + return rds.region.CreateDBInstanceAccount(rds.DBInstanceId, conf.Name, conf.Password, conf.Description) +} + +func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { + return rds.region.RenewInstance(rds.DBInstanceId, bc) +} + +func (region *SRegion) RenewDBInstance(instanceId string, bc billing.SBillingCycle) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "Period": fmt.Sprintf("%d", bc.GetMonths()), + "ClientToken": utils.GenRequestId(20), + } + _, err := region.rdsRequest("RenewInstance", params) + return err +} + +func (rds *SDBInstance) GetMetadata() *jsonutils.JSONDict { + data := jsonutils.NewDict() + tags, err := rds.region.ListResourceTags("rds", "INSTANCE", []string{rds.GetId()}) + if err != nil { + log.Errorf(`[err:%s]rds.region.FetchResourceTags("slb", "instance", []string{rds.GetId()})`, err.Error()) + return nil + } + if _, ok := tags[rds.GetId()]; !ok { + return nil + } + data.Update(jsonutils.Marshal(tags[rds.GetId()])) + return data +} + +func (rds *SDBInstance) SetMetadata(tags map[string]string, replace bool) error { + return rds.region.SetResourceTags("rds", "INSTANCE", []string{rds.GetId()}, tags, replace) +} diff --git a/pkg/multicloud/apsara/dbinstance_account.go b/pkg/multicloud/apsara/dbinstance_account.go new file mode 100644 index 0000000000..3ce54134f6 --- /dev/null +++ b/pkg/multicloud/apsara/dbinstance_account.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 apsara + +import ( + "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" +) + +type SDatabasePrivileges struct { + DatabasePrivilege []SDatabasePrivilege +} + +type SDBInstanceAccount struct { + multicloud.SDBInstanceAccountBase + instance *SDBInstance + + AccountDescription string + AccountName string + AccountStatus string + AccountType string + DBInstanceId string + DatabasePrivileges SDatabasePrivileges + PrivExceeded string +} + +func (account *SDBInstanceAccount) GetName() string { + return account.AccountName +} + +func (account *SDBInstanceAccount) Delete() error { + return account.instance.region.DeleteDBInstanceAccount(account.DBInstanceId, account.AccountName) +} + +func (account *SDBInstanceAccount) RevokePrivilege(database string) error { + return account.instance.region.RevokeDBInstancePrivilege(account.DBInstanceId, account.AccountName, database) +} + +func (region *SRegion) RevokeDBInstancePrivilege(instanceId, account, database string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "AccountName": account, + "DBName": database, + } + _, err := region.rdsRequest("RevokeAccountPrivilege", params) + return err +} + +func (account *SDBInstanceAccount) GrantPrivilege(database, privilege string) error { + return account.instance.region.GrantDBInstancePrivilege(account.DBInstanceId, account.AccountName, database, privilege) +} + +func (region *SRegion) GrantDBInstancePrivilege(instanceId, account, database, privilege string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "AccountName": account, + "DBName": database, + } + switch privilege { + case api.DATABASE_PRIVILEGE_R: + params["AccountPrivilege"] = "ReadOnly" + case api.DATABASE_PRIVILEGE_RW: + params["AccountPrivilege"] = "ReadWrite" + case api.DATABASE_PRIVILEGE_DDL: + params["AccountPrivilege"] = "DDLOnly" + case api.DATABASE_PRIVILEGE_DML: + params["AccountPrivilege"] = "DMLOnly" + case api.DATABASE_PRIVILEGE_OWNER: + params["AccountPrivilege"] = "DBOwner" + default: + return fmt.Errorf("Unknown privilege [%s]", privilege) + } + _, err := region.rdsRequest("GrantAccountPrivilege", params) + return err +} + +func (account *SDBInstanceAccount) ResetPassword(password string) error { + return account.instance.region.ResetDBInstanceAccountPassword(account.DBInstanceId, account.AccountName, password, account.AccountType) +} + +func (region *SRegion) ResetDBInstanceAccountPassword(instanceId, account, password, accountType string) error { + action := "ResetAccountPassword" + if accountType == "Super" { + action = "ResetAccount" + } + params := map[string]string{ + "DBInstanceId": instanceId, + "AccountName": account, + "AccountPassword": password, + } + _, err := region.rdsRequest(action, params) + return err +} + +func (account *SDBInstanceAccount) GetStatus() string { + switch account.AccountStatus { + case "Available": + return api.DBINSTANCE_USER_AVAILABLE + case "Unavailable": + return api.DBINSTANCE_USER_UNAVAILABLE + } + return account.AccountStatus +} + +func (account *SDBInstanceAccount) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { + privileves := []cloudprovider.ICloudDBInstanceAccountPrivilege{} + for i := 0; i < len(account.DatabasePrivileges.DatabasePrivilege); i++ { + account.DatabasePrivileges.DatabasePrivilege[i].account = account + privileves = append(privileves, &account.DatabasePrivileges.DatabasePrivilege[i]) + } + return privileves, nil +} + +func (region *SRegion) GetDBInstanceAccounts(instanceId string, offset int, limit int) ([]SDBInstanceAccount, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := map[string]string{ + "RegionId": region.RegionId, + "PageSize": fmt.Sprintf("%d", limit), + "PageNumber": fmt.Sprintf("%d", (offset/limit)+1), + "DBInstanceId": instanceId, + } + body, err := region.rdsRequest("DescribeAccounts", params) + if err != nil { + return nil, 0, errors.Wrap(err, "DescribeAccounts") + } + accounts := []SDBInstanceAccount{} + err = body.Unmarshal(&accounts, "Accounts", "DBInstanceAccount") + if err != nil { + return nil, 0, errors.Wrap(err, "Unmarshal") + } + total, _ := body.Int("TotalRecordCount") + return accounts, int(total), nil +} + +func (region *SRegion) DeleteDBInstanceAccount(instanceId string, accountName string) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "AccountName": accountName, + } + + _, err := region.rdsRequest("DeleteAccount", params) + return err +} + +func (region *SRegion) CreateDBInstanceAccount(instanceId string, name string, password string, desc string) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "AccountName": name, + "AccountPassword": password, + "AccountDescription": desc, + } + + _, err := region.rdsRequest("CreateAccount", params) + return err + +} diff --git a/pkg/multicloud/apsara/dbinstance_backup.go b/pkg/multicloud/apsara/dbinstance_backup.go new file mode 100644 index 0000000000..eb90959b02 --- /dev/null +++ b/pkg/multicloud/apsara/dbinstance_backup.go @@ -0,0 +1,303 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "github.com/coredns/coredns/plugin/pkg/log" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SDBInstanceBackup struct { + multicloud.SDBInstanceBackupBase + region *SRegion + + BackupDBNames string + BackupIntranetDownloadURL string + BackupDownloadURL string + BackupEndTime time.Time + BackupId string + BackupLocation string + BackupMethod string + BackupMode string + BackupScale string + BackupSize int + BackupStartTime time.Time + BackupStatus string + BackupType string + DBInstanceId string + HostInstanceID int + MetaStatus string + StoreStatus string +} + +func (backup *SDBInstanceBackup) GetId() string { + return backup.BackupId +} + +func (backup *SDBInstanceBackup) GetGlobalId() string { + return backup.BackupId +} + +func (backup *SDBInstanceBackup) GetName() string { + return backup.BackupId +} + +func (backup *SDBInstanceBackup) GetStartTime() time.Time { + return backup.BackupStartTime +} + +func (backup *SDBInstanceBackup) GetEndTime() time.Time { + return backup.BackupEndTime +} + +func (backup *SDBInstanceBackup) GetBackupMode() string { + switch backup.BackupMode { + case "Manual": + return api.BACKUP_MODE_MANUAL + default: + return api.BACKUP_MODE_AUTOMATED + } +} + +func (backup *SDBInstanceBackup) GetStatus() string { + switch backup.BackupStatus { + case "Success": + return api.DBINSTANCE_BACKUP_READY + case "Failed": + return api.DBINSTANCE_BACKUP_FAILED + default: + return api.DBINSTANCE_BACKUP_UNKNOWN + } +} + +func (backup *SDBInstanceBackup) GetBackupSizeMb() int { + return backup.BackupSize / 1024 / 1024 +} + +func (backup *SDBInstanceBackup) GetDBNames() string { + return backup.BackupDBNames +} + +func (backup *SDBInstanceBackup) GetEngine() string { + instance, _ := backup.region.GetDBInstanceDetail(backup.DBInstanceId) + if instance != nil { + return instance.Engine + } + return "" +} + +func (backup *SDBInstanceBackup) GetEngineVersion() string { + instance, _ := backup.region.GetDBInstanceDetail(backup.DBInstanceId) + if instance != nil { + return instance.EngineVersion + } + return "" +} + +func (backup *SDBInstanceBackup) GetDBInstanceId() string { + return backup.DBInstanceId +} + +func (region *SRegion) GetDBInstanceBackups(instanceId, backupId string, offset int, limit int) ([]SDBInstanceBackup, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := map[string]string{ + "RegionId": region.RegionId, + "PageSize": fmt.Sprintf("%d", limit), + "PageNumber": fmt.Sprintf("%d", (offset/limit)+1), + "DBInstanceId": instanceId, + } + if len(backupId) > 0 { + params["BackupId"] = backupId + } + body, err := region.rdsRequest("DescribeBackups", params) + if err != nil { + return nil, 0, errors.Wrap(err, "DescribeBackups") + } + backups := []SDBInstanceBackup{} + err = body.Unmarshal(&backups, "Items", "Backup") + if err != nil { + return nil, 0, errors.Wrap(err, "Unmarshal") + } + total, _ := body.Int("TotalRecordCount") + return backups, int(total), nil +} + +func (region *SRegion) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + dbinstnaces, err := region.GetIDBInstances() + if err != nil { + return nil, err + } + ibackups := []cloudprovider.ICloudDBInstanceBackup{} + for i := 0; i < len(dbinstnaces); i++ { + _dbinstance := dbinstnaces[i].(*SDBInstance) + _ibackup, err := _dbinstance.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrapf(err, "_dbinstance(%v).GetIDBInstanceBackups", _dbinstance) + } + ibackups = append(ibackups, _ibackup...) + } + return ibackups, nil +} + +func (rds *SDBInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + backups := []SDBInstanceBackup{} + for { + parts, total, err := rds.region.GetDBInstanceBackups(rds.DBInstanceId, "", len(backups), 50) + if err != nil { + return nil, err + } + backups = append(backups, parts...) + if len(backups) >= total { + break + } + } + + ibackups := []cloudprovider.ICloudDBInstanceBackup{} + for i := 0; i < len(backups); i++ { + backups[i].region = rds.region + ibackups = append(ibackups, &backups[i]) + } + return ibackups, nil +} + +func (rds *SDBInstance) CreateIBackup(conf *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + params := map[string]string{ + "DBInstanceId": rds.DBInstanceId, + } + switch rds.Engine { + case api.DBINSTANCE_TYPE_MYSQL: + if utils.IsInStringArray(rds.EngineVersion, []string{"5.7", "8.0"}) && ((utils.IsInStringArray(rds.GetStorageType(), []string{ + api.ALIYUN_DBINSTANCE_STORAGE_TYPE_CLOUD_ESSD, + api.ALIYUN_DBINSTANCE_STORAGE_TYPE_CLOUD_SSD, + }) && rds.GetCategory() == api.ALIYUN_DBINSTANCE_CATEGORY_HA) || + (rds.GetStorageType() == api.ALIYUN_DBINSTANCE_STORAGE_TYPE_CLOUD_SSD && + rds.GetCategory() == api.ALIYUN_DBINSTANCE_CATEGORY_BASIC)) { + params["BackupMethod"] = "Snapshot" + } else { + params["BackupMethod"] = "Physical" + if len(conf.Databases) > 0 { + params["BackupStrategy"] = "db" + params["DBName"] = strings.Join(conf.Databases, ",") + params["BackupMethod"] = "Logical" + } + } + case api.DBINSTANCE_TYPE_MARIADB: + params["BackupMethod"] = "Snapshot" + case api.DBINSTANCE_TYPE_SQLSERVER: + params["BackupMethod"] = "Physical" + case api.DBINSTANCE_TYPE_POSTGRESQL: + if rds.GetStorageType() == api.ALIYUN_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD { + params["BackupMethod"] = "Physical" + } else { + params["BackupMethod"] = "Snapshot" + } + case api.DBINSTANCE_TYPE_PPAS: + params["BackupMethod"] = "Physical" + } + body, err := rds.region.rdsRequest("CreateBackup", params) + if err != nil { + return "", errors.Wrap(err, "CreateBackup") + } + jobId, err := body.GetString("BackupJobId") + if err != nil { + return "", errors.Wrap(err, "body.BackupJobId") + } + return "", rds.region.waitBackupCreateComplete(rds.DBInstanceId, jobId) +} + +func (backup *SDBInstanceBackup) Delete() error { + return backup.region.DeleteDBInstanceBackup(backup.DBInstanceId, backup.BackupId) +} + +func (region *SRegion) DeleteDBInstanceBackup(instanceId string, backupId string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "BackupId": backupId, + } + _, err := region.rdsRequest("DeleteBackup", params) + return err +} + +type SDBInstanceBackupJob struct { + BackupProgressStatus string + Process string + JobMode string + TaskAction string + BackupStatus string + BackupJobId string +} + +type SDBInstanceBackupJobs struct { + BackupJob []SDBInstanceBackupJob +} + +func (region *SRegion) GetDBInstanceBackupJobs(instanceId, jobId string) (*SDBInstanceBackupJobs, error) { + params := map[string]string{ + "DBInstanceId": instanceId, + "ClientToken": utils.GenRequestId(20), + "BackupMode": "Manual", + } + if len(jobId) > 0 { + params["BackupJobId"] = jobId + } + body, err := region.rdsRequest("DescribeBackupTasks", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeBackupTasks") + } + + jobs := SDBInstanceBackupJobs{} + + err = body.Unmarshal(&jobs, "Items") + if err != nil { + return nil, errors.Wrapf(err, "body.Unmarshal(%s)", body) + } + + return &jobs, nil +} + +func (region *SRegion) waitBackupCreateComplete(instanceId, jobId string) error { + for i := 0; i < 20*40; i++ { + jobs, err := region.GetDBInstanceBackupJobs(instanceId, jobId) + if err != nil { + return errors.Wrapf(err, "region.GetDBInstanceBackupJobs(%s, %s)", instanceId, jobId) + } + if len(jobs.BackupJob) == 0 { + return nil + } + for _, job := range jobs.BackupJob { + log.Infof("instance %s backup job %s status: %s(%s)", instanceId, jobId, job.BackupStatus, job.Process) + if job.BackupStatus == "Finished" && job.BackupJobId == jobId { + return nil + } + if job.BackupStatus == "Failed" && job.BackupJobId == jobId { + return fmt.Errorf("instance %s backup job %s failed", instanceId, jobId) + } + } + time.Sleep(time.Second * 3) + } + return fmt.Errorf("timeout for waiting create job complete") +} diff --git a/pkg/multicloud/apsara/dbinstance_database.go b/pkg/multicloud/apsara/dbinstance_database.go new file mode 100644 index 0000000000..c48b08e4f1 --- /dev/null +++ b/pkg/multicloud/apsara/dbinstance_database.go @@ -0,0 +1,117 @@ +// 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 apsara + +import ( + "fmt" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SDBInstanceDatabase struct { + multicloud.SDBInstanceDatabaseBase + instance *SDBInstance + + CharacterSetName string + DBDescription string + DBInstanceId string + DBName string + DBStatus string + Engine string +} + +func (database *SDBInstanceDatabase) GetId() string { + return database.DBName +} + +func (database *SDBInstanceDatabase) GetGlobalId() string { + return database.DBName +} + +func (database *SDBInstanceDatabase) GetName() string { + return database.DBName +} + +func (database *SDBInstanceDatabase) GetStatus() string { + switch database.DBStatus { + case "Creating": + return api.DBINSTANCE_DATABASE_CREATING + case "Running": + return api.DBINSTANCE_DATABASE_RUNNING + case "Deleting": + return api.DBINSTANCE_DATABASE_DELETING + } + return database.DBStatus +} + +func (database *SDBInstanceDatabase) GetCharacterSet() string { + return database.CharacterSetName +} + +func (database *SDBInstanceDatabase) Delete() error { + return database.instance.region.DeleteDBInstanceDatabase(database.DBInstanceId, database.DBName) +} + +func (region *SRegion) DeleteDBInstanceDatabase(instanceId string, dbName string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "DBName": dbName, + } + + _, err := region.rdsRequest("DeleteDatabase", params) + return err +} + +func (region *SRegion) CreateDBInstanceDatabae(instanceId, characterSet, dbName, desc string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "DBName": dbName, + "CharacterSetName": characterSet, + "DBDescription": desc, + } + + _, err := region.rdsRequest("CreateDatabase", params) + return err + +} + +func (region *SRegion) GetDBInstanceDatabases(instanceId, dbName string, offset int, limit int) ([]SDBInstanceDatabase, int, error) { + if limit > 500 || limit <= 0 { + limit = 500 + } + params := map[string]string{ + "RegionId": region.RegionId, + "PageSize": fmt.Sprintf("%d", limit), + "PageNumber": fmt.Sprintf("%d", (offset/limit)+1), + "DBInstanceId": instanceId, + } + if len(dbName) > 0 { + params["DBName"] = dbName + } + body, err := region.rdsRequest("DescribeDatabases", params) + if err != nil { + return nil, 0, errors.Wrap(err, "DescribeDatabases") + } + databases := []SDBInstanceDatabase{} + err = body.Unmarshal(&databases, "Databases", "Database") + if err != nil { + return nil, 0, errors.Wrap(err, "Unmarshal") + } + total, _ := body.Int("TotalRecordCount") + return databases, int(total), nil +} diff --git a/pkg/multicloud/apsara/dbinstance_parameter.go b/pkg/multicloud/apsara/dbinstance_parameter.go new file mode 100644 index 0000000000..f86f0ec77d --- /dev/null +++ b/pkg/multicloud/apsara/dbinstance_parameter.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 apsara + +import ( + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" +) + +type SDBInstanceParameter struct { + instance *SDBInstance + + ParameterDescription string + ParameterValue string + ParameterName string +} + +func (param *SDBInstanceParameter) GetGlobalId() string { + return param.ParameterName +} + +func (param *SDBInstanceParameter) GetKey() string { + return param.ParameterName +} + +func (param *SDBInstanceParameter) GetValue() string { + return param.ParameterValue +} + +func (param *SDBInstanceParameter) GetDescription() string { + return param.ParameterDescription +} + +func (region *SRegion) GetDBInstanceParameters(instanceId string) ([]SDBInstanceParameter, error) { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "ClientToken": utils.GenRequestId(20), + } + + body, err := region.rdsRequest("DescribeParameters", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeParameters") + } + parameters1 := []SDBInstanceParameter{} + err = body.Unmarshal(¶meters1, "ConfigParameters", "DBInstanceParameter") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal.ConfigParameters") + } + parameters2 := []SDBInstanceParameter{} + err = body.Unmarshal(¶meters1, "RunningParameters", "DBInstanceParameter") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal.RunningParameters") + } + return append(parameters1, parameters2...), nil +} diff --git a/pkg/multicloud/apsara/dbinstance_privilege.go b/pkg/multicloud/apsara/dbinstance_privilege.go new file mode 100644 index 0000000000..26c0150ee6 --- /dev/null +++ b/pkg/multicloud/apsara/dbinstance_privilege.go @@ -0,0 +1,55 @@ +// 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 apsara + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SDatabasePrivilege struct { + account *SDBInstanceAccount + + AccountPrivilege string + AccountPrivilegeDetail string + DBName string +} + +func (privilege *SDatabasePrivilege) GetGlobalId() string { + return fmt.Sprintf("%s/%s", privilege.account.GetName(), privilege.DBName) +} + +func (privilege *SDatabasePrivilege) GetPrivilege() string { + switch privilege.AccountPrivilege { + case "ReadWrite": + return api.DATABASE_PRIVILEGE_RW + case "ReadOnly": + return api.DATABASE_PRIVILEGE_R + case "DDLOnly": + return api.DATABASE_PRIVILEGE_DDL + case "DMLOnly": + return api.DATABASE_PRIVILEGE_DML + case "DBOwner": + return api.DATABASE_PRIVILEGE_OWNER + case "Custom": + return api.DBINSTANCE_DATABASE_CREATING + } + return privilege.AccountPrivilege +} + +func (privilege *SDatabasePrivilege) GetDBName() string { + return privilege.DBName +} diff --git a/pkg/multicloud/apsara/disk.go b/pkg/multicloud/apsara/disk.go new file mode 100644 index 0000000000..504b24a3b3 --- /dev/null +++ b/pkg/multicloud/apsara/disk.go @@ -0,0 +1,453 @@ +// 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 apsara + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SMountInstances struct { + MountInstance []string +} + +type STags struct { + Tag []string +} + +type SDisk struct { + storage *SStorage + multicloud.SDisk + + AttachedTime time.Time + AutoSnapshotPolicyId string + Category string + PerformanceLevel string + CreationTime time.Time + DeleteAutoSnapshot bool + DeleteWithInstance bool + Description string + DetachedTime time.Time + Device string + DiskChargeType TChargeType + DiskId string + DiskName string + EnableAutoSnapshot bool + EnableAutomatedSnapshotPolicy bool + Encrypted bool + ExpiredTime time.Time + ImageId string + InstanceId string + MountInstances SMountInstances + OperationLocks SOperationLocks + Portable bool + ProductCode string + RegionId string + ResourceGroupId string + Size int + SourceSnapshotId string + Status string + Tags STags + Type string + ZoneId string +} + +func (self *SDisk) GetMetadata() *jsonutils.JSONDict { + data := jsonutils.NewDict() + + // The pricingInfo key structure is 'RegionId::DiskCategory::DiskType + priceKey := fmt.Sprintf("%s::%s::%s", self.RegionId, self.Category, self.Type) + data.Add(jsonutils.NewString(priceKey), "price_key") + + data.Add(jsonutils.NewString(api.HYPERVISOR_APSARA), "hypervisor") + + return data +} + +func (self *SRegion) GetDisks(instanceId string, zoneId string, category string, diskIds []string, offset int, limit int) ([]SDisk, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(instanceId) > 0 { + params["InstanceId"] = instanceId + } + if len(zoneId) > 0 { + params["ZoneId"] = zoneId + } + if len(category) > 0 { + params["Category"] = category + } + if diskIds != nil && len(diskIds) > 0 { + params["DiskIds"] = jsonutils.Marshal(diskIds).String() + } + + body, err := self.ecsRequest("DescribeDisks", params) + if err != nil { + log.Errorf("GetDisks fail %s", err) + return nil, 0, err + } + + disks := make([]SDisk, 0) + err = body.Unmarshal(&disks, "Disks", "Disk") + if err != nil { + log.Errorf("Unmarshal disk details fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return disks, int(total), nil +} + +func (self *SDisk) GetId() string { + return self.DiskId +} + +func (self *SDisk) Delete(ctx context.Context) error { + _, err := self.storage.zone.region.getDisk(self.DiskId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + // 未找到disk, 说明disk已经被删除了. 避免回收站中disk-delete循环删除失败 + return nil + } + log.Errorf("Failed to find disk %s when delete: %s", self.DiskId, err) + return err + } + + for { + err := self.storage.zone.region.DeleteDisk(self.DiskId) + if err != nil { + if isError(err, "IncorrectDiskStatus") { + log.Infof("The disk is initializing, try later ...") + time.Sleep(10 * time.Second) + } else { + log.Errorf("DeleteDisk fail: %s", err) + return err + } + } else { + break + } + } + return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes +} + +func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error { + return self.storage.zone.region.resizeDisk(self.DiskId, sizeMb) +} + +func (self *SDisk) GetName() string { + if len(self.DiskName) > 0 { + return self.DiskName + } + return self.DiskId +} + +func (self *SDisk) GetGlobalId() string { + return self.DiskId +} + +func (self *SDisk) IsEmulated() bool { + return false +} + +func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { + return self.storage, nil +} + +func (self *SDisk) GetStatus() string { + // In_use Available Attaching Detaching Creating ReIniting All + switch self.Status { + case "Creating", "ReIniting": + return api.DISK_ALLOCATING + default: + return api.DISK_READY + } +} + +func (self *SDisk) Refresh() error { + new, err := self.storage.zone.region.getDisk(self.DiskId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SDisk) ResizeDisk(newSize int64) error { + // newSize 单位为 GB. 范围在20 ~2000. 只能往大调。不能调小 + // https://help.apsara.com/document_detail/25522.html?spm=a2c4g.11174283.6.897.aHwqkS + return self.storage.zone.region.resizeDisk(self.DiskId, newSize) +} + +func (self *SDisk) GetDiskFormat() string { + return "vhd" +} + +func (self *SDisk) GetDiskSizeMB() int { + return self.Size * 1024 +} + +func (self *SDisk) GetIsAutoDelete() bool { + return self.DeleteWithInstance +} + +func (self *SDisk) GetTemplateId() string { + return self.ImageId +} + +func (self *SDisk) GetDiskType() string { + switch self.Type { + case "system": + return api.DISK_TYPE_SYS + case "data": + return api.DISK_TYPE_DATA + default: + return api.DISK_TYPE_DATA + } +} + +func (self *SDisk) GetFsFormat() string { + return "" +} + +func (self *SDisk) GetIsNonPersistent() bool { + return false +} + +func (self *SDisk) GetDriver() string { + return "scsi" +} + +func (self *SDisk) GetCacheMode() string { + return "none" +} + +func (self *SDisk) GetMountpoint() string { + return "" +} + +func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, desc string, projectId string) (string, error) { + params := make(map[string]string) + params["ZoneId"] = zoneId + params["DiskName"] = name + if len(desc) > 0 { + params["Description"] = desc + } + params["Encrypted"] = "false" + params["DiskCategory"] = category + if category == api.STORAGE_CLOUD_ESSD_PL2 { + params["DiskCategory"] = api.STORAGE_CLOUD_ESSD + params["PerformanceLevel"] = "PL2" + } + if category == api.STORAGE_CLOUD_ESSD_PL3 { + params["DiskCategory"] = api.STORAGE_CLOUD_ESSD + params["PerformanceLevel"] = "PL3" + } + + if len(projectId) > 0 { + params["ResourceGroupId"] = projectId + } + params["Size"] = fmt.Sprintf("%d", sizeGb) + params["ClientToken"] = utils.GenRequestId(20) + + body, err := self.ecsRequest("CreateDisk", params) + if err != nil { + return "", err + } + return body.GetString("DiskId") +} + +func (self *SRegion) getDisk(diskId string) (*SDisk, error) { + disks, total, err := self.GetDisks("", "", "", []string{diskId}, 0, 1) + if err != nil { + return nil, err + } + if total != 1 { + return nil, cloudprovider.ErrNotFound + } + return &disks[0], nil +} + +func (self *SRegion) DeleteDisk(diskId string) error { + params := make(map[string]string) + params["DiskId"] = diskId + + _, err := self.ecsRequest("DeleteDisk", params) + return err +} + +func (self *SRegion) resizeDisk(diskId string, sizeMb int64) error { + sizeGb := sizeMb / 1024 + params := make(map[string]string) + params["DiskId"] = diskId + params["NewSize"] = fmt.Sprintf("%d", sizeGb) + + _, err := self.ecsRequest("ResizeDisk", params) + if err != nil { + log.Errorf("resizing disk (%s) to %d GiB failed: %s", diskId, sizeGb, err) + return err + } + + return nil +} + +func (self *SRegion) resetDisk(diskId, snapshotId string) error { + params := make(map[string]string) + params["DiskId"] = diskId + params["SnapshotId"] = snapshotId + _, err := self.ecsRequest("ResetDisk", params) + if err != nil { + log.Errorf("ResetDisk %s to snapshot %s fail %s", diskId, snapshotId, err) + return err + } + + return nil +} + +func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { + if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.DiskId, name, desc); err != nil { + log.Errorf("createSnapshot fail %s", err) + return nil, err + } else if snapshot, err := self.getSnapshot(snapshotId); err != nil { + return nil, err + } else { + snapshot.region = self.storage.zone.region + if err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil { + return nil, err + } + return snapshot, nil + } +} + +func (self *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["DiskId"] = diskId + params["SnapshotName"] = name + params["Description"] = desc + + if body, err := self.ecsRequest("CreateSnapshot", params); err != nil { + log.Errorf("CreateSnapshot fail %s", err) + return "", err + } else { + return body.GetString("SnapshotId") + } +} + +func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { + if snapshot, err := self.getSnapshot(snapshotId); err != nil { + return nil, err + } else { + snapshot.region = self.storage.zone.region + return snapshot, nil + } +} + +func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) { + if snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1); err != nil { + return nil, err + } else if total != 1 { + return nil, cloudprovider.ErrNotFound + } else { + return &snapshots[0], nil + } +} + +func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + snapshots := make([]SSnapshot, 0) + for { + if parts, total, err := self.storage.zone.region.GetSnapshots("", self.DiskId, "", []string{}, 0, 20); err != nil { + log.Errorf("GetDisks fail %s", err) + return nil, err + } else { + snapshots = append(snapshots, parts...) + if len(snapshots) >= total { + break + } + } + } + isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)) + for i := 0; i < len(snapshots); i++ { + snapshots[i].region = self.storage.zone.region + isnapshots[i] = &snapshots[i] + } + return isnapshots, nil +} + +func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { + return "", self.storage.zone.region.resetDisk(self.DiskId, snapshotId) +} + +func (self *SDisk) GetBillingType() string { + return convertChargeType(self.DiskChargeType) +} + +func (self *SDisk) GetCreatedAt() time.Time { + return self.CreationTime +} + +func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { + if len(self.AutoSnapshotPolicyId) == 0 { + return []string{}, nil + } + return []string{self.AutoSnapshotPolicyId}, nil +} + +func (self *SDisk) GetExpiredAt() time.Time { + return convertExpiredAt(self.ExpiredTime) +} + +func (self *SDisk) GetAccessPath() string { + return "" +} + +func (self *SDisk) Rebuild(ctx context.Context) error { + err := self.storage.zone.region.rebuildDisk(self.DiskId) + if err != nil { + if isError(err, "IncorrectInstanceStatus") { + return nil + } + log.Errorf("rebuild disk fail %s", err) + return err + } + return nil +} + +func (self *SRegion) rebuildDisk(diskId string) error { + params := make(map[string]string) + params["DiskId"] = diskId + _, err := self.ecsRequest("ReInitDisk", params) + if err != nil { + log.Errorf("ReInitDisk %s fail %s", diskId, err) + return err + } + return nil +} + +func (self *SDisk) GetProjectId() string { + return self.ResourceGroupId +} diff --git a/pkg/multicloud/apsara/doc.go b/pkg/multicloud/apsara/doc.go new file mode 100644 index 0000000000..1a15fbd3d1 --- /dev/null +++ b/pkg/multicloud/apsara/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 apsara // import "yunion.io/x/onecloud/pkg/multicloud/apsara" diff --git a/pkg/multicloud/apsara/eip.go b/pkg/multicloud/apsara/eip.go new file mode 100644 index 0000000000..bbc4174671 --- /dev/null +++ b/pkg/multicloud/apsara/eip.go @@ -0,0 +1,386 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type TInternetChargeType string + +const ( + InternetChargeByTraffic = TInternetChargeType("PayByTraffic") + InternetChargeByBandwidth = TInternetChargeType("PayByBandwidth") +) + +const ( + EIP_STATUS_ASSOCIATING = "Associating" + EIP_STATUS_UNASSOCIATING = "Unassociating" + EIP_STATUS_INUSE = "InUse" + EIP_STATUS_AVAILABLE = "Available" + + EIP_OPERATION_LOCK_FINANCIAL = "financial" + EIP_OPERATION_LOCK_SECURITY = "security" + + EIP_INSTANCE_TYPE_ECS = "EcsInstance" // (默认值):VPC类型的ECS实例 + EIP_INTANNCE_TYPE_SLB = "SlbInstance" // :VPC类型的SLB实例 + EIP_INSTANCE_TYPE_NAT = "Nat" // :NAT网关 + EIP_INSTANCE_TYPE_HAVIP = "HaVip" // :HAVIP +) + +/* +{ + "AllocationId":"eip-2zeddtan63ou44dtyt9s3", + "AllocationTime":"2019-02-23T06:48:36Z", + "Bandwidth":"100", + "ChargeType":"PostPaid", + "ExpiredTime":"", + "InstanceId":"", + "InstanceType":"", + "InternetChargeType":"PayByTraffic", + "IpAddress":"39.105.131.32", + "OperationLocks":{"LockReason":[]}, + "RegionId":"cn-beijing", + "Status":"Available" +} +*/ + +type SEipAddress struct { + region *SRegion + multicloud.SEipBase + + AllocationId string + + InternetChargeType TInternetChargeType + + IpAddress string + Status string + + InstanceType string + InstanceId string + Bandwidth int /* Mbps */ + + AllocationTime time.Time + + OperationLocks string + + ChargeType TChargeType + ExpiredTime time.Time + ResourceGroupId string +} + +func (self *SEipAddress) GetId() string { + return self.AllocationId +} + +func (self *SEipAddress) GetName() string { + return self.IpAddress +} + +func (self *SEipAddress) GetGlobalId() string { + return self.AllocationId +} + +func (self *SEipAddress) GetStatus() string { + switch self.Status { + case EIP_STATUS_AVAILABLE, EIP_STATUS_INUSE: + return api.EIP_STATUS_READY + case EIP_STATUS_ASSOCIATING: + return api.EIP_STATUS_ASSOCIATE + case EIP_STATUS_UNASSOCIATING: + return api.EIP_STATUS_DISSOCIATE + default: + return api.EIP_STATUS_UNKNOWN + } +} + +func (self *SEipAddress) Refresh() error { + if self.IsEmulated() { + return nil + } + new, err := self.region.GetEip(self.AllocationId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SEipAddress) IsEmulated() bool { + if self.AllocationId == self.InstanceId { + // fixed Public IP + return true + } else { + return false + } +} + +func (self *SEipAddress) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SEipAddress) GetIpAddr() string { + return self.IpAddress +} + +func (self *SEipAddress) GetMode() string { + if self.InstanceId == self.AllocationId { + return api.EIP_MODE_INSTANCE_PUBLICIP + } else { + return api.EIP_MODE_STANDALONE_EIP + } +} + +func (self *SEipAddress) GetAssociationType() string { + switch self.InstanceType { + case EIP_INSTANCE_TYPE_ECS: + return api.EIP_ASSOCIATE_TYPE_SERVER + case EIP_INSTANCE_TYPE_NAT: + return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY + case EIP_INTANNCE_TYPE_SLB: + return api.EIP_ASSOCIATE_TYPE_LOADBALANCER + default: + //log.Fatalf("unsupported type: %s", self.InstanceType) + return "unsupported" + } +} + +func (self *SEipAddress) GetAssociationExternalId() string { + return self.InstanceId +} + +func (self *SEipAddress) GetBillingType() string { + return convertChargeType(self.ChargeType) +} + +func (self *SEipAddress) GetCreatedAt() time.Time { + return self.AllocationTime +} + +func (self *SEipAddress) GetExpiredAt() time.Time { + return convertExpiredAt(self.ExpiredTime) +} + +func (self *SEipAddress) Delete() error { + return self.region.DeallocateEIP(self.AllocationId) +} + +func (self *SEipAddress) GetBandwidth() int { + return self.Bandwidth +} + +func (self *SEipAddress) GetINetworkId() string { + return "" +} + +func (self *SEipAddress) GetInternetChargeType() string { + switch self.InternetChargeType { + case InternetChargeByTraffic: + return api.EIP_CHARGE_TYPE_BY_TRAFFIC + case InternetChargeByBandwidth: + return api.EIP_CHARGE_TYPE_BY_BANDWIDTH + default: + return api.EIP_CHARGE_TYPE_BY_TRAFFIC + } +} + +func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error { + err := cloudprovider.Wait(20*time.Second, 60*time.Second, func() (bool, error) { + err := self.region.AssociateEip(self.AllocationId, conf.InstanceId) + if err != nil { + if isError(err, "IncorrectInstanceStatus") { + return false, nil + } + return false, errors.Wrap(err, "region.AssociateEip") + } + return true, nil + }) + err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second) + return err +} + +func (self *SEipAddress) Dissociate() error { + err := self.region.DissociateEip(self.AllocationId, self.InstanceId) + if err != nil { + return err + } + err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second) + return err +} + +func (self *SEipAddress) ChangeBandwidth(bw int) error { + return self.region.UpdateEipBandwidth(self.AllocationId, bw) +} + +func (region *SRegion) GetEips(eipId string, associatedId string, offset int, limit int) ([]SEipAddress, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(eipId) > 0 { + params["AllocationId"] = eipId + } + + if len(associatedId) > 0 { + params["AssociatedInstanceId"] = associatedId + for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "ngw-": "Nat", "lb-": "SlbInstance"} { + if strings.HasPrefix(associatedId, prefix) { + params["AssociatedInstanceType"] = instanceType + } + } + } + + body, err := region.ecsRequest("DescribeEipAddresses", params) + if err != nil { + log.Errorf("DescribeEipAddresses fail %s", err) + return nil, 0, err + } + + eips := make([]SEipAddress, 0) + err = body.Unmarshal(&eips, "EipAddresses", "EipAddress") + if err != nil { + log.Errorf("Unmarshal EipAddress details fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + for i := 0; i < len(eips); i += 1 { + eips[i].region = region + } + return eips, int(total), nil +} + +func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) { + eips, total, err := region.GetEips(eipId, "", 0, 1) + if err != nil { + return nil, err + } + if total != 1 { + return nil, cloudprovider.ErrNotFound + } + return &eips[0], nil +} + +func (region *SRegion) AllocateEIP(bwMbps int, chargeType TInternetChargeType, projectId string) (*SEipAddress, error) { + params := make(map[string]string) + + params["Bandwidth"] = fmt.Sprintf("%d", bwMbps) + params["InternetChargeType"] = string(chargeType) + params["InstanceChargeType"] = "PostPaid" + params["ClientToken"] = utils.GenRequestId(20) + if len(projectId) > 0 { + params["ResourceGroupId"] = projectId + } + + body, err := region.ecsRequest("AllocateEipAddress", params) + if err != nil { + log.Errorf("AllocateEipAddress fail %s", err) + return nil, err + } + + eipId, err := body.GetString("AllocationId") + if err != nil { + log.Errorf("fail to get AllocationId after EIP allocation??? %s", err) + return nil, err + } + + return region.GetEip(eipId) +} + +func (region *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { + var ctype TInternetChargeType + switch eip.ChargeType { + case api.EIP_CHARGE_TYPE_BY_TRAFFIC: + ctype = InternetChargeByTraffic + case api.EIP_CHARGE_TYPE_BY_BANDWIDTH: + ctype = InternetChargeByBandwidth + } + return region.AllocateEIP(eip.BandwidthMbps, ctype, eip.ProjectId) +} + +func (region *SRegion) DeallocateEIP(eipId string) error { + params := make(map[string]string) + params["AllocationId"] = eipId + + _, err := region.ecsRequest("ReleaseEipAddress", params) + if err != nil { + log.Errorf("ReleaseEipAddress fail %s", err) + } + return err +} + +func (region *SRegion) AssociateEip(eipId string, instanceId string) error { + params := make(map[string]string) + params["AllocationId"] = eipId + params["InstanceId"] = instanceId + for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "lb-": "SlbInstance", "ngw-": "Nat"} { + if strings.HasPrefix(instanceId, prefix) { + params["InstanceType"] = instanceType + } + } + + _, err := region.ecsRequest("AssociateEipAddress", params) + if err != nil { + log.Errorf("AssociateEipAddress fail %s", err) + } + return err +} + +func (region *SRegion) DissociateEip(eipId string, instanceId string) error { + params := make(map[string]string) + params["AllocationId"] = eipId + params["InstanceId"] = instanceId + for prefix, instanceType := range map[string]string{"i-": "EcsInstance", "lb-": "SlbInstance", "ngw-": "Nat"} { + if strings.HasPrefix(instanceId, prefix) { + params["InstanceType"] = instanceType + } + } + + _, err := region.ecsRequest("UnassociateEipAddress", params) + if err != nil { + log.Errorf("UnassociateEipAddress fail %s", err) + } + return err +} + +func (region *SRegion) UpdateEipBandwidth(eipId string, bw int) error { + params := make(map[string]string) + params["AllocationId"] = eipId + params["Bandwidth"] = fmt.Sprintf("%d", bw) + + _, err := region.ecsRequest("ModifyEipAddressAttribute", params) + if err != nil { + log.Errorf("ModifyEipAddressAttribute fail %s", err) + } + return err +} + +func (self *SEipAddress) GetProjectId() string { + return self.ResourceGroupId +} diff --git a/pkg/multicloud/apsara/elasticcache_account.go b/pkg/multicloud/apsara/elasticcache_account.go new file mode 100644 index 0000000000..3106eed1f7 --- /dev/null +++ b/pkg/multicloud/apsara/elasticcache_account.go @@ -0,0 +1,211 @@ +// 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 apsara + +import ( + "fmt" + "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/multicloud" +) + +// https://help.apsara.com/document_detail/95802.html?spm=a2c4g.11186623.6.746.1d4b302ayCuzXB +type SElasticcacheAccount struct { + multicloud.SElasticcacheAccountBase + + cacheDB *SElasticcache + + AccountStatus string `json:"AccountStatus"` + DatabasePrivileges DatabasePrivileges `json:"DatabasePrivileges"` + InstanceID string `json:"InstanceId"` + AccountName string `json:"AccountName"` + PrivExceeded string `json:"PrivExceeded"` + AccountType string `json:"AccountType"` +} + +type DatabasePrivileges struct { + DatabasePrivilege []DatabasePrivilege `json:"DatabasePrivilege"` +} + +type DatabasePrivilege struct { + AccountPrivilege string `json:"AccountPrivilege"` +} + +func (self *SElasticcacheAccount) GetId() string { + return fmt.Sprintf("%s/%s", self.InstanceID, self.AccountName) +} + +func (self *SElasticcacheAccount) GetName() string { + return self.AccountName +} + +func (self *SElasticcacheAccount) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheAccount) GetStatus() string { + switch strings.ToLower(self.AccountStatus) { + case "unavailable": + return api.ELASTIC_CACHE_ACCOUNT_STATUS_UNAVAILABLE + case "available": + return api.ELASTIC_CACHE_ACCOUNT_STATUS_AVAILABLE + default: + return self.AccountStatus + } +} + +func (self *SElasticcacheAccount) Refresh() error { + iaccount, err := self.cacheDB.GetICloudElasticcacheAccountByName(self.GetName()) + if err != nil { + return err + } + + err = jsonutils.Update(self, iaccount.(*SElasticcacheAccount)) + if err != nil { + return err + } + + return nil +} + +func (self *SElasticcacheAccount) GetAccountType() string { + if self.AccountName == self.cacheDB.InstanceID { + return api.ELASTIC_CACHE_ACCOUNT_TYPE_ADMIN + } + + switch self.AccountType { + case "Normal": + return api.ELASTIC_CACHE_ACCOUNT_TYPE_NORMAL + case "Super": + return api.ELASTIC_CACHE_ACCOUNT_TYPE_ADMIN + default: + return self.AccountType + } +} + +func (self *SElasticcacheAccount) GetAccountPrivilege() string { + if len(self.DatabasePrivileges.DatabasePrivilege) == 0 { + return "" + } + + privilege := self.DatabasePrivileges.DatabasePrivilege[0].AccountPrivilege + switch privilege { + case "RoleReadOnly": + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_READ + case "RoleReadWrite": + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_WRITE + case "RoleRepl": + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_REPL + default: + return privilege + } +} + +// https://help.apsara.com/document_detail/95941.html?spm=a2c4g.11186623.6.746.523555d8D8Whxq +// https://help.apsara.com/document_detail/98531.html?spm=5176.11065259.1996646101.searchclickresult.4df474c38Sc2SO +func (self *SElasticcacheAccount) ResetPassword(input cloudprovider.SCloudElasticCacheAccountResetPasswordInput) error { + params := make(map[string]string) + params["InstanceId"] = self.cacheDB.GetId() + params["AccountName"] = self.GetName() + params["AccountPassword"] = input.NewPassword + + err := DoAction(self.cacheDB.region.kvsRequest, "ResetAccountPassword", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcacheAccount.ResetPassword") + } + + if input.NoPasswordAccess != nil { + return cloudprovider.ErrNotSupported + } + + return nil +} + +func (self *SElasticcacheAccount) UpdateAccount(input cloudprovider.SCloudElasticCacheAccountUpdateInput) error { + if input.Password != nil { + inputPassword := cloudprovider.SCloudElasticCacheAccountResetPasswordInput{} + inputPassword.NewPassword = *input.Password + inputPassword.NoPasswordAccess = input.NoPasswordAccess + err := self.ResetPassword(inputPassword) + if err != nil { + return err + } + } + + if input.Description != nil { + err := self.ModifyAccountDescription(*input.Description) + if err != nil { + return err + } + } + + if input.AccountPrivilege != nil { + err := self.GrantAccountPrivilege(*input.AccountPrivilege) + if err != nil { + return err + } + } + + return nil +} + +// https://help.apsara.com/document_detail/96020.html?spm=a2c4g.11186623.6.747.5c6f1c717weYEX +func (self *SElasticcacheAccount) ModifyAccountDescription(desc string) error { + params := make(map[string]string) + params["InstanceId"] = self.cacheDB.GetId() + params["AccountName"] = self.GetName() + params["AccountDescription"] = desc + + err := DoAction(self.cacheDB.region.kvsRequest, "ModifyAccountDescription", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcacheAccount.ModifyAccountDescription") + } + + return nil +} + +// https://help.apsara.com/document_detail/95897.html?spm=a2c4g.11186623.6.745.28576cf9IqM44R +func (self *SElasticcacheAccount) GrantAccountPrivilege(privilege string) error { + params := make(map[string]string) + params["InstanceId"] = self.cacheDB.GetId() + params["AccountName"] = self.GetName() + params["AccountPrivilege"] = privilege + + err := DoAction(self.cacheDB.region.kvsRequest, "GrantAccountPrivilege", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcacheAccount.GrantAccountPrivilege") + } + + return nil +} + +// https://help.apsara.com/document_detail/95988.html?spm=a2c4g.11186623.6.743.cb291c71D1Hlmu +func (self *SElasticcacheAccount) Delete() error { + params := make(map[string]string) + params["InstanceId"] = self.cacheDB.GetId() + params["AccountName"] = self.GetName() + + err := DoAction(self.cacheDB.region.kvsRequest, "DeleteAccount", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcacheAccount.Delete") + } + + return nil +} diff --git a/pkg/multicloud/apsara/elasticcache_acl.go b/pkg/multicloud/apsara/elasticcache_acl.go new file mode 100644 index 0000000000..028b00fe00 --- /dev/null +++ b/pkg/multicloud/apsara/elasticcache_acl.go @@ -0,0 +1,105 @@ +// 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 apsara + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElasticcacheAcl struct { + multicloud.SElasticcacheAclBase + + cacheDB *SElasticcache + + SecurityIPList string `json:"SecurityIpList"` + SecurityIPGroupAttribute string `json:"SecurityIpGroupAttribute"` + SecurityIPGroupName string `json:"SecurityIpGroupName"` +} + +func (self *SElasticcacheAcl) GetId() string { + return fmt.Sprintf("%s/%s", self.cacheDB.GetId(), self.SecurityIPGroupName) +} + +func (self *SElasticcacheAcl) GetName() string { + return self.SecurityIPGroupName +} + +func (self *SElasticcacheAcl) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheAcl) GetStatus() string { + return api.ELASTIC_CACHE_ACL_STATUS_AVAILABLE +} + +func (self *SElasticcacheAcl) Refresh() error { + iacl, err := self.cacheDB.GetICloudElasticcacheAcl(self.GetId()) + if err != nil { + return err + } + + err = jsonutils.Update(self, iacl.(*SElasticcacheAcl)) + if err != nil { + return err + } + + return nil +} + +func (self *SElasticcacheAcl) GetIpList() string { + return self.SecurityIPList +} + +// https://help.apsara.com/document_detail/61002.html?spm=a2c4g.11186623.6.764.3752782fJpbjxH +func (self *SElasticcacheAcl) Delete() error { + params := make(map[string]string) + params["InstanceId"] = self.cacheDB.GetId() + params["ModifyMode"] = "Delete" + params["SecurityIpGroupName"] = self.GetName() + params["SecurityIps"] = self.SecurityIPList + + err := DoAction(self.cacheDB.region.kvsRequest, "ModifySecurityIps", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcacheAcl.Delete") + } + + return nil +} + +// https://help.apsara.com/document_detail/61002.html?spm=a2c4g.11186623.6.764.3752782fJpbjxH +func (self *SElasticcacheAcl) UpdateAcl(securityIps string) error { + return self.cacheDB.region.createAcl(self.cacheDB.GetId(), self.GetName(), securityIps) +} + +func (self *SRegion) createAcl(instanceId, aclName, securityIps string) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["SecurityIpGroupName"] = aclName + params["ModifyMode"] = "Cover" + params["SecurityIps"] = securityIps + + err := DoAction(self.kvsRequest, "ModifySecurityIps", params, nil, nil) + if err != nil { + return errors.Wrap(err, "region.UpdateAcl") + } + + return nil +} diff --git a/pkg/multicloud/apsara/elasticcache_backup.go b/pkg/multicloud/apsara/elasticcache_backup.go new file mode 100644 index 0000000000..8c06f4a373 --- /dev/null +++ b/pkg/multicloud/apsara/elasticcache_backup.go @@ -0,0 +1,142 @@ +// 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 apsara + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// https://help.apsara.com/document_detail/61081.html?spm=a2c4g.11186623.6.752.3d7630beuL57kI +type SElasticcacheBackup struct { + multicloud.SElasticcacheBackupBase + + cacheDB *SElasticcache + + BackupIntranetDownloadURL string `json:"BackupIntranetDownloadURL"` + BackupType string `json:"BackupType"` + BackupEndTime time.Time `json:"BackupEndTime"` + BackupMethod string `json:"BackupMethod"` + BackupID int64 `json:"BackupId"` + BackupStartTime time.Time `json:"BackupStartTime"` + BackupDownloadURL string `json:"BackupDownloadURL"` + BackupDBNames string `json:"BackupDBNames"` + NodeInstanceID string `json:"NodeInstanceId"` + BackupMode string `json:"BackupMode"` + BackupStatus string `json:"BackupStatus"` + BackupSizeByte int64 `json:"BackupSize"` + EngineVersion string `json:"EngineVersion"` +} + +func (self *SElasticcacheBackup) GetId() string { + return fmt.Sprintf("%d", self.BackupID) +} + +func (self *SElasticcacheBackup) GetName() string { + return self.GetId() +} + +func (self *SElasticcacheBackup) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheBackup) GetStatus() string { + switch self.BackupStatus { + case "Success", "running": + return api.ELASTIC_CACHE_BACKUP_STATUS_SUCCESS + case "Failed": + return api.ELASTIC_CACHE_BACKUP_STATUS_FAILED + default: + return self.BackupStatus + } +} + +func (self *SElasticcacheBackup) Refresh() error { + ibackup, err := self.cacheDB.GetICloudElasticcacheBackup(self.GetId()) + if err != nil { + return err + } + + err = jsonutils.Update(self, ibackup.(*SElasticcacheBackup)) + if err != nil { + return err + } + + return nil +} + +func (self *SElasticcacheBackup) GetBackupSizeMb() int { + return int(self.BackupSizeByte / 1024 / 1024) +} + +func (self *SElasticcacheBackup) GetBackupType() string { + switch self.BackupType { + case "FullBackup": + return api.ELASTIC_CACHE_BACKUP_TYPE_FULL + case "IncrementalBackup": + return api.ELASTIC_CACHE_BACKUP_TYPE_INCREMENTAL + default: + return self.BackupType + } +} + +func (self *SElasticcacheBackup) GetBackupMode() string { + switch self.BackupMode { + case "Automated": + return api.ELASTIC_CACHE_BACKUP_MODE_AUTOMATED + case "Manual": + return api.ELASTIC_CACHE_BACKUP_MODE_MANUAL + default: + return self.BackupMode + } +} + +func (self *SElasticcacheBackup) GetDownloadURL() string { + return self.BackupDownloadURL +} + +func (self *SElasticcacheBackup) GetStartTime() time.Time { + return self.BackupStartTime +} + +func (self *SElasticcacheBackup) GetEndTime() time.Time { + return self.BackupEndTime +} + +func (self *SElasticcacheBackup) Delete() error { + return cloudprovider.ErrNotSupported +} + +// https://help.apsara.com/document_detail/61083.html?spm=a2c4g.11186623.6.753.216f67ddzpyvTL +func (self *SElasticcacheBackup) RestoreInstance(instanceId string) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["BackupId"] = self.GetId() + + // 目前没有查询备份ID的接口,因此,备份ID没什么用 + err := DoAction(self.cacheDB.region.kvsRequest, "RestoreInstance", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.RestoreInstance") + } + + return nil +} diff --git a/pkg/multicloud/apsara/elasticcache_instance.go b/pkg/multicloud/apsara/elasticcache_instance.go new file mode 100644 index 0000000000..b7258d31af --- /dev/null +++ b/pkg/multicloud/apsara/elasticcache_instance.go @@ -0,0 +1,946 @@ +// 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 apsara + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/aokoli/goutils" + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + billingapi "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" +) + +// https://help.apsara.com/document_detail/60933.html?spm=a2c4g.11186623.6.726.38f82ca9U1Gtxw +type SElasticcache struct { + multicloud.SElasticcacheBase + multicloud.SBillingBase + + region *SRegion + attribute *SElasticcacheAttribute + netinfo []SNetInfo + + Config string `json:"Config"` + HasRenewChangeOrder bool `json:"HasRenewChangeOrder"` + InstanceID string `json:"InstanceId"` + UserName string `json:"UserName"` + ArchitectureType string `json:"ArchitectureType"` + ZoneID string `json:"ZoneId"` + PrivateIP string `json:"PrivateIp"` + VSwitchID string `json:"VSwitchId"` + VpcID string `json:"VpcId"` + NetworkType string `json:"NetworkType"` + Qps int64 `json:"QPS"` + PackageType string `json:"PackageType"` + IsRDS bool `json:"IsRds"` + EngineVersion string `json:"EngineVersion"` + ConnectionDomain string `json:"ConnectionDomain"` + InstanceName string `json:"InstanceName"` + ReplacateID string `json:"ReplacateId"` + Bandwidth int64 `json:"Bandwidth"` + ChargeType TChargeType `json:"ChargeType"` + InstanceType string `json:"InstanceType"` + Tags Tags `json:"Tags"` + InstanceStatus string `json:"InstanceStatus"` + Port int `json:"Port"` + InstanceClass string `json:"InstanceClass"` + CreateTime time.Time `json:"CreateTime"` + EndTime time.Time `json:"EndTime"` + RegionID string `json:"RegionId"` + NodeType string `json:"NodeType"` + CapacityMB int `json:"Capacity"` + Connections int64 `json:"Connections"` + ResourceGroupId string `json:"ResourceGroupId"` +} + +type SElasticcacheAttribute struct { + Config string `json:"Config"` + HasRenewChangeOrder string `json:"HasRenewChangeOrder"` + InstanceID string `json:"InstanceId"` + ZoneID string `json:"ZoneId"` + ArchitectureType string `json:"ArchitectureType"` + PrivateIP string `json:"PrivateIp"` + VSwitchID string `json:"VSwitchId"` + Engine string `json:"Engine"` + VpcID string `json:"VpcId"` + NetworkType string `json:"NetworkType"` + Qps int64 `json:"QPS"` + PackageType string `json:"PackageType"` + ReplicaID string `json:"ReplicaId"` + IsRDS bool `json:"IsRds"` + MaintainStartTime string `json:"MaintainStartTime"` + VpcAuthMode string `json:"VpcAuthMode"` + ConnectionDomain string `json:"ConnectionDomain"` + EngineVersion string `json:"EngineVersion"` + InstanceName string `json:"InstanceName"` + Bandwidth int64 `json:"Bandwidth"` + ChargeType TChargeType `json:"ChargeType"` + AuditLogRetention string `json:"AuditLogRetention"` + MaintainEndTime string `json:"MaintainEndTime"` + ReplicationMode string `json:"ReplicationMode"` + InstanceType string `json:"InstanceType"` + InstanceStatus string `json:"InstanceStatus"` + Tags Tags `json:"Tags"` + Port int64 `json:"Port"` + InstanceClass string `json:"InstanceClass"` + CreateTime time.Time `json:"CreateTime"` + NodeType string `json:"NodeType"` + RegionID string `json:"RegionId"` + AvailabilityValue string `json:"AvailabilityValue"` + CapacityMB int `json:"Capacity"` + Connections int64 `json:"Connections"` + SecurityIPList string `json:"SecurityIPList"` +} + +type SNetInfo struct { + ConnectionString string `json:"ConnectionString"` + Port string `json:"Port"` + DBInstanceNetType string `json:"DBInstanceNetType"` + VPCID string `json:"VPCId"` + VPCInstanceID string `json:"VPCInstanceId"` + IPAddress string `json:"IPAddress"` + IPType string `json:"IPType"` + Upgradeable string `json:"Upgradeable"` + ExpiredTime *string `json:"ExpiredTime,omitempty"` +} + +func (self *SElasticcache) GetId() string { + return self.InstanceID +} + +func (self *SElasticcache) GetName() string { + return self.InstanceName +} + +func (self *SElasticcache) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcache) GetStatus() string { + switch self.InstanceStatus { + case "Normal": + return api.ELASTIC_CACHE_STATUS_RUNNING + case "Creating": + return api.ELASTIC_CACHE_STATUS_DEPLOYING + case "Changing": + return api.ELASTIC_CACHE_STATUS_CHANGING + case "Inactive": + return api.ELASTIC_CACHE_STATUS_INACTIVE + case "Flushing": + return api.ELASTIC_CACHE_STATUS_FLUSHING + case "Released": + return api.ELASTIC_CACHE_STATUS_RELEASED + case "Transforming": + return api.ELASTIC_CACHE_STATUS_TRANSFORMING + case "Unavailable": + return api.ELASTIC_CACHE_STATUS_UNAVAILABLE + case "Error": + return api.ELASTIC_CACHE_STATUS_ERROR + case "Migrating": + return api.ELASTIC_CACHE_STATUS_MIGRATING + case "BackupRecovering": + return api.ELASTIC_CACHE_STATUS_BACKUPRECOVERING + case "MinorVersionUpgrading": + return api.ELASTIC_CACHE_STATUS_MINORVERSIONUPGRADING + case "NetworkModifying": + return api.ELASTIC_CACHE_STATUS_NETWORKMODIFYING + case "SSLModifying": + return api.ELASTIC_CACHE_STATUS_SSLMODIFYING + case "MajorVersionUpgrading": + return api.ELASTIC_CACHE_STATUS_MAJORVERSIONUPGRADING + default: + return api.ELASTIC_CACHE_STATUS_MAJORVERSIONUPGRADING + } +} + +func (self *SElasticcache) Refresh() error { + cache, err := self.region.GetElasticCacheById(self.GetId()) + if err != nil { + return err + } + + err = jsonutils.Update(self, cache) + if err != nil { + return err + } + + return nil +} + +func (self *SElasticcache) GetBillingType() string { + return convertChargeType(self.ChargeType) +} + +func (self *SElasticcache) GetCreatedAt() time.Time { + return self.CreateTime +} + +func (self *SElasticcache) GetExpiredAt() time.Time { + return convertExpiredAt(self.EndTime) +} + +func (self *SElasticcache) GetInstanceType() string { + return self.InstanceClass +} + +func (self *SElasticcache) GetCapacityMB() int { + return self.CapacityMB +} + +func (self *SElasticcache) GetArchType() string { + switch self.ArchitectureType { + case "rwsplit": + return api.ELASTIC_CACHE_ARCH_TYPE_RWSPLIT + case "cluster": + return api.ELASTIC_CACHE_ARCH_TYPE_CLUSTER + case "standard": + if self.NodeType == "single" { + return api.ELASTIC_CACHE_ARCH_TYPE_SINGLE + } else if self.NodeType == "double" { + return api.ELASTIC_CACHE_ARCH_TYPE_MASTER + } + } + + return "" +} + +func (self *SElasticcache) GetNodeType() string { + return self.NodeType +} + +func (self *SElasticcache) GetEngine() string { + return self.InstanceType +} + +func (self *SElasticcache) GetEngineVersion() string { + return self.EngineVersion +} + +func (self *SElasticcache) GetVpcId() string { + return self.VpcID +} + +func (self *SElasticcache) GetZoneId() string { + zone, err := self.region.getZoneById(self.ZoneID) + if err != nil { + log.Errorf("failed to find zone for elasticcache %s error: %v", self.GetId(), err) + return "" + } + return zone.GetGlobalId() +} + +func (self *SElasticcache) GetNetworkType() string { + switch self.NetworkType { + case "VPC": + return api.LB_NETWORK_TYPE_VPC + case "CLASSIC": + return api.LB_NETWORK_TYPE_CLASSIC + default: + return api.LB_NETWORK_TYPE_VPC + } +} + +func (self *SElasticcache) GetNetworkId() string { + return self.VSwitchID +} + +func (self *SElasticcache) GetPrivateDNS() string { + return self.ConnectionDomain +} + +func (self *SElasticcache) GetPrivateIpAddr() string { + return self.PrivateIP +} + +func (self *SElasticcache) GetPrivateConnectPort() int { + return self.Port +} + +func (self *SElasticcache) Renew(bc billing.SBillingCycle) error { + return cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) GetPublicDNS() string { + pub, err := self.GetPublicNetInfo() + if err != nil { + log.Errorf("SElasticcache.GetPublicDNS %s", err) + return "" + } + + if pub != nil { + return pub.ConnectionString + } + + return "" +} + +func (self *SElasticcache) GetPublicIpAddr() string { + pub, err := self.GetPublicNetInfo() + if err != nil { + log.Errorf("SElasticcache.GetPublicIpAddr %s", err) + } + + if pub != nil { + return pub.IPAddress + } + + return "" +} + +func (self *SElasticcache) GetPublicConnectPort() int { + pub, err := self.GetPublicNetInfo() + if err != nil { + log.Errorf("SElasticcache.GetPublicConnectPort %s", err) + } + + if pub != nil { + port, _ := strconv.Atoi(pub.Port) + return port + } + + return 0 +} + +func (self *SElasticcache) GetMaintainStartTime() string { + attr, err := self.GetAttribute() + if err != nil { + log.Errorf("SElasticcache.GetMaintainStartTime %s", err) + } + + if attr != nil { + return attr.MaintainStartTime + } + + return "" +} + +func (self *SElasticcache) GetMaintainEndTime() string { + attr, err := self.GetAttribute() + if err != nil { + log.Errorf("SElasticcache.GetMaintainEndTime %s", err) + } + + if attr != nil { + return attr.MaintainEndTime + } + + return "" +} + +func (self *SElasticcache) GetICloudElasticcacheAccounts() ([]cloudprovider.ICloudElasticcacheAccount, error) { + accounts, err := self.region.GetElasticCacheAccounts(self.GetId()) + if err != nil { + return nil, err + } + + iaccounts := make([]cloudprovider.ICloudElasticcacheAccount, len(accounts)) + for i := range accounts { + accounts[i].cacheDB = self + iaccounts[i] = &accounts[i] + } + + return iaccounts, nil +} + +func (self *SElasticcache) GetICloudElasticcacheAccountByName(accountName string) (cloudprovider.ICloudElasticcacheAccount, error) { + account, err := self.region.GetElasticCacheAccountByName(self.GetId(), accountName) + if err != nil { + return nil, err + } + + account.cacheDB = self + return account, nil +} + +func (self *SElasticcache) GetICloudElasticcacheAcls() ([]cloudprovider.ICloudElasticcacheAcl, error) { + acls, err := self.region.GetElasticCacheAcls(self.GetId()) + if err != nil { + return nil, err + } + + iacls := make([]cloudprovider.ICloudElasticcacheAcl, len(acls)) + for i := range acls { + acls[i].cacheDB = self + iacls[i] = &acls[i] + } + + return iacls, nil +} + +func (self *SElasticcache) GetICloudElasticcacheBackups() ([]cloudprovider.ICloudElasticcacheBackup, error) { + start := self.CreateTime.Format("2006-01-02T15:04Z") + end := time.Now().Format("2006-01-02T15:04Z") + backups, err := self.region.GetElasticCacheBackups(self.GetId(), start, end) + if err != nil { + return nil, err + } + + ibackups := make([]cloudprovider.ICloudElasticcacheBackup, len(backups)) + for i := range backups { + backups[i].cacheDB = self + ibackups[i] = &backups[i] + } + + return ibackups, nil +} + +func (self *SElasticcache) GetICloudElasticcacheParameters() ([]cloudprovider.ICloudElasticcacheParameter, error) { + parameters, err := self.region.GetElasticCacheParameters(self.GetId()) + if err != nil { + return nil, err + } + + iparameters := make([]cloudprovider.ICloudElasticcacheParameter, len(parameters)) + for i := range parameters { + parameters[i].cacheDB = self + iparameters[i] = ¶meters[i] + } + + return iparameters, nil +} + +func (self *SElasticcache) GetAttribute() (*SElasticcacheAttribute, error) { + if self.attribute != nil { + return self.attribute, nil + } + + params := make(map[string]string) + params["RegionId"] = self.region.RegionId + params["InstanceId"] = self.GetId() + + rets := []SElasticcacheAttribute{} + err := DoListAll(self.region.kvsRequest, "DescribeInstanceAttribute", params, []string{"Instances", "DBInstanceAttribute"}, &rets) + if err != nil { + return nil, errors.Wrap(err, "elasticcache.GetAttribute") + } + + count := len(rets) + if count >= 1 { + self.attribute = &rets[0] + return self.attribute, nil + } else { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "elasticcache.GetAttribute %s", self.GetId()) + } +} + +func (self *SElasticcache) GetNetInfo() ([]SNetInfo, error) { + params := make(map[string]string) + params["RegionId"] = self.region.RegionId + params["InstanceId"] = self.GetId() + + rets := []SNetInfo{} + err := DoListAll(self.region.kvsRequest, "DescribeDBInstanceNetInfo", params, []string{"NetInfoItems", "InstanceNetInfo"}, &rets) + if err != nil { + return nil, errors.Wrap(err, "elasticcache.GetNetInfo") + } + + self.netinfo = rets + return self.netinfo, nil +} + +// https://help.apsara.com/document_detail/66742.html?spm=a2c4g.11186623.6.731.54c123d2P02qhk +func (self *SElasticcache) GetPublicNetInfo() (*SNetInfo, error) { + nets, err := self.GetNetInfo() + if err != nil { + return nil, err + } + + for i := range nets { + if nets[i].IPType == "Public" { + return &nets[i], nil + } + } + + return nil, nil +} + +func (self *SRegion) GetElasticCaches(instanceIds []string) ([]SElasticcache, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + if instanceIds != nil && len(instanceIds) > 0 { + params["InstanceIds"] = strings.Join(instanceIds, ",") + } + + ret := []SElasticcache{} + err := DoListAll(self.kvsRequest, "DescribeInstances", params, []string{"Instances", "KVStoreInstance"}, &ret) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCaches") + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +func (self *SRegion) GetElasticCacheById(instanceId string) (*SElasticcache, error) { + caches, err := self.GetElasticCaches([]string{instanceId}) + if err != nil { + return nil, errors.Wrapf(err, "region.GetElasticCacheById %s", instanceId) + } + + if len(caches) == 1 { + return &caches[0], nil + } else if len(caches) == 0 { + return nil, cloudprovider.ErrNotFound + } else { + return nil, errors.Wrapf(cloudprovider.ErrDuplicateId, "region.GetElasticCacheById %s.expect 1 found %d ", instanceId, len(caches)) + } +} + +func (self *SRegion) GetIElasticcacheById(id string) (cloudprovider.ICloudElasticcache, error) { + ec, err := self.GetElasticCacheById(id) + if err != nil { + return nil, err + } + + return ec, nil +} + +// https://help.apsara.com/document_detail/95802.html?spm=a2c4g.11186623.6.746.143e782f3Pfkfg +func (self *SRegion) GetElasticCacheAccounts(instanceId string) ([]SElasticcacheAccount, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + + ret := []SElasticcacheAccount{} + err := DoListAll(self.kvsRequest, "DescribeAccounts", params, []string{"Accounts", "Account"}, &ret) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCacheAccounts") + } + + return ret, nil +} + +func (self *SRegion) GetElasticCacheAccountByName(instanceId string, accountName string) (*SElasticcacheAccount, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + params["AccountName"] = accountName + + ret := []SElasticcacheAccount{} + err := DoListAll(self.kvsRequest, "DescribeAccounts", params, []string{"Accounts", "Account"}, &ret) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCacheAccounts") + } + + if len(ret) == 1 { + return &ret[0], nil + } else if len(ret) == 0 { + return nil, cloudprovider.ErrNotFound + } else { + return nil, errors.Wrap(fmt.Errorf("%d account with name %s found", len(ret), accountName), "region.GetElasticCacheAccountByName") + } +} + +// https://help.apsara.com/document_detail/63889.html?spm=a2c4g.11186623.6.764.3cb43852R7lnoS +func (self *SRegion) GetElasticCacheAcls(instanceId string) ([]SElasticcacheAcl, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + + ret := []SElasticcacheAcl{} + err := DoListAll(self.kvsRequest, "DescribeSecurityIps", params, []string{"SecurityIpGroups", "SecurityIpGroup"}, &ret) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCacheAcls") + } + + return ret, nil +} + +// https://help.apsara.com/document_detail/61081.html?spm=a2c4g.11186623.6.754.10613852qAbEQV +func (self *SRegion) GetElasticCacheBackups(instanceId, startTime, endTime string) ([]SElasticcacheBackup, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + params["StartTime"] = startTime + params["EndTime"] = endTime + + ret := []SElasticcacheBackup{} + err := DoListAll(self.kvsRequest, "DescribeBackups", params, []string{"Backups", "Backup"}, &ret) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCacheBackups") + } + + return ret, nil +} + +// https://help.apsara.com/document_detail/93078.html?spm=a2c4g.11186623.6.769.58011975YYL5Gl +func (self *SRegion) GetElasticCacheParameters(instanceId string) ([]SElasticcacheParameter, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["DBInstanceId"] = instanceId + + ret := []SElasticcacheParameter{} + err := DoListAll(self.kvsRequest, "DescribeParameters", params, []string{"RunningParameters", "Parameter"}, &ret) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCacheParameters") + } + + return ret, nil +} + +// https://help.apsara.com/document_detail/60873.html?spm=a2c4g.11174283.6.715.7412dce0qSYemb +func (self *SRegion) CreateIElasticcaches(ec *cloudprovider.SCloudElasticCacheInput) (cloudprovider.ICloudElasticcache, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceClass"] = ec.InstanceType + params["InstanceName"] = ec.InstanceName + params["InstanceType"] = ec.Engine + params["EngineVersion"] = ec.EngineVersion + + if len(ec.Password) > 0 { + params["UserName"] = ec.UserName + params["Password"] = ec.Password + } + + if len(ec.ZoneIds) > 0 { + params["ZoneId"] = ec.ZoneIds[0] + } + + if len(ec.PrivateIpAddress) > 0 { + params["PrivateIpAddress"] = ec.PrivateIpAddress + } + + if len(ec.NodeType) > 0 { + params["NodeType"] = ec.NodeType + } + + if len(ec.ProjectId) > 0 { + params["ResourceGroupId"] = ec.ProjectId + } + + params["NetworkType"] = ec.NetworkType + params["VpcId"] = ec.VpcId + params["VSwitchId"] = ec.NetworkId + params["ChargeType"] = ec.ChargeType + if strings.ToLower(ec.ChargeType) == billingapi.BILLING_TYPE_PREPAID && ec.BC != nil { + if ec.BC.GetMonths() >= 1 && ec.BC.GetMonths() <= 9 { + params["Period"] = strconv.Itoa(ec.BC.GetMonths()) + } else if ec.BC.GetMonths() == 12 || ec.BC.GetMonths() == 24 || ec.BC.GetMonths() == 36 { + params["Period"] = strconv.Itoa(ec.BC.GetMonths()) + } else { + return nil, fmt.Errorf("region.CreateIElasticcaches invalid billing cycle.reqired month (1~9) or year(1~3)") + } + } + + ret := &SElasticcache{} + err := DoAction(self.kvsRequest, "CreateInstance", params, []string{}, ret) + if err != nil { + return nil, errors.Wrap(err, "region.CreateIElasticcaches") + } + self.SetResourceTags("kvs", "INSTANCE", []string{ret.InstanceID}, ec.Tags, false) + + ret.region = self + return ret, nil +} + +// https://help.apsara.com/document_detail/116215.html?spm=a2c4g.11174283.6.736.6b9ddce0c5nsw6 +func (self *SElasticcache) Restart() error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["EffectiveTime"] = "0" // 立即重启 + + err := DoAction(self.region.kvsRequest, "RestartInstance", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.Restart") + } + + return nil +} + +// https://help.apsara.com/document_detail/60898.html?spm=a2c4g.11186623.6.713.56ec1603KS0xA0 +func (self *SElasticcache) Delete() error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + + err := DoAction(self.region.kvsRequest, "DeleteInstance", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.Delete") + } + + return nil +} + +// https://help.apsara.com/document_detail/60903.html?spm=a2c4g.11186623.6.711.3f062c92aRJNfw +func (self *SElasticcache) ChangeInstanceSpec(spec string) error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["InstanceClass"] = strings.Split(spec, ":")[0] + params["AutoPay"] = "true" // 自动付款 + + err := DoAction(self.region.kvsRequest, "ModifyInstanceSpec", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.ChangeInstanceSpec") + } + + return nil +} + +// https://help.apsara.com/document_detail/61000.html?spm=a2c4g.11186623.6.730.57d66cb0QlQS86 +func (self *SElasticcache) SetMaintainTime(maintainStartTime, maintainEndTime string) error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["MaintainStartTime"] = maintainStartTime + params["MaintainEndTime"] = maintainEndTime + + err := DoAction(self.region.kvsRequest, "ModifyInstanceMaintainTime", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.SetMaintainTime") + } + + return nil +} + +// https://help.apsara.com/document_detail/125795.html?spm=a2c4g.11186623.6.719.51542b3593cDKO +func (self *SElasticcache) AllocatePublicConnection(port int) (string, error) { + if port < 1024 { + port = 6379 + } + + suffix, _ := goutils.RandomAlphabetic(4) + conn := self.GetId() + strings.ToLower(suffix) + + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["Port"] = strconv.Itoa(port) + params["ConnectionStringPrefix"] = conn + + err := DoAction(self.region.kvsRequest, "AllocateInstancePublicConnection", params, nil, nil) + if err != nil { + return "", errors.Wrap(err, "elasticcache.AllocatePublicConnection") + } + + return conn, nil +} + +// https://help.apsara.com/document_detail/125796.html?spm=a2c4g.11186623.6.720.702a3b23Qayopy +func (self *SElasticcache) ReleasePublicConnection() error { + publicConn := self.GetPublicDNS() + if len(publicConn) == 0 { + log.Debugf("elasticcache.ReleasePublicConnection public connect is empty") + return nil + } + + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["CurrentConnectionString"] = publicConn + + err := DoAction(self.region.kvsRequest, "ReleaseInstancePublicConnection", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.ReleasePublicConnection") + } + + return nil +} + +// https://help.apsara.com/document_detail/93603.html?spm=a2c4g.11186623.6.732.10666cf9UVgNPb 修改链接地址 + +// https://help.apsara.com/document_detail/95973.html?spm=a2c4g.11186623.6.742.4698126aH0s4Q5 +func (self *SElasticcache) CreateAccount(input cloudprovider.SCloudElasticCacheAccountInput) (cloudprovider.ICloudElasticcacheAccount, error) { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["AccountName"] = input.AccountName + params["AccountPassword"] = input.AccountPassword + if len(input.AccountPrivilege) > 0 { + params["AccountPrivilege"] = input.AccountPrivilege + } + + if len(input.Description) > 0 { + params["AccountDescription"] = input.Description + } + + err := DoAction(self.region.kvsRequest, "CreateAccount", params, nil, nil) + if err != nil { + return nil, errors.Wrap(err, "elasticcache.CreateAccount") + } + + return self.GetICloudElasticcacheAccountByName(input.AccountName) +} + +func (self *SElasticcache) CreateAcl(aclName, securityIps string) (cloudprovider.ICloudElasticcacheAcl, error) { + acl := &SElasticcacheAcl{} + acl.cacheDB = self + acl.SecurityIPGroupName = aclName + acl.SecurityIPList = securityIps + return acl, self.region.createAcl(self.GetId(), aclName, securityIps) +} + +// https://help.apsara.com/document_detail/61113.html?spm=a2c4g.11186623.6.770.16a61e7admr0xy +func (self *SElasticcache) UpdateInstanceParameters(config jsonutils.JSONObject) error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["Config"] = config.String() + + err := DoAction(self.region.kvsRequest, "ModifyInstanceConfig", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.UpdateInstanceParameters") + } + + return nil +} + +// https://help.apsara.com/document_detail/61075.html?spm=a2c4g.11186623.6.749.4cba126a2U9xNa +func (self *SElasticcache) CreateBackup(desc string) (cloudprovider.ICloudElasticcacheBackup, error) { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + + // 目前没有查询备份ID的接口,因此,备份ID没什么用 + err := DoAction(self.region.kvsRequest, "CreateBackup", params, []string{"BackupJobID"}, nil) + if err != nil { + return nil, errors.Wrap(err, "elasticcache.CreateBackup") + } + + return nil, nil +} + +// https://help.apsara.com/document_detail/61077.html?spm=a2c4g.11186623.6.750.3d7630be7ziUfg +func (self *SElasticcache) UpdateBackupPolicy(config cloudprovider.SCloudElasticCacheBackupPolicyUpdateInput) error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + params["PreferredBackupPeriod"] = config.PreferredBackupPeriod + params["PreferredBackupTime"] = config.PreferredBackupTime + + err := DoAction(self.region.kvsRequest, "ModifyBackupPolicy", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.UpdateBackupPolicy") + } + + return nil +} + +// https://help.apsara.com/document_detail/60931.html?spm=a2c4g.11186623.6.728.5c57292920UKx3 +func (self *SElasticcache) FlushInstance(input cloudprovider.SCloudElasticCacheFlushInstanceInput) error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + + err := DoAction(self.region.kvsRequest, "FlushInstance", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.FlushInstance") + } + + return nil +} + +// https://help.apsara.com/document_detail/98531.html?spm=5176.11065259.1996646101.searchclickresult.4df474c38Sc2SO +func (self *SElasticcache) UpdateAuthMode(noPwdAccess bool, password string) error { + params := make(map[string]string) + params["InstanceId"] = self.GetId() + if noPwdAccess { + params["VpcAuthMode"] = "Close" + } else { + params["VpcAuthMode"] = "Open" + } + + err := DoAction(self.region.kvsRequest, "ModifyInstanceVpcAuthMode", params, nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcacheAccount.UpdateAuthMode") + } + + return nil +} + +func (self *SElasticcache) GetProjectId() string { + return self.ResourceGroupId +} + +func (self *SElasticcache) GetAuthMode() string { + attribute, err := self.GetAttribute() + if err != nil { + log.Errorf("elasticcache.GetAuthMode %s", err) + } + switch attribute.VpcAuthMode { + case "Open": + return "on" + default: + return "off" + } +} + +func (self *SElasticcache) GetSecurityGroupIds() ([]string, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) GetICloudElasticcacheAccount(accountId string) (cloudprovider.ICloudElasticcacheAccount, error) { + segs := strings.Split(accountId, "/") + if len(segs) < 2 { + return nil, errors.Wrap(fmt.Errorf("%s", accountId), "elasticcache.GetICloudElasticcacheAccount invalid account id ") + } + + return self.GetICloudElasticcacheAccountByName(segs[1]) +} + +func (self *SElasticcache) GetICloudElasticcacheAcl(aclId string) (cloudprovider.ICloudElasticcacheAcl, error) { + acls, err := self.GetICloudElasticcacheAcls() + if err != nil { + return nil, err + } + + for _, acl := range acls { + if acl.GetId() == aclId { + return acl, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SElasticcache) GetICloudElasticcacheBackup(backupId string) (cloudprovider.ICloudElasticcacheBackup, error) { + backups, err := self.GetICloudElasticcacheBackups() + if err != nil { + return nil, err + } + + for _, backup := range backups { + if backup.GetId() == backupId { + return backup, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (instance *SElasticcache) GetMetadata() *jsonutils.JSONDict { + data := jsonutils.NewDict() + tags, err := instance.region.ListResourceTags("kvs", "INSTANCE", []string{instance.GetId()}) + if err != nil { + log.Errorf(`[err:%s]instance.region.FetchResourceTags("kvs", "instance", []string{instance.GetId()})`, err.Error()) + return nil + } + if _, ok := tags[instance.GetId()]; !ok { + return nil + } + data.Update(jsonutils.Marshal(tags[instance.GetId()])) + return data +} + +func (instance *SElasticcache) SetMetadata(tags map[string]string, replace bool) error { + return instance.region.SetResourceTags("kvs", "INSTANCE", []string{instance.GetId()}, tags, replace) +} + +func (self *SElasticcache) UpdateSecurityGroups(secgroupIds []string) error { + return errors.Wrap(cloudprovider.ErrNotSupported, "UpdateSecurityGroups") +} diff --git a/pkg/multicloud/apsara/elasticcache_parameter.go b/pkg/multicloud/apsara/elasticcache_parameter.go new file mode 100644 index 0000000000..ce4e52deff --- /dev/null +++ b/pkg/multicloud/apsara/elasticcache_parameter.go @@ -0,0 +1,85 @@ +// 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 apsara + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElasticcacheParameter struct { + multicloud.SElasticcacheParameterBase + + cacheDB *SElasticcache + + ParameterDescription string `json:"ParameterDescription"` + ParameterValue string `json:"ParameterValue"` + ForceRestart string `json:"ForceRestart"` + CheckingCode string `json:"CheckingCode"` + ModifiableStatus string `json:"ModifiableStatus"` + ParameterName string `json:"ParameterName"` +} + +func (self *SElasticcacheParameter) GetId() string { + return fmt.Sprintf("%s/%s", self.cacheDB.InstanceID, self.ParameterName) +} + +func (self *SElasticcacheParameter) GetName() string { + return self.ParameterName +} + +func (self *SElasticcacheParameter) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheParameter) GetStatus() string { + return api.ELASTIC_CACHE_PARAMETER_STATUS_AVAILABLE +} + +func (self *SElasticcacheParameter) GetParameterKey() string { + return self.ParameterName +} + +func (self *SElasticcacheParameter) GetParameterValue() string { + return self.ParameterValue +} + +func (self *SElasticcacheParameter) GetParameterValueRange() string { + return self.CheckingCode +} + +func (self *SElasticcacheParameter) GetDescription() string { + return self.ParameterDescription +} + +func (self *SElasticcacheParameter) GetModifiable() bool { + switch self.ModifiableStatus { + case "true": + return true + default: + return false + } +} + +func (self *SElasticcacheParameter) GetForceRestart() bool { + switch self.ForceRestart { + case "true": + return true + default: + return false + } +} diff --git a/pkg/multicloud/apsara/errors.go b/pkg/multicloud/apsara/errors.go new file mode 100644 index 0000000000..99c886b36d --- /dev/null +++ b/pkg/multicloud/apsara/errors.go @@ -0,0 +1,29 @@ +// 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 apsara + +import ( + "fmt" + "strings" +) + +func isError(err error, code string) bool { + errStr := fmt.Sprintf("%s", err) + if strings.Index(errStr, code) > 0 { + return true + } else { + return false + } +} diff --git a/pkg/multicloud/apsara/host.go b/pkg/multicloud/apsara/host.go new file mode 100644 index 0000000000..8bf219c3dd --- /dev/null +++ b/pkg/multicloud/apsara/host.go @@ -0,0 +1,299 @@ +// 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 apsara + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + 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 SHost struct { + multicloud.SHostBase + zone *SZone +} + +func (self *SHost) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SHost) GetIWires() ([]cloudprovider.ICloudWire, error) { + return self.zone.GetIWires() +} + +func (self *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + return self.zone.GetIStorages() +} + +func (self *SHost) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + return self.zone.GetIStorageById(id) +} + +func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) { + vms := make([]SInstance, 0) + for { + parts, total, err := self.zone.region.GetInstances(self.zone.ZoneId, nil, len(vms), 50) + if err != nil { + return nil, err + } + vms = append(vms, parts...) + if len(vms) >= total { + break + } + } + ivms := make([]cloudprovider.ICloudVM, len(vms)) + for i := 0; i < len(vms); i += 1 { + vms[i].host = self + ivms[i] = &vms[i] + } + return ivms, nil +} + +func (self *SHost) VMGlobalId2Id(gid string) string { + return gid +} + +func (self *SHost) GetIVMById(gid string) (cloudprovider.ICloudVM, error) { + id := self.VMGlobalId2Id(gid) + parts, _, err := self.zone.region.GetInstances(self.zone.ZoneId, []string{id}, 0, 1) + if err != nil { + return nil, err + } + if len(parts) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(parts) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + parts[0].host = self + return &parts[0], nil +} + +func (self *SHost) GetId() string { + return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId()) +} + +func (self *SHost) GetName() string { + return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Name, self.zone.GetId()) +} + +func (self *SHost) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId()) +} + +func (self *SHost) IsEmulated() bool { + return true +} + +func (self *SHost) GetStatus() string { + return api.HOST_STATUS_RUNNING +} + +func (self *SHost) Refresh() error { + return nil +} + +func (self *SHost) GetHostStatus() string { + return api.HOST_ONLINE +} + +func (self *SHost) GetEnabled() bool { + return true +} + +func (self *SHost) GetAccessIp() string { + return "" +} + +func (self *SHost) GetAccessMac() string { + return "" +} + +func (self *SHost) GetSysInfo() jsonutils.JSONObject { + info := jsonutils.NewDict() + info.Add(jsonutils.NewString(CLOUD_PROVIDER_APSARA), "manufacture") + return info +} + +func (self *SHost) GetSN() string { + return "" +} + +func (self *SHost) GetCpuCount() int { + return 0 +} + +func (self *SHost) GetNodeCount() int8 { + return 0 +} + +func (self *SHost) GetCpuDesc() string { + return "" +} + +func (self *SHost) GetCpuMhz() int { + return 0 +} + +func (self *SHost) GetMemSizeMB() int { + return 0 +} + +func (self *SHost) GetStorageSizeMB() int { + return 0 +} + +func (self *SHost) GetStorageType() string { + return api.DISK_TYPE_HYBRID +} + +func (self *SHost) GetHostType() string { + return api.HOST_TYPE_APSARA +} + +func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) { + inst, err := self.zone.region.GetInstance(instanceId) + if err != nil { + return nil, err + } + inst.host = self + return inst, nil +} + +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + vmId, err := self._createVM(desc.Name, desc.ExternalImageId, desc.SysDisk, desc.Cpu, desc.MemoryMB, + desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, + desc.DataDisks, desc.PublicKey, desc.ExternalSecgroupId, desc.UserData, desc.BillingCycle, + desc.ProjectId, desc.OsType, desc.Tags) + if err != nil { + return nil, err + } + vm, err := self.GetInstanceById(vmId) + if err != nil { + return nil, err + } + // err = vm.waitStatus(InstanceStatusStopped, time.Second*10, time.Second*1800) + return vm, err +} + +func (self *SHost) _createVM(name string, imgId string, + sysDisk cloudprovider.SDiskInfo, cpu int, memMB int, instanceType string, + vswitchId string, ipAddr string, desc string, passwd string, + dataDisks []cloudprovider.SDiskInfo, publicKey string, secgroupId string, + userData string, bc *billing.SBillingCycle, projectId, osType string, + tags map[string]string, +) (string, error) { + net := self.zone.getNetworkById(vswitchId) + if net == nil { + return "", fmt.Errorf("invalid switch ID %s", vswitchId) + } + if net.wire == nil { + log.Errorf("vsiwtch's wire is empty") + return "", fmt.Errorf("vsiwtch's wire is empty") + } + if net.wire.vpc == nil { + log.Errorf("vsiwtch's wire' vpc is empty") + return "", fmt.Errorf("vsiwtch's wire's vpc is empty") + } + + var err error + keypair := "" + if len(publicKey) > 0 { + keypair, err = self.zone.region.syncKeypair(publicKey) + if err != nil { + return "", err + } + } + + img, err := self.zone.region.GetImage(imgId) + if err != nil { + log.Errorf("GetImage fail %s", err) + return "", err + } + if img.Status != ImageStatusAvailable { + log.Errorf("image %s status %s", imgId, img.Status) + return "", fmt.Errorf("image not ready") + } + + disks := make([]SDisk, len(dataDisks)+1) + disks[0].Size = img.Size + if sysDisk.SizeGB > 0 && sysDisk.SizeGB > img.Size { + disks[0].Size = sysDisk.SizeGB + } + storage, err := self.zone.getStorageByCategory(sysDisk.StorageType) + if err != nil { + return "", fmt.Errorf("Storage %s not avaiable: %s", sysDisk.StorageType, err) + } + disks[0].Category = storage.storageType + + for i, dataDisk := range dataDisks { + disks[i+1].Size = dataDisk.SizeGB + storage, err := self.zone.getStorageByCategory(dataDisk.StorageType) + if err != nil { + return "", fmt.Errorf("Storage %s not avaiable: %s", dataDisk.StorageType, err) + } + disks[i+1].Category = storage.storageType + } + + if len(instanceType) > 0 { + log.Debugf("Try instancetype : %s", instanceType) + vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, secgroupId, self.zone.ZoneId, desc, passwd, disks, vswitchId, ipAddr, keypair, userData, bc, projectId, osType, tags) + if err != nil { + log.Errorf("Failed for %s: %s", instanceType, err) + return "", fmt.Errorf("Failed to create specification %s.%s", instanceType, err.Error()) + } + return vmId, nil + } + + instanceTypes, err := self.zone.region.GetMatchInstanceTypes(cpu, memMB, 0, self.zone.ZoneId) + if err != nil { + return "", err + } + if len(instanceTypes) == 0 { + return "", fmt.Errorf("instance type %dC%dMB not avaiable", cpu, memMB) + } + + var vmId string + for _, instType := range instanceTypes { + instanceTypeId := instType.InstanceTypeId + log.Debugf("Try instancetype : %s", instanceTypeId) + vmId, err = self.zone.region.CreateInstance(name, imgId, instanceTypeId, secgroupId, self.zone.ZoneId, desc, passwd, disks, vswitchId, ipAddr, keypair, userData, bc, projectId, osType, tags) + if err != nil { + log.Errorf("Failed for %s: %s", instanceTypeId, err) + } else { + return vmId, nil + } + } + + return "", fmt.Errorf("Failed to create, %s", err.Error()) +} + +func (host *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (host *SHost) GetIsMaintenance() bool { + return false +} + +func (host *SHost) GetVersion() string { + return APSARA_API_VERSION +} diff --git a/pkg/multicloud/apsara/image.go b/pkg/multicloud/apsara/image.go new file mode 100644 index 0000000000..cc5e25101f --- /dev/null +++ b/pkg/multicloud/apsara/image.go @@ -0,0 +1,385 @@ +// 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 apsara + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + 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 ImageStatusType string + +const ( + ImageStatusCreating ImageStatusType = "Creating" + ImageStatusAvailable ImageStatusType = "Available" + ImageStatusUnAvailable ImageStatusType = "UnAvailable" + ImageStatusCreateFailed ImageStatusType = "CreateFailed" +) + +type ImageOwnerType string + +const ( + ImageOwnerSystem ImageOwnerType = "system" + ImageOwnerSelf ImageOwnerType = "self" + ImageOwnerOthers ImageOwnerType = "others" + ImageOwnerMarketplace ImageOwnerType = "marketplace" +) + +type ImageUsageType string + +const ( + ImageUsageInstance ImageUsageType = "instance" + ImageUsageNone ImageUsageType = "none" +) + +type SImage struct { + multicloud.SImageBase + storageCache *SStoragecache + + Architecture string + CreationTime time.Time + Description string + ImageId string + ImageName string + OSName string + OSType string + ImageOwnerAlias ImageOwnerType + IsSupportCloudinit bool + IsSupportIoOptimized bool + Platform string + Size int + Status ImageStatusType + Usage string +} + +func (self *SImage) GetMinRamSizeMb() int { + return 0 +} + +func (self *SImage) GetMetadata() *jsonutils.JSONDict { + data := jsonutils.NewDict() + if len(self.Architecture) > 0 { + data.Add(jsonutils.NewString(self.Architecture), "os_arch") + } + if len(self.OSType) > 0 { + data.Add(jsonutils.NewString(self.GetOsType()), "os_name") + } + if len(self.Platform) > 0 { + data.Add(jsonutils.NewString(self.Platform), "os_distribution") + } + if len(self.OSName) > 0 { + data.Add(jsonutils.NewString(self.OSName), "os_version") + } + return data +} + +func (self *SImage) GetId() string { + return self.ImageId +} + +func (self *SImage) GetName() string { + if self.ImageOwnerAlias == ImageOwnerSystem { + return self.OSName + } else { + return self.ImageName + } +} + +func (self *SImage) IsEmulated() bool { + return false +} + +func (self *SImage) Delete(ctx context.Context) error { + return self.storageCache.region.DeleteImage(self.ImageId) +} + +func (self *SImage) GetGlobalId() string { + return self.ImageId +} + +func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return self.storageCache +} + +func (self *SImage) GetStatus() string { + switch self.Status { + case ImageStatusCreating: + return api.CACHED_IMAGE_STATUS_SAVING + case ImageStatusAvailable: + return api.CACHED_IMAGE_STATUS_ACTIVE + case ImageStatusUnAvailable: + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + case ImageStatusCreateFailed: + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + default: + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (self *SImage) GetImageStatus() string { + switch self.Status { + case ImageStatusCreating: + return cloudprovider.IMAGE_STATUS_QUEUED + case ImageStatusAvailable: + return cloudprovider.IMAGE_STATUS_ACTIVE + case ImageStatusUnAvailable: + return cloudprovider.IMAGE_STATUS_DELETED + case ImageStatusCreateFailed: + return cloudprovider.IMAGE_STATUS_KILLED + default: + return cloudprovider.IMAGE_STATUS_KILLED + } +} + +func (self *SImage) Refresh() error { + new, err := self.storageCache.region.GetImage(self.ImageId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SImage) GetImageType() string { + switch self.ImageOwnerAlias { + case ImageOwnerSystem: + return cloudprovider.CachedImageTypeSystem + case ImageOwnerSelf: + return cloudprovider.CachedImageTypeCustomized + case ImageOwnerMarketplace: + return cloudprovider.CachedImageTypeMarket + case ImageOwnerOthers: + return cloudprovider.CachedImageTypeShared + default: + return cloudprovider.CachedImageTypeCustomized + } +} + +func (self *SImage) GetSizeByte() int64 { + return int64(self.Size) * 1024 * 1024 * 1024 +} + +func (self *SImage) GetOsType() string { + return utils.Capitalize(self.OSType) +} + +func (self *SImage) GetOsDist() string { + return self.Platform +} + +func (self *SImage) GetOsVersion() string { + return imagetools.NormalizeImageInfo(self.OSName, "", "", "", "").OsVersion +} + +func (self *SImage) GetOsArch() string { + return self.Architecture +} + +func (self *SImage) GetMinOsDiskSizeGb() int { + return 40 +} + +func (self *SImage) GetImageFormat() string { + return "vhd" +} + +func (self *SImage) GetCreatedAt() time.Time { + return self.CreationTime +} + +type ImageExportTask struct { + ImageId string + RegionId string + // RequestId string + TaskId string +} + +func (self *SRegion) ExportImage(imageId string, bucket *oss.Bucket) (*ImageExportTask, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["ImageId"] = imageId + params["OssBucket"] = bucket.BucketName + params["OssPrefix"] = fmt.Sprintf("%sexport", strings.Replace(imageId, "-", "", -1)) + + if body, err := self.ecsRequest("ExportImage", params); err != nil { + return nil, err + } else { + result := ImageExportTask{} + if err := body.Unmarshal(&result); err != nil { + log.Errorf("unmarshal result error %s", err) + return nil, err + } + return &result, nil + } +} + +// {"ImageId":"m-j6c1qlpa7oebbg1n2k60","RegionId":"cn-hongkong","RequestId":"F8B2F6A1-F6AA-4C92-A54C-C4A309CF811F","TaskId":"t-j6c1qlpa7oebbg1rcl9t"} + +type ImageImportTask struct { + ImageId string + RegionId string + // RequestId string + TaskId string +} + +func (self *SRegion) ImportImage(name string, osArch string, osType string, osDist string, bucket string, key string) (*ImageImportTask, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["ImageName"] = name + if osDist == "RHEL" { + osDist = "CentOS" + } + params["Platform"] = osDist // "Others Linux" + params["OSType"] = osType // "linux" + params["Architecture"] = osArch // "x86_64" + params["DiskDeviceMapping.1.OSSBucket"] = bucket + params["DiskDeviceMapping.1.OSSObject"] = key + + log.Debugf("Upload image with params %#v", params) + + body, err := self.ecsRequest("ImportImage", params) + if err != nil { + log.Errorf("ImportImage fail %s", err) + return nil, err + } + + log.Infof("%s", body) + result := ImageImportTask{} + err = body.Unmarshal(&result) + if err != nil { + log.Errorf("unmarshal result error %s", err) + return nil, err + } + + return &result, nil +} + +func (self *SRegion) GetImage(imageId string) (*SImage, error) { + images, _, err := self.GetImages("", "", []string{imageId}, "", 0, 1) + if err != nil { + return nil, err + } + if len(images) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &images[0], nil +} + +func (self *SRegion) GetImageByName(name string) (*SImage, error) { + images, _, err := self.GetImages("", "", nil, name, 0, 1) + if err != nil { + return nil, err + } + if len(images) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &images[0], nil +} + +func (self *SRegion) GetImagesBySnapshot(snapshotId string, offset int, limit int) ([]SImage, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + params["SnapshotId"] = snapshotId + + return self.getImages(params) +} + +func (self *SRegion) GetImageStatus(imageId string) (ImageStatusType, error) { + image, err := self.GetImage(imageId) + if err != nil { + return "", err + } + return image.Status, nil +} + +func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, imageId []string, name string, offset int, limit int) ([]SImage, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(status) > 0 { + params["Status"] = string(status) + } else { + params["Status"] = "Creating,Available,UnAvailable,CreateFailed" + } + if imageId != nil && len(imageId) > 0 { + params["ImageId"] = strings.Join(imageId, ",") + } + if len(owner) > 0 { + params["ImageOwnerAlias"] = string(owner) + } + + if len(name) > 0 { + params["ImageName"] = name + } + + return self.getImages(params) +} + +func (self *SRegion) getImages(params map[string]string) ([]SImage, int, error) { + body, err := self.ecsRequest("DescribeImages", params) + if err != nil { + log.Errorf("DescribeImages fail %s", err) + return nil, 0, err + } + + images := make([]SImage, 0) + err = body.Unmarshal(&images, "Images", "Image") + if err != nil { + log.Errorf("unmarshal images fail %s", err) + return nil, 0, nil + } + total, _ := body.Int("TotalCount") + return images, int(total), nil +} + +func (self *SRegion) DeleteImage(imageId string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["ImageId"] = imageId + params["Force"] = "true" + + _, err := self.ecsRequest("DeleteImage", params) + if err != nil { + log.Errorf("DeleteImage fail %s", err) + return err + } + return nil +} + +func (self *SImage) UEFI() bool { + return false +} diff --git a/pkg/multicloud/apsara/instance.go b/pkg/multicloud/apsara/instance.go new file mode 100644 index 0000000000..2b32661872 --- /dev/null +++ b/pkg/multicloud/apsara/instance.go @@ -0,0 +1,1081 @@ +// 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 apsara + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/osprofile" + "yunion.io/x/pkg/util/seclib" + "yunion.io/x/pkg/utils" + + 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" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +const ( + // Running:运行中 + //Starting:启动中 + //Stopping:停止中 + //Stopped:已停止 + + InstanceStatusStopped = "Stopped" + InstanceStatusRunning = "Running" + InstanceStatusStopping = "Stopping" + InstanceStatusStarting = "Starting" +) + +type SDedicatedHostAttribute struct { + DedicatedHostId string + DedicatedHostName string +} + +type SIpAddress struct { + IpAddress []string +} + +type SNetworkInterfaces struct { + NetworkInterface []SNetworkInterface +} + +type SOperationLocks struct { + LockReason []string +} + +type SSecurityGroupIds struct { + SecurityGroupId []string +} + +// {"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"} + +type SVpcAttributes struct { + NatIpAddress string + PrivateIpAddress SIpAddress + VSwitchId string + VpcId string +} + +type SInstance struct { + multicloud.SInstanceBase + + host *SHost + + // idisks []cloudprovider.ICloudDisk + + AutoReleaseTime string + ClusterId string + Cpu int + CreationTime time.Time + DedicatedHostAttribute SDedicatedHostAttribute + Description string + DeviceAvailable bool + EipAddress SEipAddress + ExpiredTime time.Time + GPUAmount int + GPUSpec string + HostName string + ImageId string + InnerIpAddress SIpAddress + InstanceChargeType TChargeType + InstanceId string + InstanceName string + InstanceNetworkType string + InstanceType string + InstanceTypeFamily string + InternetChargeType TInternetChargeType + InternetMaxBandwidthIn int + InternetMaxBandwidthOut int + IoOptimized bool + KeyPairName string + Memory int + NetworkInterfaces SNetworkInterfaces + OSName string + OSType string + OperationLocks SOperationLocks + PublicIpAddress SIpAddress + Recyclable bool + RegionId string + ResourceGroupId string + SaleCycle string + SecurityGroupIds SSecurityGroupIds + SerialNumber string + SpotPriceLimit string + SpotStrategy string + StartTime time.Time + Status string + StoppedMode string + VlanId string + VpcAttributes SVpcAttributes + ZoneId string +} + +// {"AutoReleaseTime":"","ClusterId":"","Cpu":1,"CreationTime":"2018-05-23T07:58Z","DedicatedHostAttribute":{"DedicatedHostId":"","DedicatedHostName":""},"Description":"","DeviceAvailable":true,"EipAddress":{"AllocationId":"","InternetChargeType":"","IpAddress":""},"ExpiredTime":"2018-05-30T16:00Z","GPUAmount":0,"GPUSpec":"","HostName":"iZ2ze57isp1ali72tzkjowZ","ImageId":"centos_7_04_64_20G_alibase_201701015.vhd","InnerIpAddress":{"IpAddress":[]},"InstanceChargeType":"PrePaid","InstanceId":"i-2ze57isp1ali72tzkjow","InstanceName":"gaoxianqi-test-7days","InstanceNetworkType":"vpc","InstanceType":"ecs.t5-lc2m1.nano","InstanceTypeFamily":"ecs.t5","InternetChargeType":"PayByBandwidth","InternetMaxBandwidthIn":-1,"InternetMaxBandwidthOut":0,"IoOptimized":true,"Memory":512,"NetworkInterfaces":{"NetworkInterface":[{"MacAddress":"00:16:3e:10:f0:c9","NetworkInterfaceId":"eni-2zecqsagtpztl6x5hu2r","PrimaryIpAddress":"192.168.220.214"}]},"OSName":"CentOS 7.4 64位","OSType":"linux","OperationLocks":{"LockReason":[]},"PublicIpAddress":{"IpAddress":[]},"Recyclable":false,"RegionId":"cn-beijing","ResourceGroupId":"","SaleCycle":"Week","SecurityGroupIds":{"SecurityGroupId":["sg-2zecqsagtpztl6x9zynl"]},"SerialNumber":"df05d9b4-df3d-4400-88d1-5f843f0dd088","SpotPriceLimit":0.000000,"SpotStrategy":"NoSpot","StartTime":"2018-05-23T07:58Z","Status":"Running","StoppedMode":"Not-applicable","VlanId":"","VpcAttributes":{"NatIpAddress":"","PrivateIpAddress":{"IpAddress":["192.168.220.214"]},"VSwitchId":"vsw-2ze9cqwza4upoyujq1thd","VpcId":"vpc-2zer4jy8ix3i8f0coc5uw"},"ZoneId":"cn-beijing-f"} + +func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit int) ([]SInstance, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(zoneId) > 0 { + params["ZoneId"] = zoneId + } + + if ids != nil && len(ids) > 0 { + params["InstanceIds"] = jsonutils.Marshal(ids).String() + } + + body, err := self.ecsRequest("DescribeInstances", params) + if err != nil { + log.Errorf("GetInstances fail %s", err) + return nil, 0, err + } + + instances := make([]SInstance, 0) + err = body.Unmarshal(&instances, "Instances", "Instance") + if err != nil { + log.Errorf("Unmarshal security group details fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return instances, int(total), nil +} + +func (self *SRegion) fetchTags(resourceType string, resourceId string) (*jsonutils.JSONDict, error) { + // 资源类型。取值范围: + // disk + // instance + // image + // securitygroup + // snapshot + var page int64 = 1 + var pageSize int64 = 50 + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["ResourceType"] = resourceType + params["ResourceId"] = resourceId + params["PageSize"] = fmt.Sprintf("%d", pageSize) + params["PageNumber"] = fmt.Sprintf("%d", page) + ret, err := self.ecsRequest("DescribeTags", params) + if err != nil { + return nil, err + } + + tags := jsonutils.NewDict() + result, _ := ret.GetArray("Tags", "Tag") + for _, item := range result { + k, _ := item.GetString("TagKey") + v, _ := item.Get("TagValue") + if len(k) > 0 { + tags.Set(k, v) + } + } + + total, _ := ret.Int("TotalCount") + for ; total > page*pageSize; page++ { + params["PageSize"] = fmt.Sprintf("%d", pageSize) + params["PageNumber"] = fmt.Sprintf("%d", page) + ret, err := self.ecsRequest("DescribeTags", params) + if err != nil { + return nil, err + } + + result, _ := ret.GetArray("Tags", "Tag") + for _, item := range result { + k, _ := item.GetString("TagKey") + v, _ := item.Get("TagValue") + if len(k) > 0 { + tags.Set(k, v) + } + } + } + + return tags, nil +} + +func (self *SInstance) GetSecurityGroupIds() ([]string, error) { + return self.SecurityGroupIds.SecurityGroupId, nil +} + +func (self *SInstance) GetMetadata() *jsonutils.JSONDict { + data := jsonutils.NewDict() + + // The pricingInfo key structure is 'RegionId::InstanceType::NetworkType::OSType::IoOptimized' + optimized := "optimized" + if !self.IoOptimized { + optimized = "none" + } + priceKey := fmt.Sprintf("%s::%s::%s::%s::%s", self.RegionId, self.InstanceType, self.InstanceNetworkType, self.OSType, optimized) + data.Add(jsonutils.NewString(priceKey), "price_key") + + tags, err := self.host.zone.region.fetchTags("instance", self.InstanceId) + if err != nil { + log.Errorln(err) + } + data.Update(tags) + + data.Add(jsonutils.NewString(self.host.zone.GetGlobalId()), "zone_ext_id") + if len(self.ImageId) > 0 { + if image, err := self.host.zone.region.GetImage(self.ImageId); err != nil { + log.Errorf("Failed to find image %s for instance %s", self.ImageId, self.GetName()) + } else if meta := image.GetMetadata(); meta != nil { + data.Update(meta) + } + } + return data +} + +func (self *SInstance) GetIHost() cloudprovider.ICloudHost { + return self.host +} + +func (self *SInstance) GetId() string { + return self.InstanceId +} + +func (self *SInstance) GetName() string { + if len(self.InstanceName) > 0 { + return self.InstanceName + } + return self.HostName +} + +func (self *SInstance) GetGlobalId() string { + return self.InstanceId +} + +func (self *SInstance) IsEmulated() bool { + return false +} + +func (self *SInstance) GetInstanceType() string { + return self.InstanceType +} + +func (self *SInstance) getVpc() (*SVpc, error) { + return self.host.zone.region.getVpc(self.VpcAttributes.VpcId) +} + +type byAttachedTime []SDisk + +func (a byAttachedTime) Len() int { return len(a) } +func (a byAttachedTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byAttachedTime) Less(i, j int) bool { + switch a[i].GetDiskType() { + case api.DISK_TYPE_SYS: + return true + case api.DISK_TYPE_SWAP: + switch a[j].GetDiskType() { + case api.DISK_TYPE_SYS: + return false + case api.DISK_TYPE_DATA: + return true + } + case api.DISK_TYPE_DATA: + if a[j].GetDiskType() != api.DISK_TYPE_DATA { + return false + } + } + return a[i].AttachedTime.Before(a[j].AttachedTime) +} + +func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + disks := []SDisk{} + for { + part, total, err := self.host.zone.region.GetDisks(self.InstanceId, "", "", nil, len(disks), 50) + if err != nil { + return nil, errors.Wrapf(err, "GetDisks for %s", self.InstanceId) + } + disks = append(disks, part...) + if len(disks) >= total { + break + } + } + + sort.Sort(byAttachedTime(disks)) + + log.Debugf("%s", jsonutils.Marshal(&disks)) + + idisks := make([]cloudprovider.ICloudDisk, len(disks)) + for i := 0; i < len(disks); i += 1 { + store, err := self.host.zone.getStorageByCategory(disks[i].Category) + if err != nil { + return nil, err + } + disks[i].storage = store + idisks[i] = &disks[i] + } + return idisks, nil +} + +func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { + nics := make([]cloudprovider.ICloudNic, 0) + for _, ip := range self.VpcAttributes.PrivateIpAddress.IpAddress { + nic := SInstanceNic{instance: self, ipAddr: ip} + nics = append(nics, &nic) + } + return nics, nil +} + +func (self *SInstance) GetVcpuCount() int { + return self.Cpu +} + +func (self *SInstance) GetVmemSizeMB() int { + return self.Memory +} + +func (self *SInstance) GetBootOrder() string { + return "dcn" +} + +func (self *SInstance) GetVga() string { + return "std" +} + +func (self *SInstance) GetVdi() string { + return "vnc" +} + +func (self *SInstance) GetOSType() string { + return osprofile.NormalizeOSType(self.OSType) +} + +func (self *SInstance) GetOSName() string { + return self.OSName +} + +func (self *SInstance) GetBios() string { + return "BIOS" +} + +func (self *SInstance) GetMachine() string { + return "pc" +} + +func (self *SInstance) GetStatus() string { + // Running:运行中 + //Starting:启动中 + //Stopping:停止中 + //Stopped:已停止 + switch self.Status { + case InstanceStatusRunning: + return api.VM_RUNNING + case InstanceStatusStarting: + return api.VM_STARTING + case InstanceStatusStopping: + return api.VM_STOPPING + case InstanceStatusStopped: + return api.VM_READY + default: + return api.VM_UNKNOWN + } +} + +func (self *SInstance) Refresh() error { + new, err := self.host.zone.region.GetInstance(self.InstanceId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +/* +func (self *SInstance) GetRemoteStatus() string { + // Running:运行中 + //Starting:启动中 + //Stopping:停止中 + //Stopped:已停止 + switch self.Status { + case InstanceStatusRunning: + return cloudprovider.CloudVMStatusRunning + case InstanceStatusStarting: + return cloudprovider.CloudVMStatusStopped + case InstanceStatusStopping: + return cloudprovider.CloudVMStatusRunning + case InstanceStatusStopped: + return cloudprovider.CloudVMStatusStopped + default: + return cloudprovider.CloudVMStatusOther + } +} +*/ + +func (self *SInstance) GetHypervisor() string { + return api.HYPERVISOR_APSARA +} + +func (self *SInstance) StartVM(ctx context.Context) error { + timeout := 300 * time.Second + interval := 15 * time.Second + + startTime := time.Now() + for time.Now().Sub(startTime) < timeout { + err := self.Refresh() + if err != nil { + return err + } + log.Debugf("status %s expect %s", self.GetStatus(), api.VM_RUNNING) + if self.GetStatus() == api.VM_RUNNING { + return nil + } else if self.GetStatus() == api.VM_READY { + err := self.host.zone.region.StartVM(self.InstanceId) + if err != nil { + return err + } + } + time.Sleep(interval) + } + return cloudprovider.ErrTimeout +} + +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce) + if err != nil { + return err + } + return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues +} + +func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { + url, err := self.host.zone.region.GetInstanceVNCUrl(self.InstanceId) + if err != nil { + return nil, err + } + passwd := seclib.RandomPassword(6) + err = self.host.zone.region.ModifyInstanceVNCUrlPassword(self.InstanceId, passwd) + if err != nil { + return nil, err + } + ret := jsonutils.NewDict() + ret.Add(jsonutils.NewString(url), "url") + ret.Add(jsonutils.NewString(passwd), "password") + ret.Add(jsonutils.NewString("apsara"), "protocol") + ret.Add(jsonutils.NewString(self.InstanceId), "instance_id") + return ret, nil +} + +func (self *SInstance) UpdateVM(ctx context.Context, name string) error { + return self.host.zone.region.UpdateVM(self.InstanceId, name, self.OSType) +} + +func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { + var keypairName string + if len(publicKey) > 0 { + var err error + keypairName, err = self.host.zone.region.syncKeypair(publicKey) + if err != nil { + return err + } + } + + return self.host.zone.region.DeployVM(self.InstanceId, name, password, keypairName, deleteKeypair, description) +} + +func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { + keypair := "" + if len(desc.PublicKey) > 0 { + var err error + keypair, err = self.host.zone.region.syncKeypair(desc.PublicKey) + if err != nil { + return "", err + } + } + diskId, err := self.host.zone.region.ReplaceSystemDisk(self.InstanceId, desc.ImageId, desc.Password, keypair, desc.SysSizeGB) + if err != nil { + return "", err + } + + return diskId, nil +} + +func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { + if len(config.InstanceType) > 0 { + return self.host.zone.region.ChangeVMConfig2(self.ZoneId, self.InstanceId, config.InstanceType, nil) + } + return self.host.zone.region.ChangeVMConfig(self.ZoneId, self.InstanceId, config.Cpu, config.MemoryMB, nil) +} + +func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { + return self.host.zone.region.AttachDisk(self.InstanceId, diskId) +} + +func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { + return cloudprovider.RetryOnError( + func() error { + return self.host.zone.region.DetachDisk(self.InstanceId, diskId) + }, + []string{ + `"Code":"InvalidOperation.Conflict"`, + }, + 4) +} + +func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { + instances, _, err := self.GetInstances("", []string{instanceId}, 0, 1) + if err != nil { + return nil, err + } + if len(instances) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &instances[0], nil +} + +func (self *SRegion) CreateInstance(name string, imageId string, instanceType string, securityGroupId string, + zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string, + keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string, + tags map[string]string, +) (string, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["ImageId"] = imageId + params["InstanceType"] = instanceType + params["SecurityGroupId"] = securityGroupId + params["ZoneId"] = zoneId + params["InstanceName"] = name + params["Description"] = desc + params["InternetChargeType"] = "PayByTraffic" + params["InternetMaxBandwidthIn"] = "200" + params["InternetMaxBandwidthOut"] = "100" + params["HostName"] = stringutils2.GenerateHostName(name, osType) + if len(passwd) > 0 { + params["Password"] = passwd + } else { + params["PasswordInherit"] = "True" + } + + if len(projectId) > 0 { + params["ResourceGroupId"] = projectId + } + //{"Code":"InvalidSystemDiskCategory.ValueNotSupported","HostId":"ecs.apsaracs.com","Message":"The specified parameter 'SystemDisk.Category' is not support IoOptimized Instance. Valid Values: cloud_efficiency;cloud_ssd. ","RequestId":"9C9A4E99-5196-42A2-80B6-4762F8F75C90"} + params["IoOptimized"] = "optimized" + for i, d := range disks { + if i == 0 { + params["SystemDisk.Category"] = d.Category + if d.Category == api.STORAGE_CLOUD_ESSD_PL2 { + params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD + params["SystemDisk.PerformanceLevel"] = "PL2" + } + if d.Category == api.STORAGE_CLOUD_ESSD_PL3 { + params["SystemDisk.Category"] = api.STORAGE_CLOUD_ESSD + params["SystemDisk.PerformanceLevel"] = "PL3" + } + params["SystemDisk.Size"] = fmt.Sprintf("%d", d.Size) + params["SystemDisk.DiskName"] = d.GetName() + params["SystemDisk.Description"] = d.Description + } else { + params[fmt.Sprintf("DataDisk.%d.Size", i)] = fmt.Sprintf("%d", d.Size) + params[fmt.Sprintf("DataDisk.%d.Category", i)] = d.Category + if d.Category == api.STORAGE_CLOUD_ESSD_PL2 { + params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD + params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL2" + } + if d.Category == api.STORAGE_CLOUD_ESSD_PL3 { + params[fmt.Sprintf("DataDisk.%d.Category", i)] = api.STORAGE_CLOUD_ESSD + params[fmt.Sprintf("DataDisk.%d..PerformanceLevel", i)] = "PL3" + } + params[fmt.Sprintf("DataDisk.%d.DiskName", i)] = d.GetName() + params[fmt.Sprintf("DataDisk.%d.Description", i)] = d.Description + params[fmt.Sprintf("DataDisk.%d.Encrypted", i)] = "false" + } + } + params["VSwitchId"] = vSwitchId + params["PrivateIpAddress"] = ipAddr + + if len(keypair) > 0 { + params["KeyPairName"] = keypair + } + + if len(userData) > 0 { + params["UserData"] = userData + } + + if len(tags) > 0 { + tagIdx := 0 + for k, v := range tags { + params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k + params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v + tagIdx += 1 + } + } + + if bc != nil { + params["InstanceChargeType"] = "PrePaid" + err := billingCycle2Params(bc, params) + if err != nil { + return "", err + } + if bc.AutoRenew { + params["AutoRenew"] = "true" + params["AutoRenewPeriod"] = "1" + } else { + params["AutoRenew"] = "False" + } + } else { + params["InstanceChargeType"] = "PostPaid" + params["SpotStrategy"] = "NoSpot" + } + + params["ClientToken"] = utils.GenRequestId(20) + + body, err := self.ecsRequest("CreateInstance", params) + if err != nil { + log.Errorf("CreateInstance fail %s", err) + return "", err + } + instanceId, _ := body.GetString("InstanceId") + return instanceId, nil +} + +func (self *SRegion) doStartVM(instanceId string) error { + return self.instanceOperation(instanceId, "StartInstance", nil) +} + +func (self *SRegion) doStopVM(instanceId string, isForce bool) error { + params := make(map[string]string) + if isForce { + params["ForceStop"] = "true" + } else { + params["ForceStop"] = "false" + } + params["StoppedMode"] = "KeepCharging" + return self.instanceOperation(instanceId, "StopInstance", params) +} + +func (self *SRegion) doDeleteVM(instanceId string) error { + params := make(map[string]string) + params["TerminateSubscription"] = "true" // terminate expired prepaid instance + params["Force"] = "true" + return self.instanceOperation(instanceId, "DeleteInstance", params) +} + +/*func (self *SRegion) waitInstanceStatus(instanceId string, target string, interval time.Duration, timeout time.Duration) error { + startTime := time.Now() + for time.Now().Sub(startTime) < timeout { + status, err := self.GetInstanceStatus(instanceId) + if err != nil { + return err + } + if status == target { + return nil + } + time.Sleep(interval) + } + return cloudprovider.ErrTimeout +} + +func (self *SInstance) waitStatus(target string, interval time.Duration, timeout time.Duration) error { + return self.host.zone.region.waitInstanceStatus(self.InstanceId, target, interval, timeout) +}*/ + +func (self *SRegion) StartVM(instanceId string) error { + status, err := self.GetInstanceStatus(instanceId) + if err != nil { + log.Errorf("Fail to get instance status on StartVM: %s", err) + return err + } + if status != InstanceStatusStopped { + log.Errorf("StartVM: vm status is %s expect %s", status, InstanceStatusStopped) + return cloudprovider.ErrInvalidStatus + } + return self.doStartVM(instanceId) + // if err != nil { + // return err + // } + // return self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*5, time.Second*180) // 3 minutes to timeout +} + +func (self *SRegion) StopVM(instanceId string, isForce bool) error { + status, err := self.GetInstanceStatus(instanceId) + if err != nil { + log.Errorf("Fail to get instance status on StopVM: %s", err) + return err + } + if status == InstanceStatusStopped { + return nil + } + if status != InstanceStatusRunning { + log.Errorf("StopVM: vm status is %s expect %s", status, InstanceStatusRunning) + return cloudprovider.ErrInvalidStatus + } + return self.doStopVM(instanceId, isForce) + // if err != nil { + // return err + // } + // return self.waitInstanceStatus(instanceId, InstanceStatusStopped, time.Second*10, time.Second*300) // 5 minutes to timeout +} + +func (self *SRegion) DeleteVM(instanceId string) error { + status, err := self.GetInstanceStatus(instanceId) + if err != nil { + log.Errorf("Fail to get instance status on DeleteVM: %s", err) + return err + } + log.Debugf("Instance status on delete is %s", status) + if status != InstanceStatusStopped { + log.Warningf("DeleteVM: vm status is %s expect %s", status, InstanceStatusStopped) + } + return self.doDeleteVM(instanceId) + // if err != nil { + // return err + // } + // err = self.waitInstanceStatus(instanceId, InstanceStatusRunning, time.Second*10, time.Second*300) // 5 minutes to timeout + // if err == cloudprovider.ErrNotFound { + // return nil + // } else if err == nil { + // return cloudprovider.ErrTimeout + // } else { + // return err + // } +} + +func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error { + instance, err := self.GetInstance(instanceId) + if err != nil { + return err + } + + // 修改密钥时直接返回 + if deleteKeypair { + err = self.DetachKeyPair(instanceId, instance.KeyPairName) + if err != nil { + return err + } + } + + if len(keypairName) > 0 { + err = self.AttachKeypair(instanceId, keypairName) + if err != nil { + return err + } + } + + params := make(map[string]string) + + // if resetPassword { + // params["Password"] = seclib2.RandomPassword2(12) + // } + // 指定密码的情况下,使用指定的密码 + if len(password) > 0 { + params["Password"] = password + } + + if len(name) > 0 && instance.InstanceName != name { + params["InstanceName"] = name + params["HostName"] = stringutils2.GenerateHostName(name, instance.OSType) + } + + if len(description) > 0 && instance.Description != description { + params["Description"] = description + } + + if len(params) > 0 { + return self.modifyInstanceAttribute(instanceId, params) + } else { + return nil + } +} + +func (self *SInstance) DeleteVM(ctx context.Context) error { + for { + err := self.host.zone.region.DeleteVM(self.InstanceId) + if err != nil { + if isError(err, "IncorrectInstanceStatus.Initializing") { + log.Infof("The instance is initializing, try later ...") + time.Sleep(10 * time.Second) + } else { + log.Errorf("DeleteVM fail: %s", err) + return err + } + } else { + break + } + } + return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes +} + +func (self *SRegion) UpdateVM(instanceId string, name, osType string) error { + /* + api: ModifyInstanceAttribute + https://help.apsara.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW + */ + params := make(map[string]string) + params["HostName"] = stringutils2.GenerateHostName(name, osType) + params["InstanceName"] = name + return self.modifyInstanceAttribute(instanceId, params) +} + +func (self *SRegion) modifyInstanceAttribute(instanceId string, params map[string]string) error { + return self.instanceOperation(instanceId, "ModifyInstanceAttribute", params) +} + +func (self *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) (string, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + params["ImageId"] = imageId + if len(passwd) > 0 { + params["Password"] = passwd + } else { + params["PasswordInherit"] = "True" + } + if len(keypairName) > 0 { + params["KeyPairName"] = keypairName + } + if sysDiskSizeGB > 0 { + params["SystemDisk.Size"] = fmt.Sprintf("%d", sysDiskSizeGB) + } + body, err := self.ecsRequest("ReplaceSystemDisk", params) + if err != nil { + return "", err + } + // log.Debugf("%s", body.String()) + return body.GetString("DiskId") +} + +func (self *SRegion) ChangeVMConfig(zoneId string, instanceId string, ncpu int, vmem int, disks []*SDisk) error { + // todo: support change disk config? + params := make(map[string]string) + instanceTypes, e := self.GetMatchInstanceTypes(ncpu, vmem, 0, zoneId) + if e != nil { + return e + } + + for _, instancetype := range instanceTypes { + params["InstanceType"] = instancetype.InstanceTypeId + params["ClientToken"] = utils.GenRequestId(20) + if err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params); err != nil { + log.Errorf("Failed for %s: %s", instancetype.InstanceTypeId, err) + } else { + return nil + } + } + + return fmt.Errorf("Failed to change vm config, specification not supported") +} + +func (self *SRegion) ChangeVMConfig2(zoneId string, instanceId string, instanceType string, disks []*SDisk) error { + // todo: support change disk config? + params := make(map[string]string) + params["InstanceType"] = instanceType + params["ClientToken"] = utils.GenRequestId(20) + if err := self.instanceOperation(instanceId, "ModifyInstanceSpec", params); err != nil { + log.Errorf("Failed for %s: %s", instanceType, err) + return fmt.Errorf("Failed to change vm config, specification not supported") + } else { + return nil + } +} + +func (self *SRegion) DetachDisk(instanceId string, diskId string) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["DiskId"] = diskId + log.Infof("Detach instance %s disk %s", instanceId, diskId) + _, err := self.ecsRequest("DetachDisk", params) + if err != nil { + if strings.Contains(err.Error(), "The specified disk has not been attached on the specified instance") { + return nil + } + return errors.Wrap(err, "DetachDisk") + } + + return nil +} + +func (self *SRegion) AttachDisk(instanceId string, diskId string) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["DiskId"] = diskId + _, err := self.ecsRequest("AttachDisk", params) + if err != nil { + log.Errorf("AttachDisk %s to %s fail %s", diskId, instanceId, err) + return err + } + + return nil +} + +func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { + if len(self.EipAddress.IpAddress) > 0 { + return self.host.zone.region.GetEip(self.EipAddress.AllocationId) + } + if len(self.PublicIpAddress.IpAddress) > 0 { + eip := SEipAddress{} + eip.region = self.host.zone.region + eip.IpAddress = self.PublicIpAddress.IpAddress[0] + eip.InstanceId = self.InstanceId + eip.InstanceType = EIP_INSTANCE_TYPE_ECS + eip.Status = EIP_STATUS_INUSE + eip.AllocationId = self.InstanceId // fixed + eip.AllocationTime = self.CreationTime + eip.Bandwidth = self.InternetMaxBandwidthOut + eip.InternetChargeType = self.InternetChargeType + return &eip, nil + } + return nil, nil +} + +func (self *SInstance) AssignSecurityGroup(secgroupId string) error { + return self.host.zone.region.AssignSecurityGroup(secgroupId, self.InstanceId) +} + +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { + return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId) +} + +func (self *SInstance) GetBillingType() string { + return convertChargeType(self.InstanceChargeType) +} + +func (self *SInstance) GetCreatedAt() time.Time { + return self.CreationTime +} + +func (self *SInstance) GetExpiredAt() time.Time { + return convertExpiredAt(self.ExpiredTime) +} + +func (self *SInstance) UpdateUserData(userData string) error { + return self.host.zone.region.updateInstance(self.InstanceId, "", "", "", "", userData) +} + +func (self *SInstance) CreateDisk(ctx context.Context, sizeMb int, uuid string, driver string) error { + return cloudprovider.ErrNotSupported +} + +func (self *SInstance) Renew(bc billing.SBillingCycle) error { + return self.host.zone.region.RenewInstance(self.InstanceId, bc) +} + +func billingCycle2Params(bc *billing.SBillingCycle, params map[string]string) error { + if bc.GetMonths() > 0 { + params["PeriodUnit"] = "Month" + params["Period"] = fmt.Sprintf("%d", bc.GetMonths()) + } else if bc.GetWeeks() > 0 { + params["PeriodUnit"] = "Week" + params["Period"] = fmt.Sprintf("%d", bc.GetWeeks()) + } else { + return fmt.Errorf("invalid renew time period %s", bc.String()) + } + return nil +} + +func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + err := billingCycle2Params(&bc, params) + if err != nil { + return err + } + params["ClientToken"] = utils.GenRequestId(20) + _, err = region.ecsRequest("RenewInstance", params) + if err != nil { + log.Errorf("RenewInstance fail %s", err) + return err + } + return nil +} + +func (self *SInstance) GetProjectId() string { + return self.ResourceGroupId +} + +func (self *SInstance) GetError() error { + return nil +} + +func (region *SRegion) ConvertPublicIpToEip(instanceId string) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["RegionId"] = region.RegionId + _, err := region.ecsRequest("ConvertNatPublicIpToEip", params) + return err +} + +func (self *SInstance) ConvertPublicIpToEip() error { + return self.host.zone.region.ConvertPublicIpToEip(self.InstanceId) +} + +func (region *SRegion) SetInstanceAutoRenew(instanceId string, autoRenew bool) error { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["RegionId"] = region.RegionId + if autoRenew { + params["RenewalStatus"] = "AutoRenewal" + params["Duration"] = "1" + } else { + params["RenewalStatus"] = "Normal" + } + _, err := region.ecsRequest("ModifyInstanceAutoRenewAttribute", params) + return err +} + +type SAutoRenewAttr struct { + Duration int + AutoRenewEnabled bool + RenewalStatus string + PeriodUnit string +} + +func (region *SRegion) GetInstanceAutoRenewAttribute(instanceId string) (*SAutoRenewAttr, error) { + params := make(map[string]string) + params["InstanceId"] = instanceId + params["RegionId"] = region.RegionId + resp, err := region.ecsRequest("DescribeInstanceAutoRenewAttribute", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeInstanceAutoRenewAttribute") + } + attr := []SAutoRenewAttr{} + err = resp.Unmarshal(&attr, "InstanceRenewAttributes", "InstanceRenewAttribute") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + if len(attr) == 1 { + return &attr[0], nil + } + return nil, fmt.Errorf("get %d auto renew info", len(attr)) +} + +func (self *SInstance) IsAutoRenew() bool { + attr, err := self.host.zone.region.GetInstanceAutoRenewAttribute(self.InstanceId) + if err != nil { + log.Errorf("failed to get instance %s auto renew info", self.InstanceId) + return false + } + return attr.AutoRenewEnabled +} + +func (self *SInstance) SetAutoRenew(autoRenew bool) error { + return self.host.zone.region.SetInstanceAutoRenew(self.InstanceId, autoRenew) +} + +func (self *SInstance) SetMetadata(tags map[string]string, replace bool) error { + return self.host.zone.region.SetResourceTags("ecs", "instance", []string{self.InstanceId}, tags, replace) +} diff --git a/pkg/multicloud/apsara/instancenic.go b/pkg/multicloud/apsara/instancenic.go new file mode 100644 index 0000000000..539a3da126 --- /dev/null +++ b/pkg/multicloud/apsara/instancenic.go @@ -0,0 +1,59 @@ +// 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 apsara + +import ( + "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SInstanceNic struct { + instance *SInstance + ipAddr string +} + +func (self *SInstanceNic) GetIP() string { + return self.ipAddr +} + +func (self *SInstanceNic) GetMAC() string { + ip, _ := netutils.NewIPV4Addr(self.ipAddr) + return ip.ToMac("00:16:") +} + +func (self *SInstanceNic) InClassicNetwork() bool { + return false +} + +func (self *SInstanceNic) GetDriver() string { + return "virtio" +} + +func (self *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork { + vswitchId := self.instance.VpcAttributes.VSwitchId + wires, err := self.instance.host.GetIWires() + if err != nil { + return nil + } + for i := 0; i < len(wires); i += 1 { + wire := wires[i].(*SWire) + net := wire.getNetworkById(vswitchId) + if net != nil { + return net + } + } + return nil +} diff --git a/pkg/multicloud/apsara/instancetype.go b/pkg/multicloud/apsara/instancetype.go new file mode 100644 index 0000000000..bcf170f364 --- /dev/null +++ b/pkg/multicloud/apsara/instancetype.go @@ -0,0 +1,65 @@ +// 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 apsara + +import ( + // "time" + "yunion.io/x/log" +) + +// {"CpuCoreCount":1,"EniQuantity":1,"GPUAmount":0,"GPUSpec":"","InstanceTypeFamily":"ecs.t1","InstanceTypeId":"ecs.t1.xsmall","LocalStorageCategory":"","MemorySize":0.500000} +// InstanceBandwidthRx":26214400,"InstanceBandwidthTx":26214400,"InstancePpsRx":4500000,"InstancePpsTx":4500000 + +type SInstanceType struct { + BaselineCredit int + CpuCoreCount int + MemorySize float32 + EniQuantity int // 实例规格支持网卡数量 + GPUAmount int + GPUSpec string + InstanceTypeFamily string + InstanceFamilyLevel string + InstanceTypeId string + LocalStorageCategory string + LocalStorageAmount int + LocalStorageCapacity int64 + InstanceBandwidthRx int + InstanceBandwidthTx int + InstancePpsRx int + InstancePpsTx int +} + +func (self *SRegion) GetInstanceTypes() ([]SInstanceType, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + + body, err := self.ecsRequest("DescribeInstanceTypes", params) + if err != nil { + log.Errorf("GetInstanceTypes fail %s", err) + return nil, err + } + + instanceTypes := make([]SInstanceType, 0) + err = body.Unmarshal(&instanceTypes, "InstanceTypes", "InstanceType") + if err != nil { + log.Errorf("Unmarshal instance type details fail %s", err) + return nil, err + } + return instanceTypes, nil +} + +func (self *SInstanceType) memoryMB() int { + return int(self.MemorySize * 1024) +} diff --git a/pkg/multicloud/apsara/keypair.go b/pkg/multicloud/apsara/keypair.go new file mode 100644 index 0000000000..66043b577c --- /dev/null +++ b/pkg/multicloud/apsara/keypair.go @@ -0,0 +1,153 @@ +// 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 apsara + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/aokoli/goutils" + "golang.org/x/crypto/ssh" + + "yunion.io/x/log" +) + +type SKeypair struct { + KeyPairFingerPrint string + KeyPairName string +} + +func (self *SRegion) GetKeypairs(finger string, name string, offset int, limit int) ([]SKeypair, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if len(finger) > 0 { + params["KeyPairFingerPrint"] = finger + } + if len(name) > 0 { + params["KeyPairName"] = name + } + + body, err := self.ecsRequest("DescribeKeyPairs", params) + if err != nil { + log.Errorf("GetKeypairs fail %s", err) + return nil, 0, err + } + + keypairs := make([]SKeypair, 0) + err = body.Unmarshal(&keypairs, "KeyPairs", "KeyPair") + if err != nil { + log.Errorf("Unmarshal keypair fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return keypairs, int(total), nil +} + +func (self *SRegion) ImportKeypair(name string, pubKey string) (*SKeypair, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PublicKeyBody"] = pubKey + params["KeyPairName"] = name + + body, err := self.ecsRequest("ImportKeyPair", params) + if err != nil { + log.Errorf("ImportKeypair fail %s", err) + return nil, err + } + + log.Debugf("%s", body) + keypair := SKeypair{} + err = body.Unmarshal(&keypair) + if err != nil { + log.Errorf("Unmarshall keypair fail %s", err) + return nil, err + } + return &keypair, nil +} + +func (self *SRegion) AttachKeypair(instanceId string, name string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["KeyPairName"] = name + instances, _ := json.Marshal(&[...]string{instanceId}) + params["InstanceIds"] = string(instances) + _, err := self.ecsRequest("AttachKeyPair", params) + if err != nil { + log.Errorf("AttachKeyPair fail %s", err) + return err + } + + return nil +} + +func (self *SRegion) DetachKeyPair(instanceId string, name string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["KeyPairName"] = name + instances, _ := json.Marshal(&[...]string{instanceId}) + params["InstanceIds"] = string(instances) + _, err := self.ecsRequest("DetachKeyPair", params) + if err != nil { + log.Errorf("DetachKeyPair fail %s", err) + return err + } + + return nil +} + +func (self *SRegion) lookUpApsaraKeypair(publicKey string) (string, error) { + pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey)) + if err != nil { + return "", fmt.Errorf("publicKey error %s", err) + } + + fingerprint := strings.Replace(ssh.FingerprintLegacyMD5(pk), ":", "", -1) + ks, total, err := self.GetKeypairs(fingerprint, "*", 0, 1) + if total < 1 { + return "", fmt.Errorf("keypair not found %s", err) + } else { + return ks[0].KeyPairName, nil + } +} + +func (self *SRegion) importApsaraKeypair(publicKey string) (string, error) { + prefix, e := goutils.RandomAlphabetic(6) + if e != nil { + return "", fmt.Errorf("publicKey error %s", e) + } + + name := prefix + strconv.FormatInt(time.Now().Unix(), 10) + if k, e := self.ImportKeypair(name, publicKey); e != nil { + return "", fmt.Errorf("keypair import error %s", e) + } else { + return k.KeyPairName, nil + } +} + +func (self *SRegion) syncKeypair(publicKey string) (string, error) { + name, e := self.lookUpApsaraKeypair(publicKey) + if e == nil { + return name, nil + } + return self.importApsaraKeypair(publicKey) +} diff --git a/pkg/multicloud/apsara/loadbalancer.go b/pkg/multicloud/apsara/loadbalancer.go new file mode 100644 index 0000000000..bbea5d0bb4 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancer.go @@ -0,0 +1,416 @@ +// 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 apsara + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type ListenerProtocol string + +const ( + ListenerProtocolTCP ListenerProtocol = "tcp" + ListenerProtocolUDP ListenerProtocol = "udp" + ListenerProtocolHTTP ListenerProtocol = "http" + ListenerProtocolHTTPS ListenerProtocol = "https" +) + +type ListenerPorts struct { + ListenerPort []int +} + +type ListenerPortsAndProtocol struct { + ListenerPortAndProtocol []ListenerPortAndProtocol +} + +type ListenerPortAndProtocol struct { + Description string + ListenerPort int + ListenerProtocol ListenerProtocol +} + +type BackendServers struct { + BackendServer []SLoadbalancerDefaultBackend +} + +type SLoadbalancer struct { + multicloud.SLoadbalancerBase + region *SRegion + + LoadBalancerId string //负载均衡实例ID。 + LoadBalancerName string //负载均衡实例的名称。 + LoadBalancerStatus string //负载均衡实例状态:inactive: 此状态的实例监听不会再转发流量。active: 实例创建后,默认状态为active。 locked: 实例已经被锁定。 + Address string //负载均衡实例的服务地址。 + RegionId string //负载均衡实例的地域ID。 + RegionIdAlias string //负载均衡实例的地域名称。 + AddressType string //负载均衡实例的网络类型。 + VSwitchId string //私网负载均衡实例的交换机ID。 + VpcId string //私网负载均衡实例的专有网络ID。 + NetworkType string //私网负载均衡实例的网络类型:vpc:专有网络实例 classic:经典网络实例 + ListenerPorts ListenerPorts + ListenerPortsAndProtocol ListenerPortsAndProtocol + BackendServers BackendServers + CreateTime time.Time //负载均衡实例的创建时间。 + MasterZoneId string //实例的主可用区ID。 + SlaveZoneId string //实例的备可用区ID。 + InternetChargeType TInternetChargeType //公网实例的计费方式。取值:paybybandwidth:按带宽计费 paybytraffic:按流量计费(默认值) 说明 当 PayType参数的值为PrePay时,只支持按带宽计费。 + PayType string //实例的计费类型,取值:PayOnDemand:按量付费 PrePay:预付费 + ResourceGroupId string //企业资源组ID。 + LoadBalancerSpec string //负载均衡实例的的性能规格 + Bandwidth int //按带宽计费的公网型实例的带宽峰值 +} + +func (lb *SLoadbalancer) GetName() string { + return lb.LoadBalancerName +} + +func (lb *SLoadbalancer) GetId() string { + return lb.LoadBalancerId +} + +func (lb *SLoadbalancer) GetGlobalId() string { + return lb.LoadBalancerId +} + +func (lb *SLoadbalancer) GetStatus() string { + if lb.LoadBalancerStatus == "active" { + return api.LB_STATUS_ENABLED + } + return api.LB_STATUS_DISABLED +} + +func (lb *SLoadbalancer) GetMetadata() *jsonutils.JSONDict { + data := jsonutils.NewDict() + tags, err := lb.region.ListResourceTags("slb", "instance", []string{lb.GetId()}) + if err != nil { + log.Errorf(`[err:%s]lb.region.FetchResourceTags("slb", "instance", []string{lb.GetId()})`, err.Error()) + return nil + } + if _, ok := tags[lb.GetId()]; !ok { + return nil + } + data.Update(jsonutils.Marshal(tags[lb.GetId()])) + return data +} + +func (lb *SLoadbalancer) GetAddress() string { + return lb.Address +} + +func (lb *SLoadbalancer) GetAddressType() string { + return lb.AddressType +} + +func (lb *SLoadbalancer) GetNetworkType() string { + return lb.NetworkType +} + +func (lb *SLoadbalancer) GetNetworkIds() []string { + return []string{lb.VSwitchId} +} + +func (lb *SLoadbalancer) GetZoneId() string { + zone, err := lb.region.getZoneById(lb.MasterZoneId) + if err != nil { + log.Errorf("failed to find zone for lb %s error: %v", lb.LoadBalancerName, err) + return "" + } + return zone.GetGlobalId() +} + +func (self *SLoadbalancer) GetZone1Id() string { + return "" +} + +func (lb *SLoadbalancer) IsEmulated() bool { + return false +} + +func (lb *SLoadbalancer) GetVpcId() string { + return lb.VpcId +} + +func (lb *SLoadbalancer) Refresh() error { + loadbalancer, err := lb.region.GetLoadbalancerDetail(lb.LoadBalancerId) + if err != nil { + return err + } + return jsonutils.Update(lb, loadbalancer) +} + +func (region *SRegion) GetLoadbalancers(ids []string) ([]SLoadbalancer, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + if ids != nil && len(ids) > 0 { + params["LoadBalancerId"] = strings.Join(ids, ",") + } + body, err := region.lbRequest("DescribeLoadBalancers", params) + if err != nil { + return nil, err + } + lbs := []SLoadbalancer{} + return lbs, body.Unmarshal(&lbs, "LoadBalancers", "LoadBalancer") +} + +func (region *SRegion) GetLoadbalancerDetail(loadbalancerId string) (*SLoadbalancer, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + body, err := region.lbRequest("DescribeLoadBalancerAttribute", params) + if err != nil { + return nil, err + } + lb := SLoadbalancer{region: region} + return &lb, body.Unmarshal(&lb) +} + +func (lb *SLoadbalancer) Delete(ctx context.Context) error { + params := map[string]string{} + params["RegionId"] = lb.region.RegionId + params["LoadBalancerId"] = lb.LoadBalancerId + _, err := lb.region.lbRequest("DeleteLoadBalancer", params) + return err +} + +func (lb *SLoadbalancer) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { + ibackendgroups := []cloudprovider.ICloudLoadbalancerBackendGroup{} + { + backendgroups, err := lb.region.GetLoadbalancerBackendgroups(lb.LoadBalancerId) + if err != nil { + return nil, err + } + + for i := 0; i < len(backendgroups); i++ { + backendgroups[i].lb = lb + ibackendgroups = append(ibackendgroups, &backendgroups[i]) + } + } + { + iDefaultBackendgroup := SLoadbalancerDefaultBackendGroup{lb: lb} + ibackendgroups = append(ibackendgroups, &iDefaultBackendgroup) + + } + { + backendgroups, err := lb.region.GetLoadbalancerMasterSlaveBackendgroups(lb.LoadBalancerId) + if err != nil { + return nil, err + } + for i := 0; i < len(backendgroups); i++ { + backendgroups[i].lb = lb + ibackendgroups = append(ibackendgroups, &backendgroups[i]) + } + } + return ibackendgroups, nil +} + +func (lb *SLoadbalancer) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + switch group.GroupType { + case api.LB_BACKENDGROUP_TYPE_NORMAL: + group, err := lb.region.CreateLoadbalancerBackendGroup(group.Name, lb.LoadBalancerId, group.Backends) + if err != nil { + return nil, err + } + group.lb = lb + return group, nil + case api.LB_BACKENDGROUP_TYPE_MASTER_SLAVE: + group, err := lb.region.CreateLoadbalancerMasterSlaveBackendGroup(group.Name, lb.LoadBalancerId, group.Backends) + if err != nil { + return nil, err + } + group.lb = lb + return group, nil + default: + return nil, fmt.Errorf("Unsupport backendgroup type %s", group.GroupType) + } +} + +func (lb *SLoadbalancer) CreateILoadBalancerListener(ctx context.Context, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + switch listener.ListenerType { + case api.LB_LISTENER_TYPE_TCP: + return lb.region.CreateLoadbalancerTCPListener(lb, listener) + case api.LB_LISTENER_TYPE_UDP: + return lb.region.CreateLoadbalancerUDPListener(lb, listener) + case api.LB_LISTENER_TYPE_HTTP: + return lb.region.CreateLoadbalancerHTTPListener(lb, listener) + case api.LB_LISTENER_TYPE_HTTPS: + return lb.region.CreateLoadbalancerHTTPSListener(lb, listener) + } + return nil, fmt.Errorf("unsupport listener type %s", listener.ListenerType) +} + +func (lb *SLoadbalancer) GetLoadbalancerSpec() string { + if len(lb.LoadBalancerSpec) == 0 { + lb.Refresh() + } + return lb.LoadBalancerSpec +} + +func (lb *SLoadbalancer) GetChargeType() string { + switch lb.InternetChargeType { + case "paybybandwidth": + return api.LB_CHARGE_TYPE_BY_BANDWIDTH + case "paybytraffic": + return api.LB_CHARGE_TYPE_BY_TRAFFIC + } + return "unknown" +} + +func (lb *SLoadbalancer) GetCreatedAt() time.Time { + return lb.CreateTime +} + +func (lb *SLoadbalancer) GetEgressMbps() int { + if lb.Bandwidth < 1 { + return 0 + } + return lb.Bandwidth +} + +func (lb *SLoadbalancer) GetILoadBalancerBackendGroupById(groupId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + groups, err := lb.GetILoadBalancerBackendGroups() + if err != nil { + return nil, err + } + for i := 0; i < len(groups); i++ { + if groups[i].GetGlobalId() == groupId { + return groups[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (lb *SLoadbalancer) GetIEIP() (cloudprovider.ICloudEIP, error) { + if lb.AddressType == "internet" { + eip := SEipAddress{ + region: lb.region, + IpAddress: lb.Address, + InstanceId: lb.GetGlobalId(), + InstanceType: EIP_INTANNCE_TYPE_SLB, + Status: EIP_STATUS_INUSE, + AllocationId: lb.GetGlobalId(), + AllocationTime: lb.CreateTime, + Bandwidth: lb.Bandwidth, + InternetChargeType: lb.InternetChargeType, + } + return &eip, nil + } + eips, total, err := lb.region.GetEips("", lb.LoadBalancerId, 0, 1) + if err != nil { + return nil, errors.Wrapf(err, "lb.region.GetEips(%s)", lb.LoadBalancerId) + } + if total != 1 { + return nil, cloudprovider.ErrNotFound + } + eips[0].region = lb.region + return &eips[0], nil +} + +func (region *SRegion) loadbalancerOperation(loadbalancerId, status string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["LoadBalancerStatus"] = status + _, err := region.lbRequest("SetLoadBalancerStatus", params) + return err +} + +func (lb *SLoadbalancer) Start() error { + if lb.LoadBalancerStatus != "active" { + return lb.region.loadbalancerOperation(lb.LoadBalancerId, "active") + } + return nil +} + +func (lb *SLoadbalancer) Stop() error { + if lb.LoadBalancerStatus != "inactive" { + return lb.region.loadbalancerOperation(lb.LoadBalancerId, "inactive") + } + return nil +} + +func (lb *SLoadbalancer) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { + listener, err := lb.GetILoadBalancerListeners() + if err != nil { + return nil, err + } + for i := 0; i < len(listener); i++ { + if listener[i].GetGlobalId() == listenerId { + return listener[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (lb *SLoadbalancer) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { + loadbalancer, err := lb.region.GetLoadbalancerDetail(lb.LoadBalancerId) + if err != nil { + return nil, err + } + listeners := []cloudprovider.ICloudLoadbalancerListener{} + for _, listenerInfo := range loadbalancer.ListenerPortsAndProtocol.ListenerPortAndProtocol { + switch listenerInfo.ListenerProtocol { + case ListenerProtocolHTTP: + listener, err := lb.region.GetLoadbalancerHTTPListener(lb.LoadBalancerId, listenerInfo.ListenerPort) + if err != nil { + return nil, err + } + listener.lb = lb + listeners = append(listeners, listener) + case ListenerProtocolHTTPS: + listener, err := lb.region.GetLoadbalancerHTTPSListener(lb.LoadBalancerId, listenerInfo.ListenerPort) + if err != nil { + return nil, err + } + listener.lb = lb + listeners = append(listeners, listener) + case ListenerProtocolTCP: + listener, err := lb.region.GetLoadbalancerTCPListener(lb.LoadBalancerId, listenerInfo.ListenerPort) + if err != nil { + return nil, err + } + listener.lb = lb + listeners = append(listeners, listener) + case ListenerProtocolUDP: + listener, err := lb.region.GetLoadbalancerUDPListener(lb.LoadBalancerId, listenerInfo.ListenerPort) + if err != nil { + return nil, err + } + listener.lb = lb + listeners = append(listeners, listener) + default: + return nil, fmt.Errorf("failed to recognize %s type listener", listenerInfo.ListenerProtocol) + } + } + return listeners, nil +} + +func (lb *SLoadbalancer) GetProjectId() string { + return lb.ResourceGroupId +} + +func (lb *SLoadbalancer) SetMetadata(tags map[string]string, replace bool) error { + return lb.region.SetResourceTags("slb", "instance", []string{lb.LoadBalancerId}, tags, replace) +} diff --git a/pkg/multicloud/apsara/loadbalanceracl.go b/pkg/multicloud/apsara/loadbalanceracl.go new file mode 100644 index 0000000000..271686a89a --- /dev/null +++ b/pkg/multicloud/apsara/loadbalanceracl.go @@ -0,0 +1,163 @@ +// 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 apsara + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type AclEntrys struct { + AclEntry []AclEntry +} + +type AclEntry struct { + AclEntryComment string + AclEntryIP string +} + +type SLoadbalancerAcl struct { + region *SRegion + + AclId string + AclName string + + AclEntrys AclEntrys +} + +func (acl *SLoadbalancerAcl) GetAclListenerID() string { + return "" +} + +func (acl *SLoadbalancerAcl) GetName() string { + return acl.AclName +} + +func (acl *SLoadbalancerAcl) GetId() string { + return acl.AclId +} + +func (acl *SLoadbalancerAcl) GetGlobalId() string { + return acl.AclId +} + +func (acl *SLoadbalancerAcl) GetStatus() string { + return "" +} + +func (acl *SLoadbalancerAcl) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (acl *SLoadbalancerAcl) IsEmulated() bool { + return false +} + +func (acl *SLoadbalancerAcl) Refresh() error { + loadbalancerAcl, err := acl.region.GetLoadbalancerAclDetail(acl.AclId) + if err != nil { + return err + } + return jsonutils.Update(acl, loadbalancerAcl) +} + +func (acl *SLoadbalancerAcl) GetAclEntries() []cloudprovider.SLoadbalancerAccessControlListEntry { + detail, err := acl.region.GetLoadbalancerAclDetail(acl.AclId) + if err != nil { + log.Errorf("GetLoadbalancerAclDetail %s failed: %v", acl.AclId, err) + return nil + } + entrys := []cloudprovider.SLoadbalancerAccessControlListEntry{} + for _, entry := range detail.AclEntrys.AclEntry { + entrys = append(entrys, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: entry.AclEntryIP, Comment: entry.AclEntryComment}) + } + return entrys +} + +func (region *SRegion) UpdateAclName(aclId, name string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + params["AclName"] = name + _, err := region.lbRequest("SetAccessControlListAttribute", params) + return err +} + +func (region *SRegion) RemoveAccessControlListEntry(aclId string, data jsonutils.JSONObject) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + params["AclEntrys"] = data.String() + _, err := region.lbRequest("RemoveAccessControlListEntry", params) + return err +} + +func (acl *SLoadbalancerAcl) Delete() error { + params := map[string]string{} + params["RegionId"] = acl.region.RegionId + params["AclId"] = acl.AclId + _, err := acl.region.lbRequest("DeleteAccessControlList", params) + return err +} + +func (region *SRegion) GetLoadbalancerAclDetail(aclId string) (*SLoadbalancerAcl, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + body, err := region.lbRequest("DescribeAccessControlListAttribute", params) + if err != nil { + return nil, err + } + detail := SLoadbalancerAcl{region: region} + return &detail, body.Unmarshal(&detail) +} + +func (region *SRegion) GetLoadBalancerAcls() ([]SLoadbalancerAcl, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + body, err := region.lbRequest("DescribeAccessControlLists", params) + if err != nil { + return nil, err + } + acls := []SLoadbalancerAcl{} + return acls, body.Unmarshal(&acls, "Acls", "Acl") +} + +func (acl *SLoadbalancerAcl) Sync(_acl *cloudprovider.SLoadbalancerAccessControlList) error { + if acl.AclName != _acl.Name { + if err := acl.region.UpdateAclName(acl.AclId, _acl.Name); err != nil { + return err + } + } + entrys := jsonutils.NewArray() + for _, entry := range acl.AclEntrys.AclEntry { + entrys.Add(jsonutils.Marshal(map[string]string{"entry": entry.AclEntryIP, "comment": entry.AclEntryComment})) + } + if entrys.Length() > 0 { + if err := acl.region.RemoveAccessControlListEntry(acl.AclId, entrys); err != nil && !isError(err, "Acl does not have any entry") { + return err + } + } + if len(_acl.Entrys) > 0 { + return acl.region.AddAccessControlListEntry(acl.AclId, _acl.Entrys) + } + return nil +} + +func (acl *SLoadbalancerAcl) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerbackend.go b/pkg/multicloud/apsara/loadbalancerbackend.go new file mode 100644 index 0000000000..1d6f76b902 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerbackend.go @@ -0,0 +1,114 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerBackend struct { + lbbg *SLoadbalancerBackendGroup + + ServerId string + Port int + Weight int +} + +func (backend *SLoadbalancerBackend) GetName() string { + return backend.ServerId +} + +func (backend *SLoadbalancerBackend) GetId() string { + return fmt.Sprintf("%s/%s", backend.lbbg.VServerGroupId, backend.ServerId) +} + +func (backend *SLoadbalancerBackend) GetGlobalId() string { + return backend.GetId() +} + +func (backend *SLoadbalancerBackend) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (backend *SLoadbalancerBackend) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backend *SLoadbalancerBackend) IsEmulated() bool { + return false +} + +func (backend *SLoadbalancerBackend) Refresh() error { + loadbalancerBackends, err := backend.lbbg.lb.region.GetLoadbalancerBackends(backend.lbbg.VServerGroupId) + if err != nil { + return err + } + for _, loadbalancerBackend := range loadbalancerBackends { + if loadbalancerBackend.ServerId == backend.ServerId { + return jsonutils.Update(backend, loadbalancerBackend) + } + } + return cloudprovider.ErrNotFound +} + +func (backend *SLoadbalancerBackend) GetWeight() int { + return backend.Weight +} + +func (backend *SLoadbalancerBackend) GetPort() int { + return backend.Port +} + +func (backend *SLoadbalancerBackend) GetBackendType() string { + return api.LB_BACKEND_GUEST +} + +func (backend *SLoadbalancerBackend) GetBackendRole() string { + return api.LB_BACKEND_ROLE_DEFAULT +} + +func (backend *SLoadbalancerBackend) GetBackendId() string { + return backend.ServerId +} + +func (region *SRegion) GetLoadbalancerBackends(backendgroupId string) ([]SLoadbalancerBackend, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = backendgroupId + body, err := region.lbRequest("DescribeVServerGroupAttribute", params) + if err != nil { + return nil, err + } + backends := []SLoadbalancerBackend{} + return backends, body.Unmarshal(&backends, "BackendServers", "BackendServer") +} + +func (backend *SLoadbalancerBackend) GetProjectId() string { + return "" +} + +func (backend *SLoadbalancerBackend) SyncConf(ctx context.Context, port, weight int) error { + err := backend.lbbg.lb.region.RemoveBackendVServer(backend.lbbg.lb.LoadBalancerId, backend.lbbg.VServerGroupId, backend.ServerId, backend.Port) + if err != nil { + return err + } + return backend.lbbg.lb.region.AddBackendVServer(backend.lbbg.lb.LoadBalancerId, backend.lbbg.VServerGroupId, backend.ServerId, weight, port) +} diff --git a/pkg/multicloud/apsara/loadbalancerbackendgroup.go b/pkg/multicloud/apsara/loadbalancerbackendgroup.go new file mode 100644 index 0000000000..1aefd7a719 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerbackendgroup.go @@ -0,0 +1,278 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type Rule struct { + RuleId string + RuleName string + Domain string + Url string +} + +type Rules struct { + Rule []Rule +} + +type Listener struct { + Protocol string + Port int +} + +type Listeners struct { + Listener []Listener +} + +type AssociatedObjects struct { + Rules Rules + Listeners Listeners +} + +type SLoadbalancerBackendGroup struct { + lb *SLoadbalancer + + VServerGroupId string + VServerGroupName string + AssociatedObjects AssociatedObjects +} + +func (backendgroup *SLoadbalancerBackendGroup) GetILoadbalancer() cloudprovider.ICloudLoadbalancer { + return backendgroup.lb +} + +func (backendgroup *SLoadbalancerBackendGroup) GetLoadbalancerId() string { + return backendgroup.lb.GetId() +} + +func (backendgroup *SLoadbalancerBackendGroup) GetProtocolType() string { + return "" +} + +func (backendgroup *SLoadbalancerBackendGroup) GetScheduler() string { + return "" +} + +func (backendgroup *SLoadbalancerBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) { + return nil, nil +} + +func (backendgroup *SLoadbalancerBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) { + return nil, nil +} + +func (backendgroup *SLoadbalancerBackendGroup) GetName() string { + return backendgroup.VServerGroupName +} + +func (backendgroup *SLoadbalancerBackendGroup) GetId() string { + return backendgroup.VServerGroupId +} + +func (backendgroup *SLoadbalancerBackendGroup) GetGlobalId() string { + return backendgroup.VServerGroupId +} + +func (backendgroup *SLoadbalancerBackendGroup) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (backendgroup *SLoadbalancerBackendGroup) IsDefault() bool { + return false +} + +func (backendgroup *SLoadbalancerBackendGroup) GetType() string { + return api.LB_BACKENDGROUP_TYPE_NORMAL +} + +func (backendgroup *SLoadbalancerBackendGroup) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backendgroup *SLoadbalancerBackendGroup) IsEmulated() bool { + return false +} + +func (backendgroup *SLoadbalancerBackendGroup) Refresh() error { + loadbalancerBackendgroups, err := backendgroup.lb.region.GetLoadbalancerBackendgroups(backendgroup.lb.LoadBalancerId) + if err != nil { + return err + } + for _, loadbalancerBackendgroup := range loadbalancerBackendgroups { + if loadbalancerBackendgroup.VServerGroupId == backendgroup.VServerGroupId { + return jsonutils.Update(backendgroup, loadbalancerBackendgroup) + } + } + return cloudprovider.ErrNotFound +} + +func (region *SRegion) GetLoadbalancerBackendgroups(loadbalancerId string) ([]SLoadbalancerBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + body, err := region.lbRequest("DescribeVServerGroups", params) + if err != nil { + return nil, err + } + backendgroups := []SLoadbalancerBackendGroup{} + return backendgroups, body.Unmarshal(&backendgroups, "VServerGroups", "VServerGroup") +} + +func (backendgroup *SLoadbalancerBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { + backends, err := backendgroup.lb.region.GetLoadbalancerBackends(backendgroup.VServerGroupId) + if err != nil { + return nil, err + } + ibackends := []cloudprovider.ICloudLoadbalancerBackend{} + for i := 0; i < len(backends); i++ { + backends[i].lbbg = backendgroup + ibackends = append(ibackends, &backends[i]) + } + return ibackends, nil +} + +func (backendgroup *SLoadbalancerBackendGroup) GetILoadbalancerBackendById(backendId string) (cloudprovider.ICloudLoadbalancerBackend, error) { + backends, err := backendgroup.GetILoadbalancerBackends() + if err != nil { + return nil, err + } + for i := 0; i < len(backends); i++ { + if backends[i].GetGlobalId() == backendId { + return backends[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) CreateLoadbalancerBackendGroup(name, loadbalancerId string, backends []cloudprovider.SLoadbalancerBackend) (*SLoadbalancerBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupName"] = name + params["LoadBalancerId"] = loadbalancerId + if len(backends) > 0 { + servers := jsonutils.NewArray() + for _, backend := range backends { + servers.Add( + jsonutils.Marshal( + map[string]string{ + "ServerId": backend.ExternalID, + "Port": fmt.Sprintf("%d", backend.Port), + "Weight": fmt.Sprintf("%d", backend.Weight), + }, + )) + } + params["BackendServers"] = servers.String() + } + body, err := region.lbRequest("CreateVServerGroup", params) + if err != nil { + return nil, err + } + groupId, err := body.GetString("VServerGroupId") + if err != nil { + return nil, err + } + return region.GetLoadbalancerBackendgroupById(groupId) +} + +func (region *SRegion) GetLoadbalancerBackendgroupById(groupId string) (*SLoadbalancerBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = groupId + body, err := region.lbRequest("DescribeVServerGroupAttribute", params) + if err != nil { + return nil, err + } + group := &SLoadbalancerBackendGroup{} + return group, body.Unmarshal(group) +} + +func (region *SRegion) UpdateLoadBalancerBackendGroupName(name, groupId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = groupId + params["VServerGroupName"] = name + _, err := region.lbRequest("SetVServerGroupAttribute", params) + return err +} + +func (backendgroup *SLoadbalancerBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error { + if group == nil { + return nil + } + + if backendgroup.VServerGroupName != group.Name { + return backendgroup.lb.region.UpdateLoadBalancerBackendGroupName(backendgroup.VServerGroupId, group.Name) + } + return nil +} + +func (region *SRegion) DeleteLoadBalancerBackendGroup(groupId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = groupId + _, err := region.lbRequest("DeleteVServerGroup", params) + return err +} + +func (backendgroup *SLoadbalancerBackendGroup) Delete(ctx context.Context) error { + return backendgroup.lb.region.DeleteLoadBalancerBackendGroup(backendgroup.VServerGroupId) +} + +func (region *SRegion) AddBackendVServer(loadbalancerId, backendGroupId, serverId string, weight, port int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["VServerGroupId"] = backendGroupId + servers := jsonutils.NewArray() + servers.Add(jsonutils.Marshal(map[string]string{"ServerId": serverId, "Weight": fmt.Sprintf("%d", weight), "Port": fmt.Sprintf("%d", port)})) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("AddVServerGroupBackendServers", params) + return err +} + +func (region *SRegion) RemoveBackendVServer(loadbalancerId, backendgroupId, serverId string, port int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["VServerGroupId"] = backendgroupId + servers := jsonutils.NewArray() + servers.Add(jsonutils.Marshal(map[string]string{"ServerId": serverId, "Port": fmt.Sprintf("%d", port)})) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("RemoveVServerGroupBackendServers", params) + return err +} + +func (backendgroup *SLoadbalancerBackendGroup) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + if err := backendgroup.lb.region.AddBackendVServer(backendgroup.lb.LoadBalancerId, backendgroup.VServerGroupId, serverId, weight, port); err != nil { + return nil, err + } + return &SLoadbalancerBackend{lbbg: backendgroup, ServerId: serverId, Weight: weight, Port: port}, nil +} + +func (backendgroup *SLoadbalancerBackendGroup) RemoveBackendServer(serverId string, weight, port int) error { + return backendgroup.lb.region.RemoveBackendVServer(backendgroup.lb.LoadBalancerId, backendgroup.VServerGroupId, serverId, port) +} + +func (backendgroup *SLoadbalancerBackendGroup) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerdefaultbackend.go b/pkg/multicloud/apsara/loadbalancerdefaultbackend.go new file mode 100644 index 0000000000..fc9947ccf7 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerdefaultbackend.go @@ -0,0 +1,112 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SLoadbalancerDefaultBackend struct { + lbbg *SLoadbalancerDefaultBackendGroup + + ServerId string + Weight int +} + +func (backend *SLoadbalancerDefaultBackend) GetName() string { + return backend.ServerId +} + +func (backend *SLoadbalancerDefaultBackend) GetId() string { + return fmt.Sprintf("%s/%s", backend.lbbg.lb.LoadBalancerId, backend.ServerId) +} + +func (backend *SLoadbalancerDefaultBackend) GetGlobalId() string { + return backend.GetId() +} + +func (backend *SLoadbalancerDefaultBackend) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (backend *SLoadbalancerDefaultBackend) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backend *SLoadbalancerDefaultBackend) IsEmulated() bool { + return false +} + +func (backend *SLoadbalancerDefaultBackend) Refresh() error { + return nil +} + +func (backend *SLoadbalancerDefaultBackend) GetWeight() int { + return backend.Weight +} + +func (backend *SLoadbalancerDefaultBackend) GetPort() int { + return 0 +} + +func (backend *SLoadbalancerDefaultBackend) GetBackendType() string { + return api.LB_BACKEND_GUEST +} + +func (backend *SLoadbalancerDefaultBackend) GetBackendRole() string { + return api.LB_BACKEND_ROLE_DEFAULT +} + +func (backend *SLoadbalancerDefaultBackend) GetBackendId() string { + return backend.ServerId +} + +func (backend *SLoadbalancerDefaultBackend) GetProjectId() string { + return "" +} + +func (backend *SLoadbalancerDefaultBackend) SyncConf(ctx context.Context, port, weight int) error { + params := map[string]string{} + params["RegionId"] = backend.lbbg.lb.region.RegionId + params["LoadBalancerId"] = backend.lbbg.lb.LoadBalancerId + loadbalancer, err := backend.lbbg.lb.region.GetLoadbalancerDetail(backend.lbbg.lb.LoadBalancerId) + if err != nil { + return err + } + servers := jsonutils.NewArray() + for i := 0; i < len(loadbalancer.BackendServers.BackendServer); i++ { + _backend := loadbalancer.BackendServers.BackendServer[i] + _backend.lbbg = backend.lbbg + if _backend.GetGlobalId() == backend.GetGlobalId() { + _backend.Weight = weight + } + servers.Add( + jsonutils.Marshal( + map[string]string{ + "ServerId": _backend.ServerId, + "Weight": fmt.Sprintf("%d", _backend.Weight), + }, + )) + } + + params["BackendServers"] = servers.String() + _, err = backend.lbbg.lb.region.lbRequest("SetBackendServers", params) + return err +} diff --git a/pkg/multicloud/apsara/loadbalancerdefaultbackendgroup.go b/pkg/multicloud/apsara/loadbalancerdefaultbackendgroup.go new file mode 100644 index 0000000000..56a3c20ff8 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerdefaultbackendgroup.go @@ -0,0 +1,160 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerDefaultBackendGroup struct { + lb *SLoadbalancer +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetILoadbalancer() cloudprovider.ICloudLoadbalancer { + return backendgroup.lb +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetLoadbalancerId() string { + return backendgroup.lb.GetId() +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetProtocolType() string { + return "" +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetScheduler() string { + return "" +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) { + return nil, nil +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) { + return nil, nil +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetName() string { + return fmt.Sprintf("%s(%s)-default", backendgroup.lb.LoadBalancerName, backendgroup.lb.Address) +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetId() string { + return fmt.Sprintf("%s/default", backendgroup.lb.LoadBalancerId) +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetGlobalId() string { + return backendgroup.GetId() +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) IsDefault() bool { + return true +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetType() string { + return api.LB_BACKENDGROUP_TYPE_DEFAULT +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) IsEmulated() bool { + return false +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) Refresh() error { + return nil +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { + loadbalancer, err := backendgroup.lb.region.GetLoadbalancerDetail(backendgroup.lb.LoadBalancerId) + if err != nil { + return nil, err + } + ibackends := []cloudprovider.ICloudLoadbalancerBackend{} + for i := 0; i < len(loadbalancer.BackendServers.BackendServer); i++ { + loadbalancer.BackendServers.BackendServer[i].lbbg = backendgroup + ibackends = append(ibackends, &loadbalancer.BackendServers.BackendServer[i]) + } + return ibackends, nil +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetILoadbalancerBackendById(backendId string) (cloudprovider.ICloudLoadbalancerBackend, error) { + backends, err := backendgroup.GetILoadbalancerBackends() + if err != nil { + return nil, err + } + for i := 0; i < len(backends); i++ { + if backends[i].GetGlobalId() == backendId { + return backends[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error { + return cloudprovider.ErrNotSupported +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) Delete(ctx context.Context) error { + return cloudprovider.ErrNotSupported +} + +func (region *SRegion) AddBackendServer(loadbalancerId, serverId string, weight, port int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + servers := jsonutils.NewArray() + servers.Add(jsonutils.Marshal(map[string]string{"ServerId": serverId, "Weight": fmt.Sprintf("%d", weight)})) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("AddBackendServers", params) + return err +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + if err := backendgroup.lb.region.AddBackendServer(backendgroup.lb.LoadBalancerId, serverId, weight, port); err != nil { + return nil, err + } + return &SLoadbalancerDefaultBackend{lbbg: backendgroup, ServerId: serverId, Weight: weight}, nil +} + +func (region *SRegion) RemoveBackendServer(loadbalancerId, serverId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + servers := jsonutils.NewArray() + servers.Add(jsonutils.NewString(serverId)) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("RemoveBackendServers", params) + return err +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) RemoveBackendServer(serverId string, weight, port int) error { + return backendgroup.lb.region.RemoveBackendServer(backendgroup.lb.LoadBalancerId, serverId) +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerhttplistener.go b/pkg/multicloud/apsara/loadbalancerhttplistener.go new file mode 100644 index 0000000000..38251b4027 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerhttplistener.go @@ -0,0 +1,371 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerHTTPListener struct { + lb *SLoadbalancer + + ListenerPort int // 负载均衡实例前端使用的端口。 + BackendServerPort int // 负载均衡实例后端使用的端口。 + Bandwidth int // 监听的带宽峰值。 + Status string // 当前监听的状态。取值:starting | running | configuring | stopping | stopped + Description string + + XForwardedFor string // 是否开启通过X-Forwarded-For头字段获取访者真实IP。 + XForwardedFor_SLBIP string // 是否通过SLB-IP头字段获取客户端请求的真实IP。 + XForwardedFor_SLBID string // 是否通过SLB-ID头字段获取负载均衡实例ID。 + XForwardedFor_proto string // 是否通过X-Forwarded-Proto头字段获取负载均衡实例的监听协议。 + Scheduler string // 调度算法。 + StickySession string // 是否开启会话保持。 + StickySessionType string // cookie的处理方式。 + CookieTimeout int // Cookie超时时间。 + Cookie string // 服务器上配置的cookie。 + AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) + + AclType string // 访问控制类型: + //white: 仅转发来自所选访问控制策略组中设置的IP地址或地址段的请求,白名单适用于应用只允许特定IP访问的场景。 + //设置白名单存在一定业务风险。一旦设置白名单,就只有白名单中的IP可以访问负载均衡监听。如果开启了白名单访问,但访问策略组中没有添加任何IP,则负载均衡监听会转发全部请求。 + + //black: 来自所选访问控制策略组中设置的IP地址或地址段的所有请求都不会转发,黑名单适用于应用只限制某些特定IP访问的场景。 + //如果开启了黑名单访问,但访问策略组中没有添加任何IP,则负载均衡监听会转发全部请求。 + + //当AclStatus参数的值为on时,该参数必选。 + + AclId string // 监听绑定的访问策略组ID。当AclStatus参数的值为on时,该参数必选。 + + HealthCheck string // 是否开启健康检查。 + HealthCheckDomain string // 用于健康检查的域名。 + HealthCheckURI string // 用于健康检查的URI。 + HealthyThreshold int // 健康检查阈值。 + UnhealthyThreshold int // 不健康检查阈值。 + HealthCheckTimeout int // 每次健康检查响应的最大超时间,单位为秒。 + HealthCheckInterval int // 健康检查的时间间隔,单位为秒。 + HealthCheckHttpCode string // 健康检查正常的HTTP状态码。 + HealthCheckConnectPort int // 健康检查的端口。 + Gzip string // 是否开启Gzip压缩。 + EnableHttp2 string // 是否开启HTTP/2特性。取值:on(默认值)|off + + Rules Rules //监听下的转发规则列表,具体请参见RuleList。 + ForwardPort int // HTTP至HTTPS的监听转发端口。暂时只支持将HTTP 80访问重定向转发至HTTPS 443。 说明 如果 ListenerForward的值为 off,该参数不显示。 + ListenerForward string // 表示是否开启HTTP至HTTPS的监听转发。on:表示开启 off:表示未开启 + VServerGroupId string // 绑定的服务器组ID +} + +func (listener *SLoadbalancerHTTPListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } + return fmt.Sprintf("HTTP:%d", listener.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPListener) GetId() string { + return fmt.Sprintf("%s/%d", listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPListener) GetGlobalId() string { + return listerner.GetId() +} + +func (listerner *SLoadbalancerHTTPListener) GetStatus() string { + switch listerner.Status { + case "starting", "running": + return api.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return api.LB_STATUS_DISABLED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (listerner *SLoadbalancerHTTPListener) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (listerner *SLoadbalancerHTTPListener) IsEmulated() bool { + return false +} + +func (listerner *SLoadbalancerHTTPListener) GetEgressMbps() int { + if listerner.Bandwidth < 1 { + return 0 + } + return listerner.Bandwidth +} + +func (listerner *SLoadbalancerHTTPListener) Refresh() error { + lis, err := listerner.lb.region.GetLoadbalancerHTTPListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) +} + +func (listerner *SLoadbalancerHTTPListener) GetListenerType() string { + return "http" +} + +func (listerner *SLoadbalancerHTTPListener) GetListenerPort() int { + return listerner.ListenerPort +} + +func (listerner *SLoadbalancerHTTPListener) GetBackendGroupId() string { + if len(listerner.VServerGroupId) == 0 { + listerner.Refresh() + } + return listerner.VServerGroupId +} + +func (listerner *SLoadbalancerHTTPListener) GetBackendServerPort() int { + return listerner.BackendServerPort +} + +func (listerner *SLoadbalancerHTTPListener) GetScheduler() string { + return listerner.Scheduler +} + +func (listerner *SLoadbalancerHTTPListener) GetAclStatus() string { + return listerner.AclStatus +} + +func (listerner *SLoadbalancerHTTPListener) GetAclType() string { + return listerner.AclType +} + +func (listerner *SLoadbalancerHTTPListener) GetAclId() string { + return listerner.AclId +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheck() string { + return listerner.HealthCheck +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckType() string { + return api.LB_HEALTH_CHECK_HTTP +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckDomain() string { + return listerner.HealthCheckDomain +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckURI() string { + return listerner.HealthCheckURI +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckCode() string { + return listerner.HealthCheckHttpCode +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckRise() int { + return listerner.HealthyThreshold +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckFail() int { + return listerner.UnhealthyThreshold +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckTimeout() int { + return listerner.HealthCheckTimeout +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckInterval() int { + return listerner.HealthCheckInterval +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckReq() string { + return "" +} + +func (listerner *SLoadbalancerHTTPListener) GetHealthCheckExp() string { + return "" +} + +func (listerner *SLoadbalancerHTTPListener) GetStickySession() string { + return listerner.StickySession +} + +func (listerner *SLoadbalancerHTTPListener) GetStickySessionType() string { + return listerner.StickySessionType +} + +func (listerner *SLoadbalancerHTTPListener) GetStickySessionCookie() string { + return listerner.Cookie +} + +func (listerner *SLoadbalancerHTTPListener) GetStickySessionCookieTimeout() int { + return listerner.CookieTimeout +} + +func (listerner *SLoadbalancerHTTPListener) XForwardedForEnabled() bool { + if listerner.XForwardedFor == "on" { + return true + } + return false +} + +func (listerner *SLoadbalancerHTTPListener) GzipEnabled() bool { + if listerner.Gzip == "on" { + return true + } + return false +} + +func (listerner *SLoadbalancerHTTPListener) GetCertificateId() string { + return "" +} + +func (listerner *SLoadbalancerHTTPListener) GetTLSCipherPolicy() string { + return "" +} + +func (listerner *SLoadbalancerHTTPListener) HTTP2Enabled() bool { + if listerner.EnableHttp2 == "on" { + return true + } + return false +} + +func (listerner *SLoadbalancerHTTPListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + rules, err := listerner.lb.region.GetLoadbalancerListenerRules(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return nil, err + } + iRules := []cloudprovider.ICloudLoadbalancerListenerRule{} + for i := 0; i < len(rules); i++ { + rules[i].httpListener = listerner + iRules = append(iRules, &rules[i]) + } + return iRules, nil +} + +func (region *SRegion) GetLoadbalancerHTTPListener(loadbalancerId string, listenerPort int) (*SLoadbalancerHTTPListener, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + body, err := region.lbRequest("DescribeLoadBalancerHTTPListenerAttribute", params) + if err != nil { + return nil, err + } + listener := SLoadbalancerHTTPListener{} + return &listener, body.Unmarshal(&listener) +} + +func (region *SRegion) DeleteLoadbalancerListener(loadbalancerId string, listenerPort int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + _, err := region.lbRequest("DeleteLoadBalancerListener", params) + return err +} + +func (region *SRegion) CreateLoadbalancerHTTPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + _, err := region.lbRequest("CreateLoadBalancerHTTPListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerHTTPListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerHTTPListener) Delete(ctx context.Context) error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + _rule := &SLoadbalancerListenerRule{ + Domain: rule.Domain, + Url: rule.Path, + RuleName: rule.Name, + } + if len(rule.BackendGroupID) > 0 { //&& rule.BackendGroupType == api.LB_BACKENDGROUP_TYPE_NORMAL { + _rule.VServerGroupId = rule.BackendGroupID + } + listenerRule, err := listerner.lb.region.CreateLoadbalancerListenerRule(listerner.ListenerPort, listerner.lb.LoadBalancerId, _rule) + if err != nil { + return nil, err + } + listenerRule.httpListener = listerner + return listenerRule, nil +} + +func (listerner *SLoadbalancerHTTPListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + rule, err := listerner.lb.region.GetLoadbalancerListenerRule(ruleId) + if err != nil { + return nil, err + } + rule.httpListener = listerner + return rule, nil +} + +func (region *SRegion) startListener(listenerPort int, loadbalancerId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + _, err := region.lbRequest("StartLoadBalancerListener", params) + return err +} + +func (region *SRegion) stopListener(listenerPort int, loadbalancerId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + _, err := region.lbRequest("StopLoadBalancerListener", params) + return err +} + +func (listerner *SLoadbalancerHTTPListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerHTTPListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerHTTPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + _, err := region.lbRequest("SetLoadBalancerHTTPListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerHTTPListener) Sync(ctx context.Context, lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerHTTPListener(listerner.lb, lblis) +} + +func (listerner *SLoadbalancerHTTPListener) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerhttpslistener.go b/pkg/multicloud/apsara/loadbalancerhttpslistener.go new file mode 100644 index 0000000000..7958036ba7 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerhttpslistener.go @@ -0,0 +1,401 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerHTTPSListener struct { + lb *SLoadbalancer + + ListenerPort int // 负载均衡实例前端使用的端口。 + BackendServerPort int // 负载均衡实例后端使用的端口。 + Bandwidth int // 监听的带宽峰值。 + Status string // 当前监听的状态。取值:starting | running | configuring | stopping | stopped + Description string + + XForwardedFor string // 是否开启通过X-Forwarded-For头字段获取访者真实IP。 + XForwardedFor_SLBIP string // 是否通过SLB-IP头字段获取客户端请求的真实IP。 + XForwardedFor_SLBID string // 是否通过SLB-ID头字段获取负载均衡实例ID。 + XForwardedFor_proto string // 是否通过X-Forwarded-Proto头字段获取负载均衡实例的监听协议。 + Scheduler string // 调度算法。 + StickySession string // 是否开启会话保持。 + StickySessionType string // cookie的处理方式。 + CookieTimeout int // Cookie超时时间。 + Cookie string // 服务器上配置的cookie。 + AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) + + AclType string // 访问控制类型 + + AclId string // 监听绑定的访问策略组ID。当AclStatus参数的值为on时,该参数必选。 + + HealthCheck string // 是否开启健康检查。 + HealthCheckDomain string // 用于健康检查的域名。 + HealthCheckURI string // 用于健康检查的URI。 + HealthyThreshold int // 健康检查阈值。 + UnhealthyThreshold int // 不健康检查阈值。 + HealthCheckTimeout int // 每次健康检查响应的最大超时间,单位为秒。 + HealthCheckInterval int // 健康检查的时间间隔,单位为秒。 + HealthCheckHttpCode string // 健康检查正常的HTTP状态码。 + HealthCheckConnectPort int // 健康检查的端口。 + VServerGroupId string // 绑定的服务器组ID。 + ServerCertificateId string // 服务器证书ID。 + CACertificateId string // CA证书ID。 + Gzip string // 是否开启Gzip压缩。 + Rules Rules //监听下的转发规则列表,具体请参见RuleList。 + DomainExtensions string // 域名扩展列表,具体请参见DomainExtensions。 + EnableHttp2 string // 是否开启HTTP/2特性。取值:on(默认值)|off + + TLSCipherPolicy string // +} + +func (listener *SLoadbalancerHTTPSListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } + return fmt.Sprintf("HTTPS:%d", listener.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPSListener) GetId() string { + return fmt.Sprintf("%s/%d", listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPSListener) GetGlobalId() string { + return listerner.GetId() +} + +func (listerner *SLoadbalancerHTTPSListener) GetStatus() string { + switch listerner.Status { + case "starting", "running": + return api.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return api.LB_STATUS_DISABLED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (listerner *SLoadbalancerHTTPSListener) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (listerner *SLoadbalancerHTTPSListener) IsEmulated() bool { + return false +} + +func (listerner *SLoadbalancerHTTPSListener) GetEgressMbps() int { + if listerner.Bandwidth < 1 { + return 0 + } + return listerner.Bandwidth +} + +func (listerner *SLoadbalancerHTTPSListener) Refresh() error { + lis, err := listerner.lb.region.GetLoadbalancerHTTPSListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) +} + +func (listerner *SLoadbalancerHTTPSListener) GetListenerType() string { + return "https" +} + +func (listerner *SLoadbalancerHTTPSListener) GetListenerPort() int { + return listerner.ListenerPort +} + +func (listerner *SLoadbalancerHTTPSListener) GetBackendGroupId() string { + if len(listerner.VServerGroupId) == 0 { + listerner.Refresh() + } + return listerner.VServerGroupId +} + +func (listerner *SLoadbalancerHTTPSListener) GetBackendServerPort() int { + return listerner.BackendServerPort +} + +func (listerner *SLoadbalancerHTTPSListener) GetScheduler() string { + return listerner.Scheduler +} + +func (listerner *SLoadbalancerHTTPSListener) GetAclStatus() string { + return listerner.AclStatus +} + +func (listerner *SLoadbalancerHTTPSListener) GetAclType() string { + return listerner.AclType +} + +func (listerner *SLoadbalancerHTTPSListener) GetAclId() string { + return listerner.AclId +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheck() string { + return listerner.HealthCheck +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckType() string { + return api.LB_HEALTH_CHECK_HTTP +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckDomain() string { + return listerner.HealthCheckDomain +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckURI() string { + return listerner.HealthCheckURI +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckCode() string { + return listerner.HealthCheckHttpCode +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckRise() int { + return listerner.HealthyThreshold +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckFail() int { + return listerner.UnhealthyThreshold +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckTimeout() int { + return listerner.HealthCheckTimeout +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckInterval() int { + return listerner.HealthCheckInterval +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckReq() string { + return "" +} + +func (listerner *SLoadbalancerHTTPSListener) GetHealthCheckExp() string { + return "" +} + +func (listerner *SLoadbalancerHTTPSListener) GetStickySession() string { + return listerner.StickySession +} + +func (listerner *SLoadbalancerHTTPSListener) GetStickySessionType() string { + return listerner.StickySessionType +} + +func (listerner *SLoadbalancerHTTPSListener) GetStickySessionCookie() string { + return listerner.Cookie +} + +func (listerner *SLoadbalancerHTTPSListener) GetStickySessionCookieTimeout() int { + return listerner.CookieTimeout +} + +func (listerner *SLoadbalancerHTTPSListener) XForwardedForEnabled() bool { + if listerner.XForwardedFor == "on" { + return true + } + return false +} + +func (listerner *SLoadbalancerHTTPSListener) GzipEnabled() bool { + if listerner.Gzip == "on" { + return true + } + return false +} + +func (listerner *SLoadbalancerHTTPSListener) GetCertificateId() string { + return listerner.ServerCertificateId +} + +func (listerner *SLoadbalancerHTTPSListener) GetTLSCipherPolicy() string { + return listerner.TLSCipherPolicy +} + +func (listerner *SLoadbalancerHTTPSListener) HTTP2Enabled() bool { + if listerner.EnableHttp2 == "on" { + return true + } + return false +} + +func (listerner *SLoadbalancerHTTPSListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + rules, err := listerner.lb.region.GetLoadbalancerListenerRules(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return nil, err + } + iRules := []cloudprovider.ICloudLoadbalancerListenerRule{} + for i := 0; i < len(rules); i++ { + rules[i].httpsListener = listerner + iRules = append(iRules, &rules[i]) + } + return iRules, nil +} + +func (region *SRegion) GetLoadbalancerHTTPSListener(loadbalancerId string, listenerPort int) (*SLoadbalancerHTTPSListener, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + body, err := region.lbRequest("DescribeLoadBalancerHTTPSListenerAttribute", params) + if err != nil { + return nil, err + } + listener := SLoadbalancerHTTPSListener{} + return &listener, body.Unmarshal(&listener) +} + +func (region *SRegion) constructHTTPCreateListenerParams(params map[string]string, listener *cloudprovider.SLoadbalancerListener) map[string]string { + params["HealthCheck"] = listener.HealthCheck + if listener.HealthCheck == "on" { + if len(listener.HealthCheckURI) == 0 { + params["HealthCheckURI"] = "/" + } + //The HealthCheckTimeout parameter is required. + if listener.HealthCheckTimeout < 1 || listener.HealthCheckTimeout > 300 { + listener.HealthCheckTimeout = 5 + } + params["HealthCheckTimeout"] = fmt.Sprintf("%d", listener.HealthCheckTimeout) + } + + if listener.ClientRequestTimeout < 1 || listener.ClientRequestTimeout > 180 { + listener.ClientRequestTimeout = 60 + } + params["RequestTimeout"] = fmt.Sprintf("%d", listener.ClientRequestTimeout) + + if listener.ClientIdleTimeout < 1 || listener.ClientIdleTimeout > 60 { + listener.ClientIdleTimeout = 15 + } + params["IdleTimeout"] = fmt.Sprintf("%d", listener.ClientIdleTimeout) + + params["StickySession"] = listener.StickySession + params["StickySessionType"] = listener.StickySessionType + params["Cookie"] = listener.StickySessionCookie + if listener.StickySessionCookieTimeout < 1 || listener.StickySessionCookieTimeout > 86400 { + listener.StickySessionCookieTimeout = 500 + } + params["CookieTimeout"] = fmt.Sprintf("%d", listener.StickySessionCookieTimeout) + //params["ForwardPort"] = fmt.Sprintf("%d", listener.ForwardPort) //暂不支持 + params["Gzip"] = "off" + if listener.Gzip { + params["Gzip"] = "on" + } + params["XForwardedFor"] = "off" + if listener.XForwardedFor { + params["XForwardedFor"] = "on" + } + return params +} + +func (region *SRegion) CreateLoadbalancerHTTPSListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + params["ServerCertificateId"] = listener.CertificateID + if listener.EnableHTTP2 { + params["EnableHttp2"] = "on" + } else { + params["EnableHttp2"] = "off" + } + + if len(listener.TLSCipherPolicy) > 0 { + params["TLSCipherPolicy"] = listener.TLSCipherPolicy + } + _, err := region.lbRequest("CreateLoadBalancerHTTPSListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerHTTPSListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerHTTPSListener) Delete(ctx context.Context) error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPSListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + _rule := &SLoadbalancerListenerRule{ + Domain: rule.Domain, + Url: rule.Path, + RuleName: rule.Name, + } + if len(rule.BackendGroupID) > 0 { //&& rule.BackendGroupType == api.LB_BACKENDGROUP_TYPE_NORMAL { + _rule.VServerGroupId = rule.BackendGroupID + } + listenerRule, err := listerner.lb.region.CreateLoadbalancerListenerRule(listerner.ListenerPort, listerner.lb.LoadBalancerId, _rule) + if err != nil { + return nil, err + } + listenerRule.httpsListener = listerner + return listenerRule, nil +} + +func (listerner *SLoadbalancerHTTPSListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + rule, err := listerner.lb.region.GetLoadbalancerListenerRule(ruleId) + if err != nil { + return nil, err + } + rule.httpsListener = listerner + return rule, nil +} + +func (listerner *SLoadbalancerHTTPSListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerHTTPSListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerHTTPSListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + params["ServerCertificateId"] = listener.CertificateID + if listener.EnableHTTP2 { + params["EnableHttp2"] = "on" + } else { + params["EnableHttp2"] = "off" + } + + if len(lb.LoadBalancerSpec) > 0 && len(listener.TLSCipherPolicy) > 0 { + params["TLSCipherPolicy"] = listener.TLSCipherPolicy + } + _, err := region.lbRequest("SetLoadBalancerHTTPSListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerHTTPSListener) Sync(ctx context.Context, lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerHTTPSListener(listerner.lb, lblis) +} + +func (listerner *SLoadbalancerHTTPSListener) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerlistenerrule.go b/pkg/multicloud/apsara/loadbalancerlistenerrule.go new file mode 100644 index 0000000000..ce304ce2eb --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerlistenerrule.go @@ -0,0 +1,176 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerListenerRule struct { + httpListener *SLoadbalancerHTTPListener + httpsListener *SLoadbalancerHTTPSListener + + Domain string `json:"Domain"` + ListenerSync string + RuleId string + RuleName string `json:"RuleName"` + Url string `json:"Url"` + VServerGroupId string `json:"VServerGroupId"` +} + +func (lbr *SLoadbalancerListenerRule) GetName() string { + return lbr.RuleName +} + +func (lbr *SLoadbalancerListenerRule) GetId() string { + return lbr.RuleId +} + +func (lbr *SLoadbalancerListenerRule) GetGlobalId() string { + return lbr.RuleId +} + +func (lbr *SLoadbalancerListenerRule) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (lbr *SLoadbalancerListenerRule) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLoadbalancerListenerRule) IsDefault() bool { + return false +} + +func (lbr *SLoadbalancerListenerRule) IsEmulated() bool { + return false +} + +func (lbr *SLoadbalancerListenerRule) getRegion() *SRegion { + if lbr.httpListener != nil { + return lbr.httpListener.lb.region + } else if lbr.httpsListener != nil { + return lbr.httpsListener.lb.region + } + return nil +} + +func (lbr *SLoadbalancerListenerRule) Refresh() error { + region := lbr.getRegion() + if region == nil { + return fmt.Errorf("failed to find listener for rule %s", lbr.RuleName) + } + rule, err := region.GetLoadbalancerListenerRule(lbr.RuleId) + if err != nil { + return err + } + return jsonutils.Update(lbr, rule) +} + +func (lbr *SLoadbalancerListenerRule) GetCondition() string { + return "" +} + +func (lbr *SLoadbalancerListenerRule) GetDomain() string { + return lbr.Domain +} + +func (lbr *SLoadbalancerListenerRule) GetPath() string { + return lbr.Url +} + +func (lbr *SLoadbalancerListenerRule) GetProjectId() string { + return "" +} + +func (lbr *SLoadbalancerListenerRule) GetBackendGroupId() string { + return lbr.VServerGroupId +} + +func (region *SRegion) GetLoadbalancerListenerRules(loadbalancerId string, listenerPort int) ([]SLoadbalancerListenerRule, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + body, err := region.lbRequest("DescribeRules", params) + if err != nil { + return nil, err + } + rules := []SLoadbalancerListenerRule{} + return rules, body.Unmarshal(&rules, "Rules", "Rule") +} + +func (lbr *SLoadbalancerListenerRule) Delete(ctx context.Context) error { + if lbr.httpListener != nil { + return lbr.httpListener.lb.region.DeleteLoadbalancerListenerRule(lbr.RuleId) + } + if lbr.httpsListener != nil { + return lbr.httpsListener.lb.region.DeleteLoadbalancerListenerRule(lbr.RuleId) + } + return fmt.Errorf("failed to find listener for listener rule %s", lbr.RuleName) +} + +func (region *SRegion) DeleteLoadbalancerListenerRule(ruleId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["RuleIds"] = fmt.Sprintf(`["%s"]`, ruleId) + _, err := region.lbRequest("DeleteRules", params) + return err +} + +func (region *SRegion) CreateLoadbalancerListenerRule(listenerPort int, loadbalancerId string, _rule *SLoadbalancerListenerRule) (*SLoadbalancerListenerRule, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + params["LoadBalancerId"] = loadbalancerId + _rules := jsonutils.NewArray() + _rules.Add(jsonutils.Marshal(_rule)) + params["RuleList"] = _rules.String() + body, err := region.lbRequest("CreateRules", params) + if err != nil { + return nil, err + } + rules := []SLoadbalancerListenerRule{} + if err := body.Unmarshal(&rules, "Rules", "Rule"); err != nil { + return nil, err + } + for _, rule := range rules { + if rule.RuleName == _rule.RuleName { + return region.GetLoadbalancerListenerRule(rule.RuleId) + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) GetLoadbalancerListenerRule(ruleId string) (*SLoadbalancerListenerRule, error) { + if len(ruleId) == 0 { + return nil, cloudprovider.ErrNotFound + } + params := map[string]string{} + params["RegionId"] = region.RegionId + params["RuleId"] = ruleId + body, err := region.lbRequest("DescribeRuleAttribute", params) + if err != nil { + return nil, err + } + rule := &SLoadbalancerListenerRule{RuleId: ruleId} + return rule, body.Unmarshal(rule) +} diff --git a/pkg/multicloud/apsara/loadbalancermasterslavebackend.go b/pkg/multicloud/apsara/loadbalancermasterslavebackend.go new file mode 100644 index 0000000000..bfa3f20455 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancermasterslavebackend.go @@ -0,0 +1,91 @@ +// 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 apsara + +import ( + "context" + "fmt" + "strings" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerMasterSlaveBackend struct { + lbbg *SLoadbalancerMasterSlaveBackendGroup + + ServerId string + Weight int + Port int + ServerType string +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetName() string { + return backend.ServerId +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetId() string { + return fmt.Sprintf("%s/%s", backend.lbbg.MasterSlaveServerGroupId, backend.ServerId) +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetGlobalId() string { + return backend.GetId() +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backend *SLoadbalancerMasterSlaveBackend) IsEmulated() bool { + return false +} + +func (backend *SLoadbalancerMasterSlaveBackend) Refresh() error { + return nil +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetWeight() int { + return backend.Weight +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetPort() int { + return backend.Port +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetBackendType() string { + return api.LB_BACKEND_GUEST +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetBackendRole() string { + return strings.ToLower(backend.ServerType) +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetBackendId() string { + return backend.ServerId +} + +func (backend *SLoadbalancerMasterSlaveBackend) GetProjectId() string { + return "" +} + +func (backend *SLoadbalancerMasterSlaveBackend) SyncConf(ctx context.Context, port, weight int) error { + return cloudprovider.ErrNotSupported +} diff --git a/pkg/multicloud/apsara/loadbalancermasterslavebackendgroup.go b/pkg/multicloud/apsara/loadbalancermasterslavebackendgroup.go new file mode 100644 index 0000000000..0bc41452ab --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancermasterslavebackendgroup.go @@ -0,0 +1,215 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerMasterSlaveBackendGroup struct { + lb *SLoadbalancer + + MasterSlaveServerGroupId string + MasterSlaveServerGroupName string +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetLoadbalancerId() string { + return backendgroup.lb.GetId() +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetProtocolType() string { + return "" +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetScheduler() string { + return "" +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) { + return nil, nil +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) { + return nil, nil +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetName() string { + return backendgroup.MasterSlaveServerGroupName +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetId() string { + return backendgroup.MasterSlaveServerGroupId +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetGlobalId() string { + return backendgroup.GetId() +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) IsEmulated() bool { + return false +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) Refresh() error { + return nil +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) IsDefault() bool { + return false +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetType() string { + return api.LB_BACKENDGROUP_TYPE_MASTER_SLAVE +} + +func (region *SRegion) GetLoadbalancerMasterSlaveBackendgroups(loadbalancerId string) ([]SLoadbalancerMasterSlaveBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + body, err := region.lbRequest("DescribeMasterSlaveServerGroups", params) + if err != nil { + return nil, err + } + backendgroups := []SLoadbalancerMasterSlaveBackendGroup{} + return backendgroups, body.Unmarshal(&backendgroups, "MasterSlaveServerGroups", "MasterSlaveServerGroup") +} + +func (region *SRegion) GetLoadbalancerMasterSlaveBackends(backendgroupId string) ([]SLoadbalancerMasterSlaveBackend, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupId"] = backendgroupId + body, err := region.lbRequest("DescribeMasterSlaveServerGroupAttribute", params) + if err != nil { + return nil, err + } + backends := []SLoadbalancerMasterSlaveBackend{} + return backends, body.Unmarshal(&backends, "MasterSlaveBackendServers", "MasterSlaveBackendServer") +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { + backends, err := backendgroup.lb.region.GetLoadbalancerMasterSlaveBackends(backendgroup.MasterSlaveServerGroupId) + if err != nil { + return nil, err + } + ibackends := []cloudprovider.ICloudLoadbalancerBackend{} + for i := 0; i < len(backends); i++ { + backends[i].lbbg = backendgroup + ibackends = append(ibackends, &backends[i]) + } + return ibackends, nil +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetILoadbalancerBackendById(backendId string) (cloudprovider.ICloudLoadbalancerBackend, error) { + backends, err := backendgroup.GetILoadbalancerBackends() + if err != nil { + return nil, err + } + for i := 0; i < len(backends); i++ { + if backends[i].GetGlobalId() == backendId { + return backends[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) CreateLoadbalancerMasterSlaveBackendGroup(name, loadbalancerId string, backends []cloudprovider.SLoadbalancerBackend) (*SLoadbalancerMasterSlaveBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupName"] = name + params["LoadBalancerId"] = loadbalancerId + if len(backends) != 2 { + return nil, fmt.Errorf("master slave backendgorup must contain two backend") + } + servers := jsonutils.NewArray() + for _, backend := range backends { + serverType := "Slave" + if backend.Index == 0 { + serverType = "Master" + } + servers.Add( + jsonutils.Marshal( + map[string]string{ + "ServerId": backend.ExternalID, + "Port": fmt.Sprintf("%d", backend.Port), + "Weight": fmt.Sprintf("%d", backend.Weight), + "ServerType": serverType, + }, + )) + } + params["MasterSlaveBackendServers"] = servers.String() + body, err := region.lbRequest("CreateMasterSlaveServerGroup", params) + if err != nil { + return nil, err + } + groupId, err := body.GetString("MasterSlaveServerGroupId") + if err != nil { + return nil, err + } + return region.GetLoadbalancerMasterSlaveBackendgroupById(groupId) +} + +func (region *SRegion) GetLoadbalancerMasterSlaveBackendgroupById(groupId string) (*SLoadbalancerMasterSlaveBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupId"] = groupId + params["NeedInstanceDetail"] = "true" + body, err := region.lbRequest("DescribeMasterSlaveServerGroupAttribute", params) + if err != nil { + return nil, err + } + group := &SLoadbalancerMasterSlaveBackendGroup{} + return group, body.Unmarshal(group) +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error { + return nil +} + +func (region *SRegion) DeleteLoadbalancerMasterSlaveBackendgroup(groupId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupId"] = groupId + _, err := region.lbRequest("DeleteMasterSlaveServerGroup", params) + return err +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) Delete(ctx context.Context) error { + return backendgroup.lb.region.DeleteLoadbalancerMasterSlaveBackendgroup(backendgroup.MasterSlaveServerGroupId) +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) RemoveBackendServer(serverId string, weight, port int) error { + return cloudprovider.ErrNotSupported +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerservercertificate.go b/pkg/multicloud/apsara/loadbalancerservercertificate.go new file mode 100644 index 0000000000..09526e5f57 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerservercertificate.go @@ -0,0 +1,141 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" +) + +type SubjectAlternativeNames struct { + SubjectAlternativeName []string +} + +type SLoadbalancerServerCertificate struct { + region *SRegion + + ServerCertificateId string // 服务器证书ID。 + ServerCertificateName string // 服务器证书名称。 + Fingerprint string // 服务器证书的指纹。 + CreateTime string // 服务器证书上传的时间。 + CreateTimeStamp uint64 // 服务器证书上传的时间戳。 + IsAliCloudCertificate int // 是否是阿里云证书。0代表不是阿里云证书。 + AliCloudCertificateName string // 阿里云证书名称。 + AliCloudCertificateId string // 阿里云证书ID。 + ExpireTime time.Time // 过期时间。 + ExpireTimeStamp uint64 // 过期时间戳。 + CommonName string // 域名,对应证书的CommonName字段。 + SubjectAlternativeNames SubjectAlternativeNames // 数组格式,返回证书的备用域名列表,对应证书的Subject Alternative Name字段,详情请参见SubjectAlternativeNames。 + ResourceGroupId string // 实例的企业资源组ID + RegionId string // 负载均衡实例的地域。 +} + +func (certificate *SLoadbalancerServerCertificate) GetPublickKey() string { + return "" +} + +func (certificate *SLoadbalancerServerCertificate) GetPrivateKey() string { + return "" +} + +func (certificate *SLoadbalancerServerCertificate) GetName() string { + return certificate.ServerCertificateName +} + +func (certificate *SLoadbalancerServerCertificate) GetId() string { + return certificate.ServerCertificateId +} + +func (certificate *SLoadbalancerServerCertificate) GetGlobalId() string { + return certificate.GetId() +} + +func (certificate *SLoadbalancerServerCertificate) GetStatus() string { + return "" +} + +func (certificate *SLoadbalancerServerCertificate) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (certificate *SLoadbalancerServerCertificate) IsEmulated() bool { + return false +} + +func (certificate *SLoadbalancerServerCertificate) GetCommonName() string { + return certificate.CommonName +} + +func (certificate *SLoadbalancerServerCertificate) GetSubjectAlternativeNames() string { + return strings.Join(certificate.SubjectAlternativeNames.SubjectAlternativeName, ",") +} + +func (certificate *SLoadbalancerServerCertificate) GetFingerprint() string { + return fmt.Sprintf("sha1:%s", strings.Replace(certificate.Fingerprint, ":", "", -1)) +} + +func (certificate *SLoadbalancerServerCertificate) GetExpireTime() time.Time { + return certificate.ExpireTime +} + +func (certificate *SLoadbalancerServerCertificate) Refresh() error { + return nil +} + +func (region *SRegion) UpdateServerCertificateName(certId, name string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ServerCertificateId"] = certId + params["ServerCertificateName"] = name + _, err := region.lbRequest("SetServerCertificateName", params) + return err +} + +func (certificate *SLoadbalancerServerCertificate) Sync(name string, privateKey string, publicKey string) error { + if certificate.ServerCertificateName != name { + return certificate.region.UpdateServerCertificateName(certificate.ServerCertificateId, name) + } + return nil +} + +func (certificate *SLoadbalancerServerCertificate) Delete() error { + return certificate.region.DeleteServerCertificate(certificate.ServerCertificateId) +} + +func (region *SRegion) DeleteServerCertificate(certId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ServerCertificateId"] = certId + _, err := region.lbRequest("DeleteServerCertificate", params) + return err +} + +func (region *SRegion) GetLoadbalancerServerCertificates() ([]SLoadbalancerServerCertificate, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + body, err := region.lbRequest("DescribeServerCertificates", params) + if err != nil { + return nil, err + } + certificates := []SLoadbalancerServerCertificate{} + return certificates, body.Unmarshal(&certificates, "ServerCertificates", "ServerCertificate") +} + +func (certificate *SLoadbalancerServerCertificate) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancertcplistener.go b/pkg/multicloud/apsara/loadbalancertcplistener.go new file mode 100644 index 0000000000..c89c115593 --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancertcplistener.go @@ -0,0 +1,360 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerTCPListener struct { + lb *SLoadbalancer + + ListenerPort int // 负载均衡实例前端使用的端口。 + BackendServerPort int // 负载均衡实例后端使用的端口。 + Bandwidth int // 监听的带宽峰值。 + Status string // 当前监听的状态,取值:starting | running | configuring | stopping | stopped + Description string + + Scheduler string // 调度算法。 + VServerGroupId string // 绑定的服务器组ID。 + MasterSlaveServerGroupId string // 绑定的主备服务器组ID。 + AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) + PersistenceTimeout int //是否开启了会话保持。取值为0时,表示没有开启。 + + AclType string // 访问控制类型 + + AclId string // 监听绑定的访问策略组ID。当AclStatus参数的值为on时,该参数必选。 + + HealthCheck string // 是否开启健康检查。 + HealthCheckType string //TCP协议监听的健康检查方式。取值:tcp | http + HealthyThreshold int // 健康检查阈值。 + UnhealthyThreshold int // 不健康检查阈值。 + HealthCheckConnectTimeout int // 每次健康检查响应的最大超时间,单位为秒。 + HealthCheckInterval int // 健康检查的时间间隔,单位为秒。 + HealthCheckConnectPort int // 健康检查的端口。 +} + +func (listener *SLoadbalancerTCPListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } + return fmt.Sprintf("TCP:%d", listener.ListenerPort) +} + +func (listerner *SLoadbalancerTCPListener) GetId() string { + return fmt.Sprintf("%s/%d", listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerTCPListener) GetGlobalId() string { + return listerner.GetId() +} + +func (listerner *SLoadbalancerTCPListener) GetStatus() string { + switch listerner.Status { + case "starting", "running": + return api.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return api.LB_STATUS_DISABLED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (listerner *SLoadbalancerTCPListener) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (listerner *SLoadbalancerTCPListener) GetEgressMbps() int { + if listerner.Bandwidth < 1 { + return 0 + } + return listerner.Bandwidth +} + +func (listerner *SLoadbalancerTCPListener) IsEmulated() bool { + return false +} + +func (listerner *SLoadbalancerTCPListener) Refresh() error { + lis, err := listerner.lb.region.GetLoadbalancerTCPListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) +} + +func (listerner *SLoadbalancerTCPListener) GetListenerType() string { + return "tcp" +} +func (listerner *SLoadbalancerTCPListener) GetListenerPort() int { + return listerner.ListenerPort +} + +func (listerner *SLoadbalancerTCPListener) GetBackendGroupId() string { + if len(listerner.VServerGroupId) > 0 { + return listerner.VServerGroupId + } + return listerner.MasterSlaveServerGroupId +} + +func (listerner *SLoadbalancerTCPListener) GetScheduler() string { + return listerner.Scheduler +} + +func (listerner *SLoadbalancerTCPListener) GetAclStatus() string { + return listerner.AclStatus +} + +func (listerner *SLoadbalancerTCPListener) GetAclType() string { + return listerner.AclType +} + +func (listerner *SLoadbalancerTCPListener) GetAclId() string { + return listerner.AclId +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheck() string { + return listerner.HealthCheck +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckType() string { + return listerner.HealthCheckType +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckDomain() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckURI() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckCode() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckRise() int { + return listerner.HealthyThreshold +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckFail() int { + return listerner.UnhealthyThreshold +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckTimeout() int { + return listerner.HealthCheckConnectTimeout +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckInterval() int { + return listerner.HealthCheckInterval +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckReq() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetHealthCheckExp() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetStickySession() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetStickySessionType() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetStickySessionCookie() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetStickySessionCookieTimeout() int { + return 0 +} + +func (listerner *SLoadbalancerTCPListener) XForwardedForEnabled() bool { + return false +} + +func (listerner *SLoadbalancerTCPListener) GzipEnabled() bool { + return false +} + +func (listerner *SLoadbalancerTCPListener) GetCertificateId() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) GetTLSCipherPolicy() string { + return "" +} + +func (listerner *SLoadbalancerTCPListener) HTTP2Enabled() bool { + return false +} + +func (listerner *SLoadbalancerTCPListener) GetBackendServerPort() int { + return listerner.BackendServerPort +} + +func (listerner *SLoadbalancerTCPListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + return []cloudprovider.ICloudLoadbalancerListenerRule{}, nil +} + +func (region *SRegion) GetLoadbalancerTCPListener(loadbalancerId string, listenerPort int) (*SLoadbalancerTCPListener, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + body, err := region.lbRequest("DescribeLoadBalancerTCPListenerAttribute", params) + if err != nil { + return nil, err + } + listener := SLoadbalancerTCPListener{} + return &listener, body.Unmarshal(&listener) +} + +func (region *SRegion) constructBaseCreateListenerParams(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) map[string]string { + params := map[string]string{} + params["RegionId"] = region.RegionId + if listener.EgressMbps < 1 { + listener.EgressMbps = -1 + } + params["Bandwidth"] = fmt.Sprintf("%d", listener.EgressMbps) + params["ListenerPort"] = fmt.Sprintf("%d", listener.ListenerPort) + params["LoadBalancerId"] = lb.LoadBalancerId + if len(listener.AccessControlListID) > 0 { + params["AclId"] = listener.AccessControlListID + } + if utils.IsInStringArray(listener.AccessControlListStatus, []string{"on", "off"}) { + params["AclStatus"] = listener.AccessControlListStatus + } + if utils.IsInStringArray(listener.AccessControlListType, []string{"white", "black"}) { + params["AclType"] = listener.AccessControlListType + } + switch listener.BackendGroupType { + case api.LB_BACKENDGROUP_TYPE_NORMAL: + params["VServerGroupId"] = listener.BackendGroupID + params["VServerGroup"] = "on" + case api.LB_BACKENDGROUP_TYPE_MASTER_SLAVE: + params["MasterSlaveServerGroupId"] = listener.BackendGroupID + params["MasterSlaveServerGroup"] = "on" + case api.LB_BACKENDGROUP_TYPE_DEFAULT: + params["BackendServerPort"] = fmt.Sprintf("%d", listener.BackendServerPort) + } + if len(listener.Name) > 0 { + params["Description"] = listener.Name + } + + if utils.IsInStringArray(listener.ListenerType, []string{api.LB_LISTENER_TYPE_TCP, api.LB_LISTENER_TYPE_UDP}) { + if listener.HealthCheckTimeout >= 1 && listener.HealthCheckTimeout <= 300 { + params["HealthCheckConnectTimeout"] = fmt.Sprintf("%d", listener.HealthCheckTimeout) + } + } + switch listener.ListenerType { + case api.LB_LISTENER_TYPE_UDP: + if len(listener.HealthCheckReq) > 0 { + params["healthCheckReq"] = listener.HealthCheckReq + } + if len(listener.HealthCheckExp) > 0 { + params["healthCheckExp"] = listener.HealthCheckExp + } + } + + if len(listener.HealthCheckDomain) > 0 { + params["HealthCheckDomain"] = listener.HealthCheckDomain + } + + if len(listener.HealthCheckHttpCode) > 0 { + params["HealthCheckHttpCode"] = listener.HealthCheckHttpCode + } + + if len(listener.HealthCheckURI) > 0 { + params["HealthCheckURI"] = listener.HealthCheckURI + } + + if listener.HealthCheckRise >= 2 && listener.HealthCheckRise <= 10 { + params["HealthyThreshold"] = fmt.Sprintf("%d", listener.HealthCheckRise) + } + + if listener.HealthCheckFail >= 2 && listener.HealthCheckFail <= 10 { + params["UnhealthyThreshold"] = fmt.Sprintf("%d", listener.HealthCheckFail) + } + + if listener.HealthCheckInterval >= 1 && listener.HealthCheckInterval <= 50 { + params["healthCheckInterval"] = fmt.Sprintf("%d", listener.HealthCheckInterval) + } + + params["Scheduler"] = listener.Scheduler + return params +} + +func (region *SRegion) CreateLoadbalancerTCPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("CreateLoadBalancerTCPListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerTCPListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerTCPListener) Delete(ctx context.Context) error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerTCPListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerTCPListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerTCPListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerTCPListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerTCPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("SetLoadBalancerTCPListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerTCPListener) Sync(ctx context.Context, lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerTCPListener(listerner.lb, lblis) +} + +func (listerner *SLoadbalancerTCPListener) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/loadbalancerudplistener.go b/pkg/multicloud/apsara/loadbalancerudplistener.go new file mode 100644 index 0000000000..bb62f5f15b --- /dev/null +++ b/pkg/multicloud/apsara/loadbalancerudplistener.go @@ -0,0 +1,286 @@ +// 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 apsara + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerUDPListener struct { + lb *SLoadbalancer + + ListenerPort int // 负载均衡实例前端使用的端口。 + BackendServerPort int // 负载均衡实例后端使用的端口。 + Bandwidth int // 监听的带宽峰值。 + Status string // 当前监听的状态,取值:starting | running | configuring | stopping | stopped + Description string + + Scheduler string // 调度算法 + VServerGroupId string // 绑定的服务器组ID。 + MasterSlaveServerGroupId string // 绑定的主备服务器组ID。 + AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) + + AclType string // 访问控制类型: + + AclId string // 监听绑定的访问策略组ID。当AclStatus参数的值为on时,该参数必选。 + + HealthCheck string // 是否开启健康检查。 + HealthyThreshold int // 健康检查阈值。 + UnhealthyThreshold int // 不健康检查阈值。 + HealthCheckConnectTimeout int // 每次健康检查响应的最大超时间,单位为秒。 + HealthCheckInterval int // 健康检查的时间间隔,单位为秒。 + HealthCheckConnectPort int // 健康检查的端口。 + + HealthCheckExp string // UDP监听健康检查的响应串 + HealthCheckReq string // UDP监听健康检查的请求串 +} + +func (listener *SLoadbalancerUDPListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } + return fmt.Sprintf("UDP:%d", listener.ListenerPort) +} + +func (listerner *SLoadbalancerUDPListener) GetId() string { + return fmt.Sprintf("%s/%d", listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerUDPListener) GetGlobalId() string { + return listerner.GetId() +} + +func (listerner *SLoadbalancerUDPListener) GetStatus() string { + switch listerner.Status { + case "starting", "running": + return api.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return api.LB_STATUS_DISABLED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (listerner *SLoadbalancerUDPListener) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (listerner *SLoadbalancerUDPListener) GetEgressMbps() int { + if listerner.Bandwidth < 1 { + return 0 + } + return listerner.Bandwidth +} + +func (listerner *SLoadbalancerUDPListener) IsEmulated() bool { + return false +} + +func (listerner *SLoadbalancerUDPListener) Refresh() error { + lis, err := listerner.lb.region.GetLoadbalancerUDPListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) +} + +func (listerner *SLoadbalancerUDPListener) GetListenerType() string { + return "udp" +} + +func (listerner *SLoadbalancerUDPListener) GetListenerPort() int { + return listerner.ListenerPort +} + +func (listerner *SLoadbalancerUDPListener) GetBackendGroupId() string { + if len(listerner.VServerGroupId) > 0 { + return listerner.VServerGroupId + } + return listerner.MasterSlaveServerGroupId +} + +func (listerner *SLoadbalancerUDPListener) GetBackendServerPort() int { + return listerner.BackendServerPort +} + +func (listerner *SLoadbalancerUDPListener) GetScheduler() string { + return listerner.Scheduler +} + +func (listerner *SLoadbalancerUDPListener) GetAclStatus() string { + return listerner.AclStatus +} + +func (listerner *SLoadbalancerUDPListener) GetAclType() string { + return listerner.AclType +} + +func (listerner *SLoadbalancerUDPListener) GetAclId() string { + return listerner.AclId +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheck() string { + return listerner.HealthCheck +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckType() string { + return api.LB_HEALTH_CHECK_UDP +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckDomain() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckURI() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckCode() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckRise() int { + return listerner.HealthyThreshold +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckFail() int { + return listerner.UnhealthyThreshold +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckTimeout() int { + return listerner.HealthCheckConnectTimeout +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckInterval() int { + return listerner.HealthCheckInterval +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckReq() string { + return listerner.HealthCheckReq +} + +func (listerner *SLoadbalancerUDPListener) GetHealthCheckExp() string { + return listerner.HealthCheckExp +} + +func (listerner *SLoadbalancerUDPListener) GetStickySession() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetStickySessionType() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetStickySessionCookie() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetStickySessionCookieTimeout() int { + return 0 +} + +func (listerner *SLoadbalancerUDPListener) XForwardedForEnabled() bool { + return false +} + +func (listerner *SLoadbalancerUDPListener) GzipEnabled() bool { + return false +} + +func (listerner *SLoadbalancerUDPListener) GetCertificateId() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) GetTLSCipherPolicy() string { + return "" +} + +func (listerner *SLoadbalancerUDPListener) HTTP2Enabled() bool { + return false +} + +func (listerner *SLoadbalancerUDPListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + return []cloudprovider.ICloudLoadbalancerListenerRule{}, nil +} + +func (region *SRegion) GetLoadbalancerUDPListener(loadbalancerId string, listenerPort int) (*SLoadbalancerUDPListener, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + body, err := region.lbRequest("DescribeLoadBalancerUDPListenerAttribute", params) + if err != nil { + return nil, err + } + listener := SLoadbalancerUDPListener{} + return &listener, body.Unmarshal(&listener) +} + +func (region *SRegion) CreateLoadbalancerUDPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("CreateLoadBalancerUDPListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerUDPListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerUDPListener) Delete(ctx context.Context) error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerUDPListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerUDPListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerUDPListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerUDPListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerUDPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("SetLoadBalancerUDPListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerUDPListener) Sync(ctx context.Context, lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerUDPListener(listerner.lb, lblis) +} + +func (listerner *SLoadbalancerUDPListener) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/apsara/monitor.go b/pkg/multicloud/apsara/monitor.go new file mode 100644 index 0000000000..9f7201f194 --- /dev/null +++ b/pkg/multicloud/apsara/monitor.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 apsara + +import ( + "strconv" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" +) + +const ( + APSARA_API_VERSION_METRICS = "2019-01-01" +) + +func (r *SRegion) metricsRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := r.getSdkClient() + if err != nil { + return nil, errors.Wrap(err, "r.getSdkClient") + } + return r.productRequest(client, APSARA_PRODUCT_METRICS, r.client.endpoints.MetricsEndpoint, APSARA_API_VERSION_METRICS, action, params, r.client.debug) +} + +type SResourceLabel struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type SResource struct { + Description string `json:"Description"` + Labels string `json:"Labels"` + Namespace string `json:"Namespace"` +} + +func (r *SRegion) DescribeProjectMeta(limit, offset int) (int, []SResource, error) { + params := make(map[string]string) + if limit <= 0 { + limit = 30 + } + params["PageSize"] = strconv.FormatInt(int64(limit), 10) + if offset > 0 { + pageNum := (offset / limit) + 1 + params["PageNumber"] = strconv.FormatInt(int64(pageNum), 10) + } + body, err := r.metricsRequest("DescribeProjectMeta", params) + if err != nil { + return 0, nil, errors.Wrap(err, "r.metricsRequest DescribeProjectMeta") + } + total, _ := body.Int("Total") + res := make([]SResource, 0) + err = body.Unmarshal(&res, "Resources", "Resource") + if err != nil { + return 0, nil, errors.Wrap(err, "body.Unmarshal Resources Resource") + } + return int(total), res, nil +} + +func (r *SRegion) FetchNamespaces() ([]SResource, error) { + resources := make([]SResource, 0) + total := -1 + for total < 0 || len(resources) < total { + ntotal, res, err := r.DescribeProjectMeta(1000, len(resources)) + if err != nil { + return nil, errors.Wrap(err, "r.DescribeProjectMeta") + } + if len(res) == 0 { + break + } + resources = append(resources, res...) + total = ntotal + } + return resources, nil +} + +type SMetricMeta struct { + Description string `json:"Description"` + MetricName string `json:"MetricName"` + Statistics string `json:"Statistics"` + Labels string `json:"Labels"` + Dimensions string `json:"Dimensions"` + Namespace string `json:"Namespace"` + Periods string `json:"Periods"` + Unit string `json:"Unit"` +} + +func (r *SRegion) DescribeMetricMetaList(ns string, limit, offset int) (int, []SMetricMeta, error) { + params := make(map[string]string) + if limit <= 0 { + limit = 30 + } + params["Namespace"] = ns + params["PageSize"] = strconv.FormatInt(int64(limit), 10) + if offset > 0 { + pageNum := (offset / limit) + 1 + params["PageNumber"] = strconv.FormatInt(int64(pageNum), 10) + } + body, err := r.metricsRequest("DescribeMetricMetaList", params) + if err != nil { + return 0, nil, errors.Wrap(err, "r.metricsRequest DescribeMetricMetaList") + } + total, _ := body.Int("TotalCount") + res := make([]SMetricMeta, 0) + err = body.Unmarshal(&res, "Resources", "Resource") + if err != nil { + return 0, nil, errors.Wrap(err, "body.Unmarshal Resources Resource") + } + return int(total), res, nil +} + +func (r *SRegion) FetchMetrics(ns string) ([]SMetricMeta, error) { + metrics := make([]SMetricMeta, 0) + total := -1 + for total < 0 || len(metrics) < total { + ntotal, res, err := r.DescribeMetricMetaList(ns, 1000, len(metrics)) + if err != nil { + return nil, errors.Wrap(err, "r.DescribeMetricMetaList") + } + if len(res) == 0 { + break + } + metrics = append(metrics, res...) + total = ntotal + } + return metrics, nil +} + +func (r *SRegion) DescribeMetricList(name string, ns string, since time.Time, until time.Time, nextToken string) ([]jsonutils.JSONObject, string, error) { + params := make(map[string]string) + params["MetricName"] = name + params["Namespace"] = ns + params["Length"] = "2000" + if len(nextToken) > 0 { + params["NextToken"] = nextToken + } + if !since.IsZero() { + params["StartTime"] = strconv.FormatInt(since.Unix()*1000, 10) + } + if !until.IsZero() { + params["EndTime"] = strconv.FormatInt(until.Unix()*1000, 10) + } + body, err := r.metricsRequest("DescribeMetricList", params) + if err != nil { + return nil, "", errors.Wrap(err, "region.MetricRequest") + } + nToken, _ := body.GetString("NextToken") + dataStr, _ := body.GetString("Datapoints") + if len(dataStr) == 0 { + return nil, "", nil + } + dataJson, err := jsonutils.ParseString(dataStr) + if err != nil { + return nil, "", errors.Wrap(err, "jsonutils.ParseString") + } + dataArray, err := dataJson.GetArray() + if err != nil { + return nil, "", errors.Wrap(err, "dataJson.GetArray") + } + return dataArray, nToken, nil +} + +func (r *SRegion) FetchMetricData(name string, ns string, since time.Time, until time.Time) ([]jsonutils.JSONObject, error) { + data := make([]jsonutils.JSONObject, 0) + nextToken := "" + for { + datArray, next, err := r.DescribeMetricList(name, ns, since, until, nextToken) + if err != nil { + return nil, errors.Wrap(err, "r.DescribeMetricList") + } + data = append(data, datArray...) + if len(next) == 0 { + break + } + nextToken = next + } + return data, nil +} diff --git a/pkg/multicloud/apsara/natdtable.go b/pkg/multicloud/apsara/natdtable.go new file mode 100644 index 0000000000..294503284f --- /dev/null +++ b/pkg/multicloud/apsara/natdtable.go @@ -0,0 +1,186 @@ +// 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 apsara + +import ( + "fmt" + "strconv" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SForwardTableEntry struct { + multicloud.SResourceBase + nat *SNatGetway + + ForwardEntryId string + ForwardEntryName string + IpProtocol string + Status string + ExternalIp string + ForwardTableId string + ExternalPort string + InternalPort string + InternalIp string +} + +func (dtable *SForwardTableEntry) GetName() string { + if len(dtable.ForwardEntryName) > 0 { + return dtable.ForwardEntryName + } + return dtable.ForwardEntryId +} + +func (dtable *SForwardTableEntry) GetId() string { + return dtable.ForwardEntryId +} + +func (dtable *SForwardTableEntry) GetGlobalId() string { + return dtable.ForwardEntryId +} + +func (dtable *SForwardTableEntry) GetStatus() string { + switch dtable.Status { + case "Available": + return api.NAT_STAUTS_AVAILABLE + default: + return api.NAT_STATUS_UNKNOWN + } +} + +func (dtable *SForwardTableEntry) GetIpProtocol() string { + return dtable.IpProtocol +} + +func (dtable *SForwardTableEntry) GetExternalIp() string { + return dtable.ExternalIp +} + +func (dtable *SForwardTableEntry) GetExternalPort() int { + port, _ := strconv.Atoi(dtable.ExternalPort) + return port +} + +func (dtable *SForwardTableEntry) GetInternalIp() string { + return dtable.InternalIp +} + +func (dtable *SForwardTableEntry) GetInternalPort() int { + port, _ := strconv.Atoi(dtable.InternalPort) + return port +} + +func (region *SRegion) GetAllDTables(tableId string) ([]SForwardTableEntry, error) { + dtables := []SForwardTableEntry{} + for { + part, total, err := region.GetForwardTableEntries(tableId, len(dtables), 50) + if err != nil { + return nil, err + } + dtables = append(dtables, part...) + if len(dtables) >= total { + break + } + } + return dtables, nil +} + +func (dtable *SForwardTableEntry) Delete() error { + return dtable.nat.vpc.region.DeleteForwardTableEntry(dtable.ForwardTableId, dtable.GetId()) +} + +func (region *SRegion) GetForwardTableEntries(tableId string, offset int, limit int) ([]SForwardTableEntry, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + params["ForwardTableId"] = tableId + + body, err := region.vpcRequest("DescribeForwardTableEntries", params) + if err != nil { + return nil, 0, err + } + + dtables := []SForwardTableEntry{} + err = body.Unmarshal(&dtables, "ForwardTableEntries", "ForwardTableEntry") + if err != nil { + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return dtables, int(total), nil +} + +func (region *SRegion) GetForwardTableEntry(tableID, forwardEntryID string) (SForwardTableEntry, error) { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["ForwardTableId"] = tableID + params["ForwardEntryId"] = forwardEntryID + body, err := region.vpcRequest("DescribeForwardTableEntries", params) + if err != nil { + return SForwardTableEntry{}, err + } + + dtables := []SForwardTableEntry{} + err = body.Unmarshal(&dtables, "ForwardTableEntries", "ForwardTableEntry") + if err != nil { + return SForwardTableEntry{}, err + } + if len(dtables) == 0 { + return SForwardTableEntry{}, cloudprovider.ErrNotFound + } + return dtables[0], nil +} + +func (region *SRegion) DeleteForwardTableEntry(tableId string, entryId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["ForwardTableId"] = tableId + params["ForwardEntryId"] = entryId + _, err := region.vpcRequest("DeleteForwardEntry", params) + return err +} + +func (region *SRegion) CreateForwardTableEntry(rule cloudprovider.SNatDRule, tableID string) (string, error) { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["ForwardTableId"] = tableID + params["ExternalIp"] = rule.ExternalIP + params["ExternalPort"] = strconv.Itoa(rule.ExternalPort) + params["InternalIp"] = rule.InternalIP + params["InternalPort"] = strconv.Itoa(rule.InternalPort) + params["IpProtocol"] = rule.Protocol + body, err := region.vpcRequest("CreateForwardEntry", params) + if err != nil { + return "", err + } + + entryID, _ := body.GetString("ForwardEntryId") + return entryID, nil +} + +func (dtable *SForwardTableEntry) Refresh() error { + new, err := dtable.nat.vpc.region.GetForwardTableEntry(dtable.ForwardTableId, dtable.ForwardEntryId) + if err != nil { + return err + } + return jsonutils.Update(dtable, new) +} diff --git a/pkg/multicloud/apsara/natgateway.go b/pkg/multicloud/apsara/natgateway.go new file mode 100644 index 0000000000..d0635317ba --- /dev/null +++ b/pkg/multicloud/apsara/natgateway.go @@ -0,0 +1,222 @@ +// 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 apsara + +import ( + "fmt" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SBandwidthPackageIds struct { + BandwidthPackageId []string +} + +type SForwardTableIds struct { + ForwardTableId []string +} + +type SSnatTableIds struct { + SnatTableId []string +} + +type SNatGetway struct { + multicloud.SNatGatewayBase + + vpc *SVpc + + BandwidthPackageIds SBandwidthPackageIds + BusinessStatus string + CreationTime time.Time + ExpiredTime time.Time + Description string + ForwardTableIds SForwardTableIds + SnatTableIds SSnatTableIds + InstanceChargeType TChargeType + Name string + NatGatewayId string + RegionId string + Spec string + Status string + VpcId string +} + +func (nat *SNatGetway) GetId() string { + return nat.NatGatewayId +} + +func (nat *SNatGetway) GetGlobalId() string { + return nat.NatGatewayId +} + +func (nat *SNatGetway) GetName() string { + if len(nat.Name) > 0 { + return nat.Name + } + return nat.NatGatewayId +} + +func (nat *SNatGetway) GetStatus() string { + switch nat.Status { + case "Initiating": + return api.NAT_STATUS_ALLOCATE + case "Available": + return api.NAT_STAUTS_AVAILABLE + case "Pending": + return api.NAT_STATUS_DEPLOYING + default: + return api.NAT_STATUS_UNKNOWN + } + +} + +func (nat *SNatGetway) GetBillingType() string { + return convertChargeType(nat.InstanceChargeType) +} + +func (nat *SNatGetway) GetNatSpec() string { + return nat.Spec +} + +func (nat *SNatGetway) GetCreatedAt() time.Time { + return nat.CreationTime +} + +func (nat *SNatGetway) GetExpiredAt() time.Time { + return nat.ExpiredTime +} + +func (nat *SNatGetway) GetIEips() ([]cloudprovider.ICloudEIP, error) { + eips := []SEipAddress{} + for { + parts, total, err := nat.vpc.region.GetEips("", nat.NatGatewayId, len(eips), 50) + if err != nil { + return nil, err + } + eips = append(eips, parts...) + if len(eips) >= total { + break + } + } + ieips := []cloudprovider.ICloudEIP{} + for i := 0; i < len(eips); i++ { + eips[i].region = nat.vpc.region + ieips = append(ieips, &eips[i]) + } + return ieips, nil +} + +func (nat *SNatGetway) GetINatDTable() ([]cloudprovider.ICloudNatDEntry, error) { + itables := []cloudprovider.ICloudNatDEntry{} + for _, dtableId := range nat.ForwardTableIds.ForwardTableId { + dtables, err := nat.vpc.region.GetAllDTables(dtableId) + if err != nil { + return nil, err + } + for i := 0; i < len(dtables); i++ { + dtables[i].nat = nat + itables = append(itables, &dtables[i]) + } + } + return itables, nil +} + +func (nat *SNatGetway) GetINatSTable() ([]cloudprovider.ICloudNatSEntry, error) { + stables, err := nat.getSnatEntries() + if err != nil { + return nil, err + } + itables := []cloudprovider.ICloudNatSEntry{} + for i := 0; i < len(stables); i++ { + stables[i].nat = nat + itables = append(itables, &stables[i]) + } + return itables, nil +} + +func (nat *SNatGetway) GetINatDEntryByID(id string) (cloudprovider.ICloudNatDEntry, error) { + dNATEntry, err := nat.vpc.region.GetForwardTableEntry(nat.ForwardTableIds.ForwardTableId[0], id) + if err != nil { + return nil, cloudprovider.ErrNotFound + } + dNATEntry.nat = nat + return &dNATEntry, nil +} + +func (nat *SNatGetway) GetINatSEntryByID(id string) (cloudprovider.ICloudNatSEntry, error) { + sNATEntry, err := nat.vpc.region.GetSNATEntry(nat.SnatTableIds.SnatTableId[0], id) + if err != nil { + return nil, cloudprovider.ErrNotFound + } + sNATEntry.nat = nat + return &sNATEntry, nil +} + +func (nat *SNatGetway) CreateINatDEntry(rule cloudprovider.SNatDRule) (cloudprovider.ICloudNatDEntry, error) { + entryID, err := nat.vpc.region.CreateForwardTableEntry(rule, nat.ForwardTableIds.ForwardTableId[0]) + if err != nil { + return nil, errors.Wrapf(err, `create dnat rule for nat gateway %q`, nat.GetId()) + } + return nat.GetINatDEntryByID(entryID) +} + +func (nat *SNatGetway) CreateINatSEntry(rule cloudprovider.SNatSRule) (cloudprovider.ICloudNatSEntry, error) { + entryID, err := nat.vpc.region.CreateSNATTableEntry(rule, nat.SnatTableIds.SnatTableId[0]) + if err != nil { + return nil, errors.Wrapf(err, `create snat rule for nat gateway %q`, nat.GetId()) + } + return nat.GetINatSEntryByID(entryID) +} + +func (self *SRegion) GetNatGateways(vpcId string, natGwId string, offset, limit int) ([]SNatGetway, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if len(vpcId) > 0 { + params["VpcId"] = vpcId + } + if len(natGwId) > 0 { + params["NatGatewayId"] = natGwId + } + + body, err := self.vpcRequest("DescribeNatGateways", params) + if err != nil { + log.Errorf("GetVSwitches fail %s", err) + return nil, 0, err + } + + if self.client.debug { + log.Debugf("%s", body.PrettyString()) + } + + gateways := make([]SNatGetway, 0) + err = body.Unmarshal(&gateways, "NatGateways", "NatGateway") + if err != nil { + log.Errorf("Unmarshal gateways fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return gateways, int(total), nil +} diff --git a/pkg/multicloud/apsara/natstable.go b/pkg/multicloud/apsara/natstable.go new file mode 100644 index 0000000000..444b1387ae --- /dev/null +++ b/pkg/multicloud/apsara/natstable.go @@ -0,0 +1,220 @@ +// 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 apsara + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SSNATTableEntry struct { + multicloud.SResourceBase + nat *SNatGetway + + SnatEntryId string + SnatEntryName string + SnatIp string + SnatTableId string `json:"snat_table_id"` + SourceCIDR string `json:"source_cidr"` + SourceVSwitchId string `json:"source_vswitch_id"` + Status string +} + +func (stable *SSNATTableEntry) GetName() string { + if len(stable.SnatEntryName) > 0 { + return stable.SnatEntryName + } + return stable.SnatEntryId +} + +func (stable *SSNATTableEntry) GetId() string { + return stable.SnatEntryId +} + +func (stable *SSNATTableEntry) GetGlobalId() string { + return stable.SnatEntryId +} + +func (stable *SSNATTableEntry) GetStatus() string { + switch stable.Status { + case "Available": + return api.NAT_STAUTS_AVAILABLE + default: + return api.NAT_STATUS_UNKNOWN + } +} + +func (stable *SSNATTableEntry) GetIP() string { + return stable.SnatIp +} + +func (stable *SSNATTableEntry) GetSourceCIDR() string { + return stable.SourceCIDR +} + +func (stable *SSNATTableEntry) GetNetworkId() string { + return stable.SourceVSwitchId +} + +func (stable *SSNATTableEntry) Delete() error { + return stable.nat.vpc.region.DeleteSnatEntry(stable.SnatTableId, stable.GetId()) +} + +func (self *SRegion) GetSNATEntries(tableId string, offset, limit int) ([]SSNATTableEntry, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + params["SnatTableId"] = tableId + + body, err := self.vpcRequest("DescribeSnatTableEntries", params) + if err != nil { + log.Errorf("DescribeSnatTableEntries fail %s", err) + return nil, 0, err + } + + if self.client.debug { + log.Debugf("%s", body.PrettyString()) + } + + entries := make([]SSNATTableEntry, 0) + err = body.Unmarshal(&entries, "SnatTableEntries", "SnatTableEntry") + if err != nil { + log.Errorf("Unmarshal entries fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return entries, int(total), nil +} + +func (self *SRegion) GetSNATEntry(tableID, SNATEntryID string) (SSNATTableEntry, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["SnatTableId"] = tableID + params["SnatEntryId"] = SNATEntryID + + body, err := self.vpcRequest("DescribeSnatTableEntries", params) + if err != nil { + log.Errorf("DescribeSnatTableEntries fail %s", err) + return SSNATTableEntry{}, err + } + + if self.client.debug { + log.Debugf("%s", body.PrettyString()) + } + + entries := make([]SSNATTableEntry, 0) + err = body.Unmarshal(&entries, "SnatTableEntries", "SnatTableEntry") + if err != nil { + log.Errorf("Unmarshal entries fail %s", err) + return SSNATTableEntry{}, err + } + if len(entries) == 0 { + return SSNATTableEntry{}, cloudprovider.ErrNotFound + } + return entries[0], nil + +} + +func (region *SRegion) CreateSNATTableEntry(rule cloudprovider.SNatSRule, tableID string) (string, error) { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["SnatTableId"] = tableID + params["SnatIp"] = rule.ExternalIP + if len(rule.NetworkID) != 0 { + params["SourceVSwitchId"] = rule.NetworkID + } + if len(rule.SourceCIDR) != 0 { + params["SourceCIDR"] = rule.SourceCIDR + } + body, err := region.vpcRequest("CreateSnatEntry", params) + if err != nil { + return "", err + } + + entryID, _ := body.GetString("SnatEntryId") + return entryID, nil +} + +func (region *SRegion) DeleteSnatEntry(tableId string, entryId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["SnatTableId"] = tableId + params["SnatEntryId"] = entryId + _, err := region.vpcRequest("DeleteSnatEntry", params) + return err +} + +func (nat *SNatGetway) getSnatEntriesForTable(tblId string) ([]SSNATTableEntry, error) { + entries := make([]SSNATTableEntry, 0) + entryTotal := -1 + for entryTotal < 0 || len(entries) < entryTotal { + parts, total, err := nat.vpc.region.GetSNATEntries(tblId, len(entries), 50) + if err != nil { + return nil, err + } + if len(parts) > 0 { + entries = append(entries, parts...) + } + entryTotal = total + } + return entries, nil +} + +func (nat *SNatGetway) getSnatEntries() ([]SSNATTableEntry, error) { + entries := make([]SSNATTableEntry, 0) + for i := range nat.SnatTableIds.SnatTableId { + sentries, err := nat.getSnatEntriesForTable(nat.SnatTableIds.SnatTableId[i]) + if err != nil { + return nil, err + } + entries = append(entries, sentries...) + } + return entries, nil +} + +func (nat *SNatGetway) dissociateWithVswitch(vswitchId string) error { + entries, err := nat.getSnatEntries() + if err != nil { + return err + } + for i := range entries { + log.Debugf("%v", entries[i]) + if entries[i].SourceVSwitchId == vswitchId { + err := nat.vpc.region.DeleteSnatEntry(entries[i].SnatTableId, entries[i].SnatEntryId) + if err != nil { + return err + } + } + } + return nil +} + +func (stable *SSNATTableEntry) Refresh() error { + new, err := stable.nat.vpc.region.GetSNATEntry(stable.SnatTableId, stable.SnatEntryId) + if err != nil { + return err + } + return jsonutils.Update(stable, new) +} diff --git a/pkg/multicloud/apsara/networkinterfaces.go b/pkg/multicloud/apsara/networkinterfaces.go new file mode 100644 index 0000000000..62b6768794 --- /dev/null +++ b/pkg/multicloud/apsara/networkinterfaces.go @@ -0,0 +1,166 @@ +// 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 apsara + +import ( + "fmt" + "time" + + "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 SPrivateIp struct { + nic *SNetworkInterface + Primary bool + PrivateIpAddress string +} + +func (ip *SPrivateIp) GetGlobalId() string { + return ip.PrivateIpAddress +} + +func (ip *SPrivateIp) GetINetworkId() string { + return ip.nic.VSwitchId +} + +func (ip *SPrivateIp) GetIP() string { + return ip.PrivateIpAddress +} + +func (ip *SPrivateIp) IsPrimary() bool { + return ip.Primary +} + +type SPrivateIpSets struct { + PrivateIpSet []SPrivateIp +} + +type SNetworkInterface struct { + multicloud.SNetworkInterfaceBase + region *SRegion + + InstanceId string + CreationTime time.Time + MacAddress string + NetworkInterfaceName string + PrivateIpSets SPrivateIpSets + ResourceGroupId string + SecurityGroupIds SSecurityGroupIds + Status string + Type string + VSwitchId string + VpcId string + ZoneId string + NetworkInterfaceId string + PrimaryIpAddress string + PrivateIpAddress string +} + +func (nic *SNetworkInterface) GetName() string { + return nic.NetworkInterfaceName +} + +func (nic *SNetworkInterface) GetId() string { + return nic.NetworkInterfaceId +} + +func (nic *SNetworkInterface) GetGlobalId() string { + return nic.NetworkInterfaceId +} + +func (nic *SNetworkInterface) GetAssociateId() string { + return nic.InstanceId +} + +func (nic *SNetworkInterface) GetAssociateType() string { + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_SERVER +} + +func (nic *SNetworkInterface) GetMacAddress() string { + return nic.MacAddress +} + +func (nic *SNetworkInterface) GetStatus() string { + switch nic.Status { + case "Available": + return api.NETWORK_INTERFACE_STATUS_AVAILABLE + } + return nic.Status +} + +func (region *SRegion) GetINetworkInterfaces() ([]cloudprovider.ICloudNetworkInterface, error) { + interfaces := []SNetworkInterface{} + for { + parts, total, err := region.GetNetworkInterfaces("", len(interfaces), 50) + if err != nil { + return nil, err + } + interfaces = append(interfaces, parts...) + if len(interfaces) >= total { + break + } + } + ret := []cloudprovider.ICloudNetworkInterface{} + for i := 0; i < len(interfaces); i++ { + // 阿里云实例的弹性网卡已经在guestnetwork同步了 + if len(interfaces[i].InstanceId) == 0 { + interfaces[i].region = region + ret = append(ret, &interfaces[i]) + } + } + return ret, nil +} + +func (nic *SNetworkInterface) GetICloudInterfaceAddresses() ([]cloudprovider.ICloudInterfaceAddress, error) { + address := []cloudprovider.ICloudInterfaceAddress{} + for i := 0; i < len(nic.PrivateIpSets.PrivateIpSet); i++ { + nic.PrivateIpSets.PrivateIpSet[i].nic = nic + address = append(address, &nic.PrivateIpSets.PrivateIpSet[i]) + } + return address, nil +} + +func (region *SRegion) GetNetworkInterfaces(instanceId string, offset int, limit int) ([]SNetworkInterface, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + + params := map[string]string{ + "RegionId": region.RegionId, + "PageSize": fmt.Sprintf("%d", limit), + "PageNumber": fmt.Sprintf("%d", (offset/limit)+1), + } + + if len(instanceId) > 0 { + params["InstanceId"] = instanceId + } + + body, err := region.ecsRequest("DescribeNetworkInterfaces", params) + if err != nil { + return nil, 0, errors.Wrap(err, "DescribeNetworkInterfaces") + } + + interfaces := []SNetworkInterface{} + err = body.Unmarshal(&interfaces, "NetworkInterfaceSets", "NetworkInterfaceSet") + if err != nil { + return nil, 0, errors.Wrap(err, "Unmarshal") + } + total, _ := body.Int("TotalCount") + return interfaces, int(total), nil +} diff --git a/pkg/multicloud/apsara/objects.go b/pkg/multicloud/apsara/objects.go new file mode 100644 index 0000000000..9c488b1ff4 --- /dev/null +++ b/pkg/multicloud/apsara/objects.go @@ -0,0 +1,112 @@ +// 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 apsara + +import ( + "context" + "net/http" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +const ( + OSS_META_HEADER = "x-oss-meta-" +) + +type SObject struct { + bucket *SBucket + + cloudprovider.SBaseCloudObject +} + +func (o *SObject) GetIBucket() cloudprovider.ICloudBucket { + return o.bucket +} + +func (o *SObject) GetAcl() cloudprovider.TBucketACLType { + acl := cloudprovider.ACLPrivate + osscli, err := o.bucket.region.GetOssClient() + if err != nil { + log.Errorf("o.bucket.region.GetOssClient error %s", err) + return acl + } + bucket, err := osscli.Bucket(o.bucket.Name) + if err != nil { + log.Errorf("osscli.Bucket error %s", err) + return acl + } + result, err := bucket.GetObjectACL(o.Key) + if err != nil { + log.Errorf("bucket.GetObjectACL error %s", err) + return acl + } + if result.ACL == string(oss.ACLDefault) { + return o.bucket.GetAcl() + } + acl = cloudprovider.TBucketACLType(result.ACL) + return acl +} + +func (o *SObject) SetAcl(aclStr cloudprovider.TBucketACLType) error { + acl, err := str2Acl(string(aclStr)) + if err != nil { + return errors.Wrap(err, "str2Acl") + } + osscli, err := o.bucket.region.GetOssClient() + if err != nil { + return errors.Wrap(err, "o.bucket.region.GetOssClient") + } + bucket, err := osscli.Bucket(o.bucket.Name) + if err != nil { + return errors.Wrap(err, "osscli.Bucket") + } + err = bucket.SetObjectACL(o.Key, acl) + if err != nil { + return errors.Wrap(err, "bucket.SetObjectACL") + } + return nil +} + +func (o *SObject) GetMeta() http.Header { + if o.Meta != nil { + return o.Meta + } + osscli, err := o.bucket.region.GetOssClient() + if err != nil { + log.Errorf("o.bucket.region.GetOssClient error %s", err) + return nil + } + bucket, err := osscli.Bucket(o.bucket.Name) + if err != nil { + log.Errorf("osscli.Bucket error %s", err) + return nil + } + result, err := bucket.GetObjectDetailedMeta(o.Key) + if err != nil { + log.Errorf("bucket.GetObjectACL error %s", err) + return nil + } + o.Meta = cloudprovider.FetchMetaFromHttpHeader(OSS_META_HEADER, result) + return o.Meta +} + +func (o *SObject) SetMeta(ctx context.Context, meta http.Header) error { + return cloudprovider.ObjectSetMeta(ctx, o.bucket, o, meta) +} diff --git a/pkg/multicloud/apsara/project.go b/pkg/multicloud/apsara/project.go new file mode 100644 index 0000000000..49b483420d --- /dev/null +++ b/pkg/multicloud/apsara/project.go @@ -0,0 +1,144 @@ +// 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 apsara + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SResourceGroup struct { + multicloud.SResourceBase + client *SApsaraClient + + Status string + AccountId string + DisplayName string + Id string + CreateDate time.Time + Name string +} + +func (self *SResourceGroup) GetGlobalId() string { + return self.Id +} + +func (self *SResourceGroup) GetId() string { + return self.Id +} + +func (self *SResourceGroup) GetName() string { + if len(self.DisplayName) > 0 { + return self.DisplayName + } + return self.Name +} + +func (self *SResourceGroup) Refresh() error { + group, err := self.client.GetResourceGroup(self.Id) + if err != nil { + return errors.Wrap(err, "GetResourceGroup") + } + return jsonutils.Update(self, group) +} + +func (self *SResourceGroup) GetStatus() string { + switch self.Status { + case "Creating": + return api.EXTERNAL_PROJECT_STATUS_CREATING + case "OK": + return api.EXTERNAL_PROJECT_STATUS_AVAILABLE + case "Deleted", "Deleting", "PendingDelete": + return api.EXTERNAL_PROJECT_STATUS_DELETING + default: + return api.EXTERNAL_PROJECT_STATUS_UNKNOWN + } +} + +func (self *SApsaraClient) GetResourceGroups(pageNumber int, pageSize int) ([]SResourceGroup, int, error) { + if pageSize <= 0 || pageSize > 100 { + pageSize = 10 + } + if pageNumber <= 0 { + pageNumber = 1 + } + params := map[string]string{ + "PageNumber": fmt.Sprintf("%d", pageNumber), + "PageSize": fmt.Sprintf("%d", pageSize), + } + resp, err := self.rmRequest("ListResourceGroups", params) + if err != nil { + return nil, 0, errors.Wrap(err, "rmRequest.ListResourceGroups") + } + groups := []SResourceGroup{} + err = resp.Unmarshal(&groups, "ResourceGroups", "ResourceGroup") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalCount") + return groups, int(total), nil +} + +func (self *SApsaraClient) CreateIProject(name string) (cloudprovider.ICloudProject, error) { + group, err := self.CreateResourceGroup(name) + if err != nil { + return nil, errors.Wrap(err, "CreateProject") + } + return group, nil +} + +func (self *SApsaraClient) CreateResourceGroup(name string) (*SResourceGroup, error) { + params := map[string]string{ + "DisplayName": name, + "Name": name, + } + resp, err := self.rmRequest("CreateResourceGroup", params) + if err != nil { + return nil, errors.Wrap(err, "CreateResourceGroup") + } + group := &SResourceGroup{client: self} + err = resp.Unmarshal(group, "ResourceGroup") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + err = cloudprovider.WaitStatus(group, api.EXTERNAL_PROJECT_STATUS_AVAILABLE, time.Second*5, time.Minute*3) + if err != nil { + return nil, errors.Wrap(err, "WaitStatus") + } + return group, nil +} + +func (self *SApsaraClient) GetResourceGroup(id string) (*SResourceGroup, error) { + params := map[string]string{ + "ResourceGroupId": id, + } + resp, err := self.rmRequest("GetResourceGroup", params) + if err != nil { + return nil, err + } + group := &SResourceGroup{client: self} + err = resp.Unmarshal(group, "ResourceGroup") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return group, nil +} diff --git a/pkg/multicloud/apsara/provider/doc.go b/pkg/multicloud/apsara/provider/doc.go new file mode 100644 index 0000000000..b7109fc84f --- /dev/null +++ b/pkg/multicloud/apsara/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/apsara/provider" diff --git a/pkg/multicloud/apsara/provider/provider.go b/pkg/multicloud/apsara/provider/provider.go new file mode 100644 index 0000000000..176a7f7ee6 --- /dev/null +++ b/pkg/multicloud/apsara/provider/provider.go @@ -0,0 +1,220 @@ +// 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" + + "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/apsara" +) + +type SApsaraProviderFactory struct { + cloudprovider.SPrivateCloudBaseProviderFactory +} + +func (self *SApsaraProviderFactory) GetId() string { + return apsara.CLOUD_PROVIDER_APSARA +} + +func (self *SApsaraProviderFactory) GetName() string { + return apsara.CLOUD_PROVIDER_APSARA_CN +} + +func (self *SApsaraProviderFactory) 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") + } + output.Account = input.AccessKeyId + output.Secret = input.AccessKeySecret + if input.SApsaraEndpoints == nil || len(input.EcsEndpoint) == 0 { + return output, httperrors.NewMissingParameterError("ecs_endpoint") + } + if input.SApsaraEndpoints == nil || len(input.VpcEndpoint) == 0 { + return output, httperrors.NewMissingParameterError("vpc_endpoint") + } + return output, nil +} + +func (self *SApsaraProviderFactory) 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 (self *SApsaraProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig) (cloudprovider.ICloudProvider, error) { + client, err := apsara.NewApsaraClient( + apsara.NewApsaraClientConfig( + cfg.Account, + cfg.Secret, + cfg.SApsaraEndpoints, + ).CloudproviderConfig(cfg), + ) + if err != nil { + return nil, err + } + return &SApsaraProvider{ + SBaseProvider: cloudprovider.NewBaseProvider(self), + client: client, + }, nil +} + +func (self *SApsaraProviderFactory) GetClientRC(info cloudprovider.SProviderInfo) (map[string]string, error) { + return map[string]string{ + "APSARA_ACCESS_KEY": info.Account, + "APSARA_SECRET": info.Secret, + "APSARA_REGION": "", + }, nil +} + +func init() { + factory := SApsaraProviderFactory{} + cloudprovider.RegisterFactory(&factory) +} + +type SApsaraProvider struct { + cloudprovider.SBaseProvider + client *apsara.SApsaraClient +} + +func (self *SApsaraProvider) WithClient(client *apsara.SApsaraClient) *SApsaraProvider { + self.client = client + return self +} + +func (self *SApsaraProvider) GetSysInfo() (jsonutils.JSONObject, error) { + regions := self.client.GetIRegions() + info := jsonutils.NewDict() + info.Add(jsonutils.NewInt(int64(len(regions))), "region_count") + info.Add(jsonutils.NewString(apsara.APSARA_API_VERSION), "api_version") + return info, nil +} + +func (self *SApsaraProvider) GetVersion() string { + return apsara.APSARA_API_VERSION +} + +func (self *SApsaraProvider) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + return self.client.GetSubAccounts() +} + +func (self *SApsaraProvider) GetAccountId() string { + return self.client.GetAccountId() +} + +func (self *SApsaraProvider) GetIRegions() []cloudprovider.ICloudRegion { + return self.client.GetIRegions() +} + +func (self *SApsaraProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { + return self.client.GetIRegionById(extId) +} + +func (self *SApsaraProvider) GetBalance() (float64, string, error) { + return 0, api.CLOUD_PROVIDER_HEALTH_NORMAL, nil +} + +func (self *SApsaraProvider) GetIProjects() ([]cloudprovider.ICloudProject, error) { + return self.client.GetIProjects() +} + +func (self *SApsaraProvider) CreateIProject(name string) (cloudprovider.ICloudProject, error) { + return self.client.CreateIProject(name) +} + +func (self *SApsaraProvider) GetStorageClasses(regionId string) []string { + return []string{ + "Standard", "IA", "Archive", + } +} + +func (self *SApsaraProvider) GetBucketCannedAcls(regionId string) []string { + return []string{ + string(cloudprovider.ACLPrivate), + string(cloudprovider.ACLPublicRead), + string(cloudprovider.ACLPublicReadWrite), + } +} + +func (self *SApsaraProvider) GetObjectCannedAcls(regionId string) []string { + return []string{ + string(cloudprovider.ACLPrivate), + string(cloudprovider.ACLPublicRead), + string(cloudprovider.ACLPublicReadWrite), + } +} + +func (self *SApsaraProvider) GetCapabilities() []string { + return self.client.GetCapabilities() +} + +func (self *SApsaraProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SApsaraProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SApsaraProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SApsaraProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SApsaraProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} + +func (self *SApsaraProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} + +func (self *SApsaraProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SApsaraProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetISystemCloudpolicies() +} + +func (self *SApsaraProvider) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetICustomCloudpolicies() +} + +func (self *SApsaraProvider) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) { + return self.client.CreateICloudpolicy(opts) +} diff --git a/pkg/multicloud/apsara/quota.go b/pkg/multicloud/apsara/quota.go new file mode 100644 index 0000000000..1e553cc1ed --- /dev/null +++ b/pkg/multicloud/apsara/quota.go @@ -0,0 +1,136 @@ +// 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 apsara + +import ( + "fmt" + "strconv" + "strings" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type ValueItem struct { + Value string + DiskCategory string +} + +type AttributeValues struct { + ValueItem []ValueItem +} + +type SAccountAttributeItem struct { + AttributeValues AttributeValues + AttributeName string +} + +type SQuota struct { + Name string + UsedCount int + MaxCount int +} + +func (q *SQuota) GetGlobalId() string { + return q.Name +} + +func (q *SQuota) GetDesc() string { + return q.Name +} + +func (q *SQuota) GetQuotaType() string { + return q.Name +} + +func (q *SQuota) GetMaxQuotaCount() int { + return q.MaxCount +} + +func (q *SQuota) GetCurrentQuotaUsedCount() int { + return q.UsedCount +} + +func (region *SRegion) GetAccountAttributes() ([]SAccountAttributeItem, error) { + params := map[string]string{ + "RegionId": region.RegionId, + } + resp, err := region.ecsRequest("DescribeAccountAttributes", params) + if err != nil { + return nil, errors.Wrap(err, "ecsRequest") + } + quotas := []SAccountAttributeItem{} + err = resp.Unmarshal("as, "AccountAttributeItems", "AccountAttributeItem") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return quotas, nil +} + +func (region *SRegion) GetQuotas() ([]SQuota, error) { + attrs, err := region.GetAccountAttributes() + if err != nil { + return nil, errors.Wrap(err, "GetAccountAttributes") + } + quotas := map[string]SQuota{} + for _, attr := range attrs { + for _, item := range attr.AttributeValues.ValueItem { + value, err := strconv.ParseInt(item.Value, 10, 64) + if err != nil { + continue + } + used := false + name := attr.AttributeName + if strings.HasPrefix(name, "used-") { + used = true + name = strings.TrimPrefix(name, "used-") + } + if len(item.DiskCategory) > 0 { + name = fmt.Sprintf("%s/%s", name, item.DiskCategory) + } + if _, ok := quotas[name]; !ok { + quotas[name] = SQuota{ + Name: name, + UsedCount: -1, + } + } + quota := quotas[name] + if used { + quota.UsedCount = int(value) + } else { + quota.MaxCount = int(value) + } + quotas[name] = quota + } + } + ret := []SQuota{} + for _, quota := range quotas { + ret = append(ret, quota) + } + return ret, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + quotas, err := region.GetQuotas() + if err != nil { + return nil, errors.Wrap(err, "GetQuotas") + } + ret := []cloudprovider.ICloudQuota{} + for i := range quotas { + ret = append(ret, "as[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/apsara/ram.go b/pkg/multicloud/apsara/ram.go new file mode 100644 index 0000000000..9557dca21d --- /dev/null +++ b/pkg/multicloud/apsara/ram.go @@ -0,0 +1,27 @@ +// 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 apsara + +import ( + "yunion.io/x/jsonutils" +) + +func (self *SApsaraClient) ramRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return productRequest(cli, APSARA_PRODUCT_RAM, self.endpoints.RamEndpoint, APSARA_RAM_API_VERSION, apiName, params, self.debug) +} diff --git a/pkg/multicloud/apsara/ram_group.go b/pkg/multicloud/apsara/ram_group.go new file mode 100644 index 0000000000..520782b3c2 --- /dev/null +++ b/pkg/multicloud/apsara/ram_group.go @@ -0,0 +1,325 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SGroup struct { + client *SApsaraClient + + Comments string + CreatedDate time.Time + GroupName string + UpdateDate time.Time +} + +type sGroups struct { + Group []SGroup +} + +type SGroups struct { + Groups sGroups + Marker string + IsTruncated bool +} + +func (self *SGroup) GetName() string { + return self.GroupName +} + +func (self *SGroup) GetGlobalId() string { + return self.GroupName +} + +func (self *SGroup) GetDescription() string { + return self.Comments +} + +func (self *SGroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + ret := []cloudprovider.IClouduser{} + offset := "" + for { + part, err := self.client.ListUsersForGroup(self.GroupName, offset, 1000) + if err != nil { + return nil, errors.Wrapf(err, "ListUsersForGroup") + } + for i := range part.Users.User { + part.Users.User[i].client = self.client + ret = append(ret, &part.Users.User[i]) + } + offset = part.Marker + if len(offset) == 0 || !part.IsTruncated { + break + } + } + return ret, nil +} + +func (self *SGroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := self.client.ListPoliciesForGroup(self.GroupName) + if err != nil { + return nil, errors.Wrapf(err, "ListPoliciesForGroup") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == POLICY_TYPE_SYSTEM { + policies[i].client = self.client + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (self *SGroup) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := self.client.ListPoliciesForGroup(self.GroupName) + if err != nil { + return nil, errors.Wrapf(err, "ListPoliciesForGroup") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == POLICY_TYPE_CUSTOM { + policies[i].client = self.client + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (self *SGroup) AddUser(name string) error { + return self.client.AddUserToGroup(self.GroupName, name) +} + +func (self *SGroup) RemoveUser(name string) error { + return self.client.RemoveUserFromGroup(self.GroupName, name) +} + +func (self *SGroup) AttachSystemPolicy(policyName string) error { + return self.client.AttachPolicyToGroup(POLICY_TYPE_SYSTEM, policyName, self.GroupName) +} + +func (self *SGroup) AttachCustomPolicy(policyName string) error { + return self.client.AttachPolicyToGroup(POLICY_TYPE_CUSTOM, policyName, self.GroupName) +} + +func (self *SGroup) DetachSystemPolicy(policyName string) error { + return self.client.DetachPolicyFromGroup(POLICY_TYPE_SYSTEM, policyName, self.GroupName) +} + +func (self *SGroup) DetachCustomPolicy(policyName string) error { + return self.client.DetachPolicyFromGroup(POLICY_TYPE_CUSTOM, policyName, self.GroupName) +} + +func (self *SGroup) Delete() error { + return self.client.DeleteGroup(self.GroupName) +} + +func (self *SApsaraClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + group, err := self.GetGroup(name) + if err != nil { + return nil, errors.Wrapf(err, "GetGroup(%s)", name) + } + return group, nil +} + +func (self *SApsaraClient) CreateICloudgroup(name string, desc string) (cloudprovider.ICloudgroup, error) { + group, err := self.CreateGroup(name, desc) + if err != nil { + return nil, errors.Wrapf(err, "CreateGroup") + } + return group, nil +} + +func (self *SApsaraClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + ret := []cloudprovider.ICloudgroup{} + offset := "" + for { + part, err := self.ListGroups(offset, 100) + if err != nil { + return nil, errors.Wrap(err, "ListGroups") + } + for i := range part.Groups.Group { + part.Groups.Group[i].client = self + ret = append(ret, &part.Groups.Group[i]) + } + offset = part.Marker + if len(offset) == 0 || !part.IsTruncated { + break + } + } + return ret, nil +} + +func (self *SApsaraClient) ListGroups(offset string, limit int) (*SGroups, error) { + if limit < 1 || limit > 1000 { + limit = 1000 + } + params := map[string]string{ + "MaxItems": fmt.Sprintf("%d", limit), + } + if len(offset) > 0 { + params["Marker"] = offset + } + groups := SGroups{} + resp, err := self.ramRequest("ListGroups", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListGroups") + } + err = resp.Unmarshal(&groups) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return &groups, nil +} + +// https://help.apsara.com/document_detail/28732.html?spm=a2c4g.11186623.6.777.580735b2m2xUh8 +func (self *SApsaraClient) ListPoliciesForGroup(groupName string) ([]SPolicy, error) { + params := map[string]string{ + "GroupName": groupName, + } + resp, err := self.ramRequest("ListPoliciesForGroup", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListPoliciesForGroup") + } + policies := []SPolicy{} + err = resp.Unmarshal(&policies, "Policies", "Policy") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return policies, nil +} + +func (self *SApsaraClient) ListUsersForGroup(groupName string, offset string, limit int) (*SUsers, error) { + if limit < 1 || limit > 1000 { + limit = 1000 + } + params := map[string]string{ + "GroupName": groupName, + "MaxItems": fmt.Sprintf("%d", limit), + } + if len(offset) > 0 { + params["Marker"] = offset + } + resp, err := self.ramRequest("ListUsersForGroup", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListUserForGroup") + } + users := &SUsers{} + err = resp.Unmarshal(users) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return users, nil +} + +func (self *SApsaraClient) DeleteGroup(groupName string) error { + params := map[string]string{ + "GroupName": groupName, + } + _, err := self.ramRequest("DeleteGroup", params) + return err +} + +func (self *SApsaraClient) CreateGroup(groupName, comments string) (*SGroup, error) { + params := map[string]string{ + "GroupName": groupName, + } + if len(comments) > 0 { + params["Comments"] = comments + } + resp, err := self.ramRequest("CreateGroup", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateGroup") + } + group := &SGroup{client: self} + err = resp.Unmarshal(group, "Group") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return group, nil +} + +func (self *SApsaraClient) GetGroup(groupName string) (*SGroup, error) { + params := map[string]string{ + "GroupName": groupName, + } + resp, err := self.ramRequest("GetGroup", params) + if err != nil { + return nil, errors.Wrap(err, "GetGroup") + } + group := &SGroup{client: self} + err = resp.Unmarshal(group, "Group") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return group, nil +} + +func (self *SApsaraClient) RemoveUserFromGroup(groupName, userName string) error { + params := map[string]string{ + "GroupName": groupName, + "UserName": userName, + } + _, err := self.ramRequest("RemoveUserFromGroup", params) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "RemoveUserFromGroup") + } + return nil +} + +func (self *SApsaraClient) DetachPolicyFromGroup(policyType, policyName, groupName string) error { + params := map[string]string{ + "GroupName": groupName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("DetachPolicyFromGroup", params) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "DetachPolicyFromGroup") + } + return nil +} + +func (self *SApsaraClient) AddUserToGroup(groupName, userName string) error { + params := map[string]string{ + "GroupName": groupName, + "UserName": userName, + } + _, err := self.ramRequest("AddUserToGroup", params) + if err != nil && !strings.Contains(err.Error(), "EntityAlreadyExists.User.Group") { + return errors.Wrap(err, "AddUserToGroup") + } + return nil +} + +func (self *SApsaraClient) AttachPolicyToGroup(policyType, policyName, groupName string) error { + params := map[string]string{ + "GroupName": groupName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("AttachPolicyToGroup", params) + if err != nil && !strings.Contains(err.Error(), "EntityAlreadyExists.Group.Policy") { + return errors.Wrap(err, "AttachPolicyToGroup") + } + return nil +} diff --git a/pkg/multicloud/apsara/ram_policy.go b/pkg/multicloud/apsara/ram_policy.go new file mode 100644 index 0000000000..d3962ba7d6 --- /dev/null +++ b/pkg/multicloud/apsara/ram_policy.go @@ -0,0 +1,316 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +const ( + POLICY_TYPE_SYSTEM = "System" + POLICY_TYPE_CUSTOM = "Custom" +) + +/** + {"AttachmentCount":0, +"CreateDate":"2018-10-12T05:05:16Z", +"DefaultVersion":"v1", +"Description":"只读访问Data Lake Analytics的权限", +"PolicyName":"ApsaraDLAReadOnlyAccess", +"PolicyType":"System", +"UpdateDate":"2018-10-12T05:05:16Z"} +*/ + +type SDefaultPolicyVersion struct { + CreateDate time.Time + IsDefaultVersion bool + PolicyDocument string + VersionId string +} + +type SPolicyDetails struct { + Policy SPolicy + DefaultPolicyVersion SDefaultPolicyVersion +} + +type sPolicies struct { + Policy []SPolicy +} + +type SPolicies struct { + Policies sPolicies + Marker string + IsTruncated bool +} + +type SPolicy struct { + client *SApsaraClient + AttachmentCount int + CreateDate time.Time + UpdateDate time.Time + DefaultVersion string + Description string + PolicyName string + PolicyType string +} + +func (policy *SPolicy) GetName() string { + return policy.PolicyName +} + +func (policy *SPolicy) GetDescription() string { + return policy.Description +} + +func (policy *SPolicy) GetGlobalId() string { + return policy.PolicyName +} + +func (policy *SPolicy) UpdateDocument(document *jsonutils.JSONDict) error { + return policy.client.CreatePolicyVersion(policy.PolicyName, document.String(), true) +} + +func (policy *SPolicy) Delete() error { + return policy.client.DeletePolicy(policy.PolicyType, policy.PolicyName) +} + +func (policy *SPolicy) GetDocument() (*jsonutils.JSONDict, error) { + details, err := policy.client.GetPolicy(policy.PolicyType, policy.PolicyName) + if err != nil { + return nil, errors.Wrapf(err, "GetPolicy(%s,%s)", policy.PolicyType, policy.PolicyName) + } + obj, err := jsonutils.Parse([]byte(details.DefaultPolicyVersion.PolicyDocument)) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Parse") + } + return obj.(*jsonutils.JSONDict), nil +} + +func (self *SApsaraClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + ret := []cloudprovider.ICloudpolicy{} + offset := "" + for { + part, err := self.ListPolicies(POLICY_TYPE_SYSTEM, offset, 1000) + if err != nil { + return nil, errors.Wrapf(err, "ListPolicies") + } + for i := range part.Policies.Policy { + part.Policies.Policy[i].client = self + ret = append(ret, &part.Policies.Policy[i]) + } + offset = part.Marker + if len(offset) == 0 || !part.IsTruncated { + break + } + } + return ret, nil +} + +func (self *SApsaraClient) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + ret := []cloudprovider.ICloudpolicy{} + offset := "" + for { + part, err := self.ListPolicies(POLICY_TYPE_CUSTOM, offset, 1000) + if err != nil { + return nil, errors.Wrapf(err, "ListPolicies") + } + for i := range part.Policies.Policy { + part.Policies.Policy[i].client = self + ret = append(ret, &part.Policies.Policy[i]) + } + offset = part.Marker + if len(offset) == 0 || !part.IsTruncated { + break + } + } + return ret, nil +} + +func (self *SApsaraClient) AttachPolicyToUser(policyName, policyType, userName string) error { + params := map[string]string{ + "UserName": userName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("AttachPolicyToUser", params) + if err != nil && !strings.Contains(err.Error(), "EntityAlreadyExists.User.Policy") { + return errors.Wrap(err, "AttachPolicyToUser") + } + return nil +} + +func (self *SApsaraClient) DetachPolicyFromUser(policyName, policyType, userName string) error { + if len(policyType) == 0 { + policyType = "System" + } + params := map[string]string{ + "UserName": userName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("DetachPolicyFromUser", params) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "DetachPolicyFromUser") + } + return nil +} + +// https://help.apsara.com/document_detail/28719.html?spm=a2c4g.11174283.6.764.27055662H6TGg5 +func (self *SApsaraClient) ListPolicies(policyType string, offset string, limit int) (*SPolicies, error) { + if limit < 1 || limit > 1000 { + limit = 1000 + } + params := map[string]string{ + "MaxItems": fmt.Sprintf("%d", limit), + } + if len(policyType) > 0 { + params["PolicyType"] = policyType + } + if len(offset) > 0 { + params["Marker"] = offset + } + + body, err := self.ramRequest("ListPolicies", params) + if err != nil { + return nil, errors.Wrapf(err, "ListPolicies") + } + policies := &SPolicies{} + + err = body.Unmarshal(&policies) + if err != nil { + return nil, errors.Wrapf(err, "body.Unmarshal") + } + + return policies, nil +} + +func (self *SApsaraClient) GetPolicy(policyType string, policyName string) (*SPolicyDetails, error) { + params := make(map[string]string) + params["PolicyType"] = policyType + params["PolicyName"] = policyName + + body, err := self.ramRequest("GetPolicy", params) + if err != nil { + if isError(err, "EntityNotExist.Role") { + return nil, cloudprovider.ErrNotFound + } + return nil, err + } + + policy := SPolicyDetails{} + + err = body.Unmarshal(&policy) + if err != nil { + return nil, err + } + + return &policy, nil +} + +type SStatement struct { + Action []string `json:"Action,allowempty"` + Effect string `json:"Effect"` + Resource []string `json:"Resource"` +} + +type SPolicyDocument struct { + Statement []SStatement `json:"Statement,allowempty"` + Version string `json:"Version"` +} + +func (self *SApsaraClient) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) { + if opts.Document == nil { + return nil, errors.Error("nil document") + } + policy, err := self.CreatePolicy(opts.Name, opts.Document.String(), opts.Desc) + if err != nil { + return nil, errors.Wrapf(err, "CreatePolicy") + } + return policy, nil +} + +func (self *SApsaraClient) CreatePolicy(name string, document string, desc string) (*SPolicy, error) { + params := make(map[string]string) + params["PolicyName"] = name + params["PolicyDocument"] = document + if len(desc) > 0 { + params["Description"] = desc + } + + body, err := self.ramRequest("CreatePolicy", params) + if err != nil { + return nil, err + } + + policy := SPolicy{client: self} + + err = body.Unmarshal(&policy, "Policy") + if err != nil { + return nil, err + } + + return &policy, nil +} + +func (self *SApsaraClient) DeletePolicy(policyType string, policyName string) error { + params := make(map[string]string) + params["PolicyName"] = policyName + params["PolicyType"] = policyType + + _, err := self.ramRequest("DeletePolicy", params) + return err +} + +func (self *SApsaraClient) DeleteRole(roleName string) error { + params := make(map[string]string) + params["RoleName"] = roleName + + _, err := self.ramRequest("DeleteRole", params) + return err +} + +func (self *SApsaraClient) AttachPolicy2Role(policyType string, policyName string, roleName string) error { + params := make(map[string]string) + params["PolicyType"] = policyType + params["PolicyName"] = policyName + params["RoleName"] = roleName + + _, err := self.ramRequest("AttachPolicyToRole", params) + if err != nil { + return errors.Wrap(err, "AttachPolicyToRole") + } + + return nil +} + +func (self *SApsaraClient) CreatePolicyVersion(name, document string, isDefault bool) error { + params := map[string]string{ + "PolicyName": name, + "PolicyDocument": document, + "RotateStrategy": "DeleteOldestNonDefaultVersionWhenLimitExceeded", + } + if isDefault { + params["SetAsDefault"] = "true" + } + _, err := self.ramRequest("CreatePolicyVersion", params) + return err +} diff --git a/pkg/multicloud/apsara/ram_role.go b/pkg/multicloud/apsara/ram_role.go new file mode 100644 index 0000000000..993f4b269e --- /dev/null +++ b/pkg/multicloud/apsara/ram_role.go @@ -0,0 +1,137 @@ +// 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 apsara + +import ( + "fmt" + "time" + + "github.com/pkg/errors" +) + +type sRoles struct { + Role []SRole +} + +type SRoles struct { + Roles sRoles + Marker string + IsTruncated bool +} + +type SRole struct { + client *SApsaraClient + + Arn string + CreateDate time.Time + Description string + RoleId string + RoleName string + + AssumeRolePolicyDocument string +} + +func (self *SApsaraClient) ListRoles(offset string, limit int) (*SRoles, error) { + if limit < 0 || limit > 1000 { + limit = 1000 + } + + params := map[string]string{} + if len(offset) > 0 { + params["Marker"] = offset + } + if limit > 0 { + params["MaxItems"] = fmt.Sprintf("%d", limit) + } + + body, err := self.ramRequest("ListRoles", params) + if err != nil { + return nil, errors.Wrapf(err, "ListRoles") + } + + roles := &SRoles{} + err = body.Unmarshal(roles) + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(&") + } + + return roles, nil +} + +func (self *SApsaraClient) CreateRole(roleName string, document string, desc string) (*SRole, error) { + params := make(map[string]string) + params["RoleName"] = roleName + params["AssumeRolePolicyDocument"] = document + if len(desc) > 0 { + params["Description"] = desc + } + + body, err := self.ramRequest("CreateRole", params) + if err != nil { + return nil, errors.Wrap(err, "CreateRole") + } + + role := SRole{client: self} + err = body.Unmarshal(&role, "Role") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal") + } + + return &role, nil +} + +func (self *SApsaraClient) GetRole(roleName string) (*SRole, error) { + params := make(map[string]string) + params["RoleName"] = roleName + + body, err := self.ramRequest("GetRole", params) + if err != nil { + return nil, errors.Wrap(err, "GetRole") + } + + role := SRole{client: self} + err = body.Unmarshal(&role, "Role") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal") + } + + return &role, nil +} + +func (self *SApsaraClient) ListPoliciesForRole(name string) ([]SPolicy, error) { + params := map[string]string{ + "RoleName": name, + } + resp, err := self.ramRequest("ListPoliciesForRole", params) + if err != nil { + return nil, errors.Wrapf(err, "ListPoliciesForRole") + } + policies := []SPolicy{} + err = resp.Unmarshal(&policies, "Policies", "Policy") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return policies, nil +} + +func (self *SApsaraClient) DetachPolicyFromRole(policyType, policyName, roleName string) error { + params := map[string]string{ + "PolicyName": policyName, + "PolicyType": policyType, + "RoleName": roleName, + } + _, err := self.ramRequest("DetachPolicyFromRole", params) + return err +} diff --git a/pkg/multicloud/apsara/ram_user.go b/pkg/multicloud/apsara/ram_user.go new file mode 100644 index 0000000000..1edac509b1 --- /dev/null +++ b/pkg/multicloud/apsara/ram_user.go @@ -0,0 +1,396 @@ +// 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 apsara + +import ( + "fmt" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type sUsers struct { + User []SUser +} + +type SUsers struct { + Users sUsers + Marker string + IsTruncated bool +} + +type SUser struct { + client *SApsaraClient + + Comments string + CreateDate time.Time + DisplayName string + Email string + MobilePhone string + UserId string + UserName string +} + +func (user *SUser) GetGlobalId() string { + if len(user.UserId) > 0 { + return user.UserId + } + u, err := user.client.GetUser(user.UserName) + if err != nil { + return "" + } + return u.UserId +} + +func (user *SUser) GetName() string { + return user.UserName +} + +func (user *SUser) Delete() error { + groups, err := user.client.ListGroupsForUser(user.UserName) + if err != nil { + return errors.Wrap(err, "ListGroupsForUser") + } + for i := range groups { + err = user.client.RemoveUserFromGroup(groups[i].GroupName, user.UserName) + if err != nil { + return errors.Wrapf(err, "RemoveUserFromGroup %s > %s", groups[i].GroupName, user.UserName) + } + } + policies, err := user.client.ListPoliciesForUser(user.UserName) + if err != nil { + return errors.Wrap(err, "ListPoliciesForUser") + } + for i := range policies { + err = user.client.DetachPolicyFromUser(policies[i].PolicyName, policies[i].PolicyType, user.UserName) + if err != nil { + return errors.Wrapf(err, "DetachPolicyFromUser %s %s %s", policies[i].PolicyName, policies[i].PolicyType, user.UserName) + } + } + return user.client.DeleteClouduser(user.UserName) +} + +func (user *SUser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := user.client.ListGroupsForUser(user.UserName) + if err != nil { + return nil, errors.Wrapf(err, "ListGroupsForUser") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = user.client + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (user *SUser) UpdatePassword(password string) error { + return user.client.UpdateLoginProfile(user.UserName, password) +} + +func (user *SUser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := user.client.ListPoliciesForUser(user.UserName) + if err != nil { + return nil, errors.Wrap(err, "ListPoliciesForUser") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == "System" { + policies[i].client = user.client + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (user *SUser) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := user.client.ListPoliciesForUser(user.UserName) + if err != nil { + return nil, errors.Wrap(err, "ListPoliciesForUser") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == "Custom" { + policies[i].client = user.client + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (user *SUser) IsConsoleLogin() bool { + _, err := user.client.GetLoginProfile(user.UserName) + if errors.Cause(err) == cloudprovider.ErrNotFound { + return false + } + return true +} + +func (user *SUser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.UserName, password) +} + +func (user *SUser) AttachSystemPolicy(policyName string) error { + return user.client.AttachPolicyToUser(policyName, POLICY_TYPE_SYSTEM, user.UserName) +} + +func (user *SUser) AttachCustomPolicy(policyName string) error { + return user.client.AttachPolicyToUser(policyName, POLICY_TYPE_CUSTOM, user.UserName) +} + +func (user *SUser) DetachSystemPolicy(policyName string) error { + return user.client.DetachPolicyFromUser(policyName, POLICY_TYPE_SYSTEM, user.UserName) +} + +func (user *SUser) DetachCustomPolicy(policyName string) error { + return user.client.DetachPolicyFromUser(policyName, POLICY_TYPE_CUSTOM, user.UserName) +} + +func (self *SApsaraClient) DeleteClouduser(name string) error { + params := map[string]string{ + "UserName": name, + } + _, err := self.ramRequest("DeleteUser", params) + return err +} + +func (self *SApsaraClient) CreateUser(name, phone, email, comments string) (*SUser, error) { + params := map[string]string{ + "UserName": name, + "DisplayName": name, + } + if len(phone) > 0 { + params["MobilePhone"] = phone + } + if len(email) > 0 { + params["Email"] = email + } + if len(comments) > 0 { + params["Comments"] = comments + } + resp, err := self.ramRequest("CreateUser", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateUser") + } + + user := &SUser{client: self} + err = resp.Unmarshal(user, "User") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return user, nil +} + +func (self *SApsaraClient) ListUsers(offset string, limit int) (*SUsers, error) { + params := map[string]string{} + if len(offset) > 0 { + params["Marker"] = offset + } + if limit > 0 { + params["MaxItems"] = fmt.Sprintf("%d", limit) + } + resp, err := self.ramRequest("ListUsers", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListUsers") + } + users := &SUsers{} + err = resp.Unmarshal(users) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return users, nil +} + +func (self *SApsaraClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + user, err := self.CreateUser(conf.Name, conf.MobilePhone, conf.Email, conf.Desc) + if err != nil { + return nil, errors.Wrap(err, "CreateUser") + } + if len(conf.Password) > 0 { + _, err := self.CreateLoginProfile(conf.Name, conf.Password) + if err != nil { + return nil, errors.Wrap(err, "CreateLoginProfile") + } + } + for _, policyId := range conf.ExternalPolicyIds { + err := user.AttachSystemPolicy(policyId) + if err != nil { + log.Errorf("attach policy %s for user %s error: %v", policyId, conf.Name, err) + } + } + return user, nil +} + +func (self *SApsaraClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + ret := []cloudprovider.IClouduser{} + offset := "" + for { + part, err := self.ListUsers(offset, 100) + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + for i := range part.Users.User { + part.Users.User[i].client = self + ret = append(ret, &part.Users.User[i]) + } + offset = part.Marker + if len(offset) == 0 || !part.IsTruncated { + break + } + } + return ret, nil +} + +func (self *SApsaraClient) GetUser(name string) (*SUser, error) { + params := map[string]string{ + "UserName": name, + } + resp, err := self.ramRequest("GetUser", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateUser") + } + user := &SUser{client: self} + err = resp.Unmarshal(user, "User") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return user, nil +} + +func (self *SApsaraClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.GetUser(name) +} + +type SLoginProfile struct { + CreateDate string + MFABindRequired bool + PasswordResetRequired bool + UserName string +} + +func (self *SApsaraClient) GetLoginProfile(name string) (*SLoginProfile, error) { + params := map[string]string{ + "UserName": name, + } + resp, err := self.ramRequest("GetLoginProfile", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.GetLoginProfile") + } + profile := &SLoginProfile{} + err = resp.Unmarshal(profile, "LoginProfile") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return profile, nil +} + +func (self *SApsaraClient) DeleteLoginProfile(name string) error { + params := map[string]string{ + "UserName": name, + } + _, err := self.ramRequest("DeleteLoginProfile", params) + return err +} + +func (self *SApsaraClient) CreateLoginProfile(name, password string) (*SLoginProfile, error) { + params := map[string]string{ + "UserName": name, + "Password": password, + } + resp, err := self.ramRequest("CreateLoginProfile", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateLoginProfile") + } + profile := &SLoginProfile{} + err = resp.Unmarshal(profile, "LoginProfile") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return profile, nil +} + +func (self *SApsaraClient) UpdateLoginProfile(name, password string) error { + params := map[string]string{ + "UserName": name, + "Password": password, + } + _, err := self.ramRequest("UpdateLoginProfile", params) + if err != nil { + return errors.Wrap(err, "ramRequest.CreateLoginProfile") + } + return nil +} + +func (self *SApsaraClient) ResetClouduserPassword(name, password string) error { + _, err := self.GetLoginProfile(name) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + _, err = self.CreateLoginProfile(name, password) + return err + } + return errors.Wrap(err, "GetLoginProfile") + } + return self.UpdateLoginProfile(name, password) +} + +func (self *SApsaraClient) GetIamLoginUrl() string { + params := map[string]string{} + resp, err := self.ramRequest("GetAccountAlias", params) + if err != nil { + log.Errorf("GetAccountAlias error: %v", err) + return "" + } + alias, _ := resp.GetString("AccountAlias") + if len(alias) > 0 { + return fmt.Sprintf("https://signin.apsara.com/%s.onapsara.com/login.htm", alias) + } + return "" +} + +// https://help.apsara.com/document_detail/28707.html?spm=a2c4g.11186623.6.752.f4466bbfVy5j0s +func (self *SApsaraClient) ListGroupsForUser(user string) ([]SGroup, error) { + params := map[string]string{ + "UserName": user, + } + resp, err := self.ramRequest("ListGroupsForUser", params) + if err != nil { + return nil, errors.Wrap(err, "ListGroupsForUser") + } + groups := []SGroup{} + err = resp.Unmarshal(&groups, "Groups", "Group") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return groups, nil +} + +// https://help.apsara.com/document_detail/28732.html?spm=a2c4g.11186623.6.777.580735b2m2xUh8 +func (self *SApsaraClient) ListPoliciesForUser(user string) ([]SPolicy, error) { + params := map[string]string{ + "UserName": user, + } + resp, err := self.ramRequest("ListPoliciesForUser", params) + if err != nil { + return nil, errors.Wrap(err, "ListPoliciesForUser") + } + policies := []SPolicy{} + err = resp.Unmarshal(&policies, "Policies", "Policy") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return policies, nil +} diff --git a/pkg/multicloud/apsara/ramimage.go b/pkg/multicloud/apsara/ramimage.go new file mode 100644 index 0000000000..0b07d8c884 --- /dev/null +++ b/pkg/multicloud/apsara/ramimage.go @@ -0,0 +1,201 @@ +// 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 apsara + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +const ( + ApsaraECSImageImportRole = "ApsaraECSImageImportDefaultRole" + ApsaraECSImageImportRoleDocument = `{ +"Statement": [ +{ +"Action": "sts:AssumeRole", +"Effect": "Allow", +"Principal": { + "Service": [ + "ecs.apsaracs.com" + ] +} +} +], +"Version": "1" +}` + + ApsaraECSImageImportRolePolicyType = "System" + ApsaraECSImageImportRolePolicy = "ApsaraECSImageImportRolePolicy" + ApsaraECSImageImportRolePolicyDocument = `{ +"Version": "1", +"Statement": [ +{ +"Action": [ + "oss:GetObject", + "oss:GetBucketLocation" +], +"Resource": "*", +"Effect": "Allow" +} +] +}` +) + +func (self *SApsaraClient) EnableImageImport() error { + _, err := self.GetRole(ApsaraECSImageImportRole) + if err != nil { + if err != cloudprovider.ErrNotFound { + return err + } + _, err = self.CreateRole(ApsaraECSImageImportRole, + ApsaraECSImageImportRoleDocument, + "Allow Import External Image from OSS") + if err != nil { + return err + } + } + + _, err = self.GetPolicy(ApsaraECSImageImportRolePolicyType, ApsaraECSImageImportRolePolicy) + if err != nil { + /*if err != cloudprovider.ErrNotFound { + return err + } + _, err = self.createPolicy(ApsaraECSImageImportRolePolicy, + ApsaraECSImageImportRolePolicyDocument, + "Allow Import External Image policy") + if err != nil { + return err + }*/ + return err + } + + policies, err := self.ListPoliciesForRole(ApsaraECSImageImportRole) + if err != nil { + return err + } + for i := 0; i < len(policies); i += 1 { + if policies[i].PolicyType == ApsaraECSImageImportRolePolicyType && + policies[i].PolicyName == ApsaraECSImageImportRolePolicy { + return nil // find policy + } + } + + err = self.AttachPolicy2Role(ApsaraECSImageImportRolePolicyType, ApsaraECSImageImportRolePolicy, ApsaraECSImageImportRole) + if err != nil { + return err + } + + return nil +} + +const ( + ApsaraECSImageExportRole = "ApsaraECSImageExportDefaultRole" + ApsaraECSImageExportRoleDocument = `{ + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs.apsaracs.com" + ] + } + } + ], + "Version": "1" +}` + + ApsaraEmptyRoleDocument = `{ + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs.apsaracs.com" + ] + } + } + ], + "Version": "1" +}` + + ApsaraECSImageExportRolePolicyType = "System" + ApsaraECSImageExportRolePolicy = "ApsaraECSImageExportRolePolicy" + ApsaraECSImageExportRolePolicyDocument = `{ + "Version": "1", + "Statement": [ + { + "Action": [ + "oss:GetObject", + "oss:PutObject", + "oss:DeleteObject", + "oss:GetBucketLocation", + "oss:AbortMultipartUpload", + "oss:ListMultipartUploads", + "oss:ListParts" + ], + "Resource": "*", + "Effect": "Allow" + } + ] + }` +) + +func (self *SApsaraClient) EnableImageExport() error { + _, err := self.GetRole(ApsaraECSImageExportRole) + if err != nil { + if err != cloudprovider.ErrNotFound { + return err + } + _, err = self.CreateRole(ApsaraECSImageExportRole, + ApsaraECSImageExportRoleDocument, + "Allow Export Import to OSS") + if err != nil { + return err + } + } + + _, err = self.GetPolicy(ApsaraECSImageExportRolePolicyType, ApsaraECSImageExportRolePolicy) + if err != nil { + /*if err != cloudprovider.ErrNotFound { + return err + } + _, err = self.createPolicy(ApsaraECSImageImportRolePolicy, + ApsaraECSImageImportRolePolicyDocument, + "Allow Import External Image policy") + if err != nil { + return err + }*/ + return err + } + + policies, err := self.ListPoliciesForRole(ApsaraECSImageExportRole) + if err != nil { + return err + } + for i := 0; i < len(policies); i += 1 { + if policies[i].PolicyType == ApsaraECSImageExportRolePolicyType && + policies[i].PolicyName == ApsaraECSImageExportRolePolicy { + return nil // find policy + } + } + + err = self.AttachPolicy2Role(ApsaraECSImageExportRolePolicyType, ApsaraECSImageExportRolePolicy, ApsaraECSImageExportRole) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/multicloud/apsara/region.go b/pkg/multicloud/apsara/region.go new file mode 100644 index 0000000000..433ec32331 --- /dev/null +++ b/pkg/multicloud/apsara/region.go @@ -0,0 +1,1124 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SRegion struct { + multicloud.SRegion + + client *SApsaraClient + sdkClient *sdk.Client + ossClient *oss.Client + + RegionId string + LocalName string + + RegionEndpoint string + + izones []cloudprovider.ICloudZone + + ivpcs []cloudprovider.ICloudVpc + + lbEndpints map[string]string + + storageCache *SStoragecache + + instanceTypes []SInstanceType + + latitude float64 + longitude float64 + fetchLocation bool +} + +func (self *SRegion) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SRegion) GetClient() *SApsaraClient { + return self.client +} + +func (self *SRegion) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SRegion) getSdkClient() (*sdk.Client, error) { + if self.sdkClient == nil { + cli, err := sdk.NewClientWithAccessKey(self.RegionId, self.client.accessKey, self.client.accessSecret) + if err != nil { + return nil, err + } + self.sdkClient = cli + } + return self.sdkClient, nil +} + +func (self *SRegion) productRequest(client *sdk.Client, product, domain, apiVersion, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + return jsonRequest(client, domain, apiVersion, apiName, params, debug) +} + +func (self *SRegion) GetOssClient() (*oss.Client, error) { + if self.ossClient == nil { + cli, err := self.client.getOssClient(self.RegionId) + if err != nil { + return nil, errors.Wrap(err, "self.client.getOssClient") + } + self.ossClient = cli + } + return self.ossClient, nil +} + +func (self *SRegion) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + endpoint := self.RegionEndpoint + if len(endpoint) == 0 { + endpoint = self.client.endpoints.EcsEndpoint + } + return jsonRequest(client, endpoint, APSARA_API_VERSION, apiName, params, self.client.debug) +} + +func (self *SRegion) rdsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + return self.productRequest(client, APSARA_PRODUCT_RDS, self.client.endpoints.RdsEndpoint, APSARA_API_VERION_RDS, apiName, params, self.client.debug) +} + +func (self *SRegion) vpcRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + + return self.productRequest(client, APSARA_PRODUCT_VPC, self.client.endpoints.VpcEndpoint, APSARA_API_VERSION_VPC, action, params, self.client.debug) +} + +func (self *SRegion) kvsRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + + return self.productRequest(client, APSARA_PRODUCT_KVSTORE, self.client.endpoints.KvsEndpoint, APSARA_API_VERSION_KVS, action, params, self.client.debug) +} + +func (self *SRegion) tagRequest(serviceType string, action string, params map[string]string) (jsonutils.JSONObject, error) { + switch serviceType { + case "ecs": + return self.ecsRequest(action, params) + case "rds": + return self.rdsRequest(action, params) + case "slb": + return self.lbRequest(action, params) + case "kvs": + return self.kvsRequest(action, params) + default: + return nil, errors.Wrapf(errors.ErrNotSupported, "not support %service tag", serviceType) + } +} + +type LBRegion struct { + RegionEndpoint string + RegionId string +} + +func (self *SRegion) fetchLBRegions(client *sdk.Client) error { + if len(self.lbEndpints) > 0 { + return nil + } + params := map[string]string{} + result, err := self._lbRequest(client, "DescribeRegions", self.client.endpoints.SlbEndpoint, params) + if err != nil { + return err + } + self.lbEndpints = map[string]string{} + regions := []LBRegion{} + if err := result.Unmarshal(®ions, "Regions", "Region"); err != nil { + return err + } + for _, region := range regions { + self.lbEndpints[region.RegionId] = region.RegionEndpoint + } + return nil +} + +func (self *SRegion) lbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + domain := self.client.endpoints.SlbEndpoint + /* + if !utils.IsInStringArray(apiName, []string{"DescribeRegions", "DescribeZones"}) { + if regionId, ok := params["RegionId"]; ok { + if err := self.fetchLBRegions(client); err != nil { + return nil, err + } + endpoint, ok := self.lbEndpints[regionId] + if !ok { + return nil, fmt.Errorf("failed to find endpoint for lb region %s", regionId) + } + domain = endpoint + } + }*/ + return self._lbRequest(client, apiName, domain, params) +} + +func (self *SRegion) _lbRequest(client *sdk.Client, apiName string, domain string, params map[string]string) (jsonutils.JSONObject, error) { + return self.productRequest(client, APSARA_PRODUCT_SLB, domain, APSARA_API_VERSION_LB, apiName, params, self.client.debug) +} + +///////////////////////////////////////////////////////////////////////////// +func (self *SRegion) GetId() string { + return self.RegionId +} + +func (self *SRegion) GetName() string { + switch self.client.cpcfg.Vendor { + case api.CLOUD_PROVIDER_APSARA: + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_APSARA_CN, self.LocalName) + default: + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_APSARA_CN, self.LocalName) + } +} + +func (self *SRegion) GetGlobalId() string { + return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_APSARA, self.RegionId) +} + +func (self *SRegion) IsEmulated() bool { + return false +} + +func (self *SRegion) GetProvider() string { + return self.client.cpcfg.Vendor +} + +func (self *SRegion) GetCloudEnv() string { + return "" +} + +func (self *SRegion) GetGeographicInfo() cloudprovider.SGeographicInfo { + return cloudprovider.SGeographicInfo{} +} + +func (self *SRegion) GetStatus() string { + return api.CLOUD_REGION_STATUS_INSERVER +} + +func (self *SRegion) Refresh() error { + // do nothing + return nil +} + +func (self *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) { + if self.izones == nil { + var err error + err = self.fetchInfrastructure() + if err != nil { + return nil, err + } + } + return self.izones, nil +} + +func (self *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) { + izones, err := self.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 (self *SRegion) getStoragecache() *SStoragecache { + if self.storageCache == nil { + self.storageCache = &SStoragecache{region: self} + } + return self.storageCache +} + +func (self *SRegion) _fetchZones(chargeType TChargeType, spotStrategy SpotStrategyType) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + if len(chargeType) > 0 { + params["InstanceChargeType"] = string(chargeType) + } + if len(spotStrategy) > 0 { + params["SpotStrategy"] = string(spotStrategy) + } + body, err := self.ecsRequest("DescribeZones", params) + if err != nil { + return err + } + + zones := make([]SZone, 0) + err = body.Unmarshal(&zones, "Zones", "Zone") + if err != nil { + return err + } + + self.izones = make([]cloudprovider.ICloudZone, len(zones)) + + for i := 0; i < len(zones); i += 1 { + zones[i].region = self + self.izones[i] = &zones[i] + } + + return nil +} + +func (self *SRegion) getZoneById(id string) (*SZone, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + zone := izones[i].(*SZone) + if zone.ZoneId == id { + return zone, nil + } + } + return nil, fmt.Errorf("no such zone %s", id) +} + +func (self *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + return self.GetInstance(id) +} + +func (self *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { + return self.getDisk(id) +} + +func (self *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) { + if self.ivpcs == nil { + err := self.fetchInfrastructure() + if err != nil { + return nil, err + } + } + return self.ivpcs, nil +} + +func (self *SRegion) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { + ivpcs, err := self.GetIVpcs() + if err != nil { + return nil, err + } + for i := 0; i < len(ivpcs); i += 1 { + if ivpcs[i].GetGlobalId() == id { + return ivpcs[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) fetchIVpcs() error { + vpcs := make([]SVpc, 0) + for { + part, total, err := self.GetVpcs(nil, len(vpcs), 50) + if err != nil { + return err + } + vpcs = append(vpcs, part...) + if len(vpcs) >= total { + break + } + } + self.ivpcs = make([]cloudprovider.ICloudVpc, len(vpcs)) + for i := 0; i < len(vpcs); i += 1 { + vpcs[i].region = self + self.ivpcs[i] = &vpcs[i] + } + return nil +} + +func (self *SRegion) fetchInfrastructure() error { + err := self._fetchZones(PostPaidInstanceChargeType, NoSpotStrategy) + if err != nil { + return err + } + err = self.fetchIVpcs() + if err != nil { + return err + } + for i := 0; i < len(self.ivpcs); i += 1 { + for j := 0; j < len(self.izones); j += 1 { + zone := self.izones[j].(*SZone) + vpc := self.ivpcs[i].(*SVpc) + wire := SWire{zone: zone, vpc: vpc} + zone.addWire(&wire) + vpc.addWire(&wire) + } + } + return nil +} + +func (self *SRegion) GetVpcs(vpcId []string, offset int, limit int) ([]SVpc, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if vpcId != nil && len(vpcId) > 0 { + params["VpcId"] = strings.Join(vpcId, ",") + } + + body, err := self.ecsRequest("DescribeVpcs", params) + if err != nil { + log.Errorf("GetVpcs fail %s", err) + return nil, 0, err + } + + vpcs := make([]SVpc, 0) + err = body.Unmarshal(&vpcs, "Vpcs", "Vpc") + if err != nil { + log.Errorf("Unmarshal vpc fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return vpcs, int(total), nil +} + +func (self *SRegion) getVpc(vpcId string) (*SVpc, error) { + vpcs, total, err := self.GetVpcs([]string{vpcId}, 0, 1) + if err != nil { + return nil, err + } + if total != 1 { + return nil, cloudprovider.ErrNotFound + } + vpcs[0].region = self + return &vpcs[0], nil +} + +func (self *SRegion) GetVRouters(offset int, limit int) ([]SVRouter, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + body, err := self.ecsRequest("DescribeVRouters", params) + if err != nil { + log.Errorf("GetVRouters fail %s", err) + return nil, 0, err + } + + vrouters := make([]SVRouter, 0) + err = body.Unmarshal(&vrouters, "VRouters", "VRouter") + if err != nil { + log.Errorf("Unmarshal vrouter fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return vrouters, int(total), nil +} + +func (self *SRegion) GetRouteTables(ids []string, offset int, limit int) ([]SRouteTable, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if ids != nil && len(ids) > 0 { + params["RouteTableId"] = strings.Join(ids, ",") + } + + body, err := self.ecsRequest("DescribeRouteTables", params) + if err != nil { + log.Errorf("GetRouteTables fail %s", err) + return nil, 0, err + } + + routetables := make([]SRouteTable, 0) + err = body.Unmarshal(&routetables, "RouteTables", "RouteTable") + if err != nil { + log.Errorf("Unmarshal routetables fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return routetables, int(total), nil +} + +func (self *SRegion) GetMatchInstanceTypes(cpu int, memMB int, gpu int, zoneId string) ([]SInstanceType, error) { + if self.instanceTypes == nil { + types, err := self.GetInstanceTypes() + if err != nil { + log.Errorf("GetInstanceTypes %s", err) + return nil, err + } + self.instanceTypes = types + } + var available []string + if len(zoneId) > 0 { + zone, err := self.getZoneById(zoneId) + if err != nil { + return nil, err + } + available = zone.AvailableInstanceTypes.InstanceTypes + } + ret := make([]SInstanceType, 0) + for _, t := range self.instanceTypes { + if t.CpuCoreCount == cpu && memMB == t.memoryMB() && gpu == t.GPUAmount { + if available == nil || utils.IsInStringArray(t.InstanceTypeId, available) { + ret = append(ret, t) + } + } + } + return ret, nil +} + +func (self *SRegion) CreateInstanceSimple(name string, imgId string, cpu int, memGB int, storageType string, dataDiskSizesGB []int, vswitchId string, passwd string, publicKey string) (*SInstance, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + z := izones[i].(*SZone) + log.Debugf("Search in zone %s", z.LocalName) + net := z.getNetworkById(vswitchId) + if net != nil { + desc := &cloudprovider.SManagedVMCreateConfig{ + Name: name, + ExternalImageId: imgId, + SysDisk: cloudprovider.SDiskInfo{SizeGB: 0, StorageType: storageType}, + Cpu: cpu, + MemoryMB: memGB * 1024, + ExternalNetworkId: vswitchId, + Password: passwd, + DataDisks: []cloudprovider.SDiskInfo{}, + PublicKey: publicKey, + } + for _, sizeGB := range dataDiskSizesGB { + desc.DataDisks = append(desc.DataDisks, cloudprovider.SDiskInfo{SizeGB: sizeGB, StorageType: storageType}) + } + inst, err := z.getHost().CreateVM(desc) + if err != nil { + return nil, err + } + return inst.(*SInstance), nil + } + } + return nil, fmt.Errorf("cannot find vswitch %s", vswitchId) +} + +func (self *SRegion) instanceOperation(instanceId string, opname string, extra map[string]string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + if extra != nil && len(extra) > 0 { + for k, v := range extra { + params[k] = v + } + } + _, err := self.ecsRequest(opname, params) + return err +} + +func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) { + instance, err := self.GetInstance(instanceId) + if err != nil { + return "", err + } + return instance.Status, nil +} + +func (self *SRegion) GetInstanceVNCUrl(instanceId string) (string, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + body, err := self.ecsRequest("DescribeInstanceVncUrl", params) + if err != nil { + return "", err + } + return body.GetString("VncUrl") +} + +func (self *SRegion) ModifyInstanceVNCUrlPassword(instanceId string, passwd string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["InstanceId"] = instanceId + params["VncPassword"] = passwd // must be 6 digital + alphabet + _, err := self.ecsRequest("ModifyInstanceVncPasswd", params) + return err +} + +func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudprovider.ICloudVpc, error) { + params := make(map[string]string) + if len(cidr) > 0 { + params["CidrBlock"] = cidr + } + if len(name) > 0 { + params["VpcName"] = name + } + if len(desc) > 0 { + params["Description"] = desc + } + params["ClientToken"] = utils.GenRequestId(20) + body, err := self.ecsRequest("CreateVpc", params) + if err != nil { + return nil, err + } + vpcId, err := body.GetString("VpcId") + if err != nil { + return nil, err + } + err = self.fetchInfrastructure() + if err != nil { + return nil, err + } + return self.GetIVpcById(vpcId) +} + +func (self *SRegion) DeleteVpc(vpcId string) error { + params := make(map[string]string) + params["VpcId"] = vpcId + + _, err := self.ecsRequest("DeleteVpc", params) + return err +} + +func (self *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + ihost, err := izones[i].GetIHostById(id) + if err == nil { + return ihost, nil + } else if err != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + istore, err := izones[i].GetIStorageById(id) + if err == nil { + return istore, nil + } else if err != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) { + iHosts := make([]cloudprovider.ICloudHost, 0) + + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + iZoneHost, err := izones[i].GetIHosts() + if err != nil { + return nil, err + } + iHosts = append(iHosts, iZoneHost...) + } + return iHosts, nil +} + +func (self *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + iStores := make([]cloudprovider.ICloudStorage, 0) + + izones, err := self.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 (self *SRegion) updateInstance(instId string, name, desc, passwd, hostname, userData string) error { + params := make(map[string]string) + params["InstanceId"] = instId + if len(name) > 0 { + params["InstanceName"] = name + } + if len(desc) > 0 { + params["Description"] = desc + } + if len(passwd) > 0 { + params["Password"] = passwd + } + if len(hostname) > 0 { + params["HostName"] = hostname + } + if len(userData) > 0 { + params["UserData"] = userData + } + _, err := self.ecsRequest("ModifyInstanceAttribute", params) + return err +} + +func (self *SRegion) UpdateInstancePassword(instId string, passwd string) error { + return self.updateInstance(instId, "", "", passwd, "", "") +} + +// func (self *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { +// eips, total, err := self.GetSnapshots("", 0, 50) +// if err != nil { +// return nil, err +// } +// for len(eips) < total { +// var parts []SEipAddress +// parts, total, err = self.GetEips("", len(eips), 50) +// if err != nil { +// return nil, err +// } +// eips = append(eips, parts...) +// } +// ret := make([]cloudprovider.ICloudEIP, len(eips)) +// for i := 0; i < len(eips); i += 1 { +// ret[i] = &eips[i] +// } +// return ret, nil +// } + +func (self *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) { + eips, total, err := self.GetEips("", "", 0, 50) + if err != nil { + return nil, err + } + for len(eips) < total { + var parts []SEipAddress + parts, total, err = self.GetEips("", "", len(eips), 50) + if err != nil { + return nil, err + } + eips = append(eips, parts...) + } + ret := make([]cloudprovider.ICloudEIP, len(eips)) + for i := 0; i < len(eips); i += 1 { + ret[i] = &eips[i] + } + return ret, nil +} + +func (self *SRegion) GetIEipById(eipId string) (cloudprovider.ICloudEIP, error) { + eips, total, err := self.GetEips(eipId, "", 0, 1) + if err != nil { + return nil, err + } + if total == 0 { + return nil, cloudprovider.ErrNotFound + } + if total > 1 { + return nil, cloudprovider.ErrDuplicateId + } + return &eips[0], nil +} + +func (region *SRegion) GetISecurityGroupById(secgroupId string) (cloudprovider.ICloudSecurityGroup, error) { + secgroup, err := region.GetSecurityGroupDetails(secgroupId) + if err != nil { + return nil, err + } + vpc, err := region.getVpc(secgroup.VpcId) + if err != nil { + return nil, errors.Wrapf(err, "region.getVpc(%s)", secgroup.VpcId) + } + secgroup.vpc = vpc + return secgroup, nil +} + +func (region *SRegion) GetISecurityGroupByName(opts *cloudprovider.SecurityGroupFilterOptions) (cloudprovider.ICloudSecurityGroup, error) { + secgroups, total, err := region.GetSecurityGroups(opts.VpcId, opts.Name, []string{}, 0, 0) + if err != nil { + return nil, err + } + if total == 0 { + return nil, cloudprovider.ErrNotFound + } + if total > 1 { + return nil, cloudprovider.ErrDuplicateId + } + return &secgroups[0], nil +} + +func (region *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) { + externalId, err := region.CreateSecurityGroup(conf.VpcId, conf.Name, conf.Desc) + if err != nil { + return nil, err + } + return region.GetISecurityGroupById(externalId) +} + +func (region *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, error) { + lbs, err := region.GetLoadbalancers(nil) + if err != nil { + return nil, err + } + ilbs := []cloudprovider.ICloudLoadbalancer{} + for i := 0; i < len(lbs); i++ { + lbs[i].region = region + ilbs = append(ilbs, &lbs[i]) + } + return ilbs, nil +} + +func (region *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { + return region.GetLoadbalancerDetail(loadbalancerId) +} + +func (region *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { + certs, err := region.GetLoadbalancerServerCertificates() + if err != nil { + return nil, err + } + for i := 0; i < len(certs); i++ { + if certs[i].GetGlobalId() == certId { + certs[i].region = region + return &certs[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ServerCertificateName"] = cert.Name + params["PrivateKey"] = cert.PrivateKey + params["ServerCertificate"] = cert.Certificate + body, err := region.lbRequest("UploadServerCertificate", params) + if err != nil { + return nil, err + } + certID, err := body.GetString("ServerCertificateId") + if err != nil { + return nil, err + } + return region.GetILoadBalancerCertificateById(certID) +} + +func (region *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { + certificates, err := region.GetLoadbalancerServerCertificates() + if err != nil { + return nil, err + } + iCertificates := []cloudprovider.ICloudLoadbalancerCertificate{} + for i := 0; i < len(certificates); i++ { + certificates[i].region = region + iCertificates = append(iCertificates, &certificates[i]) + } + return iCertificates, nil +} + +func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerName"] = loadbalancer.Name + if len(loadbalancer.ZoneID) > 0 { + params["MasterZoneId"] = loadbalancer.ZoneID + } + + if len(loadbalancer.VpcID) > 0 { + params["VpcId"] = loadbalancer.VpcID + } + + if len(loadbalancer.NetworkIDs) > 0 { + params["VSwitchId"] = loadbalancer.NetworkIDs[0] + } + + if len(loadbalancer.Address) > 0 { + params["Address"] = loadbalancer.Address + } + + if len(loadbalancer.AddressType) > 0 { + params["AddressType"] = loadbalancer.AddressType + } + + if len(loadbalancer.LoadbalancerSpec) > 0 { + params["LoadBalancerSpec"] = loadbalancer.LoadbalancerSpec + } + + if len(loadbalancer.ChargeType) > 0 { + params["InternetChargeType"] = "payby" + loadbalancer.ChargeType + } + + if len(loadbalancer.ProjectId) > 0 { + params["ResourceGroupId"] = loadbalancer.ProjectId + } + + if loadbalancer.ChargeType == api.LB_CHARGE_TYPE_BY_BANDWIDTH && loadbalancer.EgressMbps > 0 { + params["Bandwidth"] = fmt.Sprintf("%d", loadbalancer.EgressMbps) + } + + body, err := region.lbRequest("CreateLoadBalancer", params) + if err != nil { + return nil, err + } + loadBalancerID, err := body.GetString("LoadBalancerId") + if err != nil { + return nil, err + } + region.SetResourceTags("slb", "instance", []string{loadBalancerID}, loadbalancer.Tags, false) + iLoadbalancer, err := region.GetLoadbalancerDetail(loadBalancerID) + if err != nil { + return nil, err + } + return iLoadbalancer, cloudprovider.WaitStatus(iLoadbalancer, api.LB_STATUS_ENABLED, time.Second*5, time.Minute*5) +} + +func (region *SRegion) AddAccessControlListEntry(aclId string, entrys []cloudprovider.SLoadbalancerAccessControlListEntry) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + aclArray := jsonutils.NewArray() + for i := 0; i < len(entrys); i++ { + //阿里云AclEntrys参数必须是CIDR格式的。 + if regutils.MatchIPAddr(entrys[i].CIDR) { + entrys[i].CIDR += "/32" + } + aclArray.Add(jsonutils.Marshal(map[string]string{"entry": entrys[i].CIDR, "comment": entrys[i].Comment})) + } + if aclArray.Length() == 0 { + return nil + } + params["AclEntrys"] = aclArray.String() + _, err := region.lbRequest("AddAccessControlListEntry", params) + return err +} + +func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclName"] = acl.Name + body, err := region.lbRequest("CreateAccessControlList", params) + if err != nil { + return nil, err + } + aclId, err := body.GetString("AclId") + if err != nil { + return nil, err + } + iAcl, err := region.GetLoadbalancerAclDetail(aclId) + if err != nil { + return nil, err + } + return iAcl, region.AddAccessControlListEntry(aclId, acl.Entrys) +} + +func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) { + iBuckets, err := region.client.getIBuckets() + if err != nil { + return nil, errors.Wrap(err, "getIBuckets") + } + ret := make([]cloudprovider.ICloudBucket, 0) + for i := range iBuckets { + loc := iBuckets[i].GetLocation() + // remove oss- prefix + if loc[4:] != region.GetId() { + continue + } + ret = append(ret, iBuckets[i]) + } + return ret, nil +} + +func str2StorageClass(storageClassStr string) (oss.StorageClassType, error) { + storageClass := oss.StorageStandard + if strings.EqualFold(storageClassStr, string(oss.StorageStandard)) { + // + } else if strings.EqualFold(storageClassStr, string(oss.StorageIA)) { + storageClass = oss.StorageIA + } else if strings.EqualFold(storageClassStr, string(oss.StorageArchive)) { + storageClass = oss.StorageArchive + } else { + return storageClass, errors.Error("not supported storageClass") + } + return storageClass, nil +} + +func str2Acl(aclStr string) (oss.ACLType, error) { + acl := oss.ACLPrivate + if strings.EqualFold(aclStr, string(oss.ACLPrivate)) { + // private, default + } else if strings.EqualFold(aclStr, string(oss.ACLPublicRead)) { + acl = oss.ACLPublicRead + } else if strings.EqualFold(aclStr, string(oss.ACLPublicReadWrite)) { + acl = oss.ACLPublicReadWrite + } else { + return acl, errors.Error("not supported acl") + } + return acl, nil +} + +func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr string) error { + osscli, err := region.GetOssClient() + if err != nil { + return errors.Wrap(err, "region.GetOssClient") + } + opts := make([]oss.Option, 0) + if len(storageClassStr) > 0 { + storageClass, err := str2StorageClass(storageClassStr) + if err != nil { + return err + } + opts = append(opts, oss.StorageClass(storageClass)) + } + if len(aclStr) > 0 { + acl, err := str2Acl(aclStr) + if err != nil { + return err + } + opts = append(opts, oss.ACL(acl)) + } + err = osscli.CreateBucket(name, opts...) + if err != nil { + return errors.Wrap(err, "oss.CreateBucket") + } + region.client.invalidateIBuckets() + return nil +} + +func ossErrorCode(err error) int { + if srvErr, ok := err.(oss.ServiceError); ok { + return srvErr.StatusCode + } + if srvErr, ok := err.(*oss.ServiceError); ok { + return srvErr.StatusCode + } + return -1 +} + +func (region *SRegion) DeleteIBucket(name string) error { + osscli, err := region.GetOssClient() + if err != nil { + return errors.Wrap(err, "region.GetOssClient") + } + err = osscli.DeleteBucket(name) + if err != nil { + if ossErrorCode(err) == 404 { + return nil + } + return errors.Wrap(err, "DeleteBucket") + } + region.client.invalidateIBuckets() + return nil +} + +func (region *SRegion) IBucketExist(name string) (bool, error) { + osscli, err := region.GetOssClient() + if err != nil { + return false, errors.Wrap(err, "region.GetOssClient") + } + exist, err := osscli.IsBucketExist(name) + if err != nil { + return false, errors.Wrap(err, "IsBucketExist") + } + return exist, nil +} + +func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) { + osscli, err := region.GetOssClient() + if err != nil { + return nil, errors.Wrap(err, "region.GetOssClient") + } + bi, err := osscli.GetBucketInfo(name) + if err != nil { + return nil, errors.Wrap(err, "Bucket") + } + bInfo := bi.BucketInfo + b := SBucket{ + region: region, + Name: bInfo.Name, + Location: bInfo.Location, + CreationDate: bInfo.CreationDate, + StorageClass: bInfo.StorageClass, + } + return &b, nil +} + +func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) { + return region.GetIBucketById(name) +} + +func (self *SRegion) GetIElasticcaches() ([]cloudprovider.ICloudElasticcache, error) { + caches, err := self.GetElasticCaches(nil) + if err != nil { + return nil, err + } + + icaches := make([]cloudprovider.ICloudElasticcache, len(caches)) + for i := range caches { + caches[i].region = self + icaches[i] = &caches[i] + } + + return icaches, nil +} + +func (region *SRegion) GetCapabilities() []string { + return region.client.GetCapabilities() +} diff --git a/pkg/multicloud/apsara/resource_tags.go b/pkg/multicloud/apsara/resource_tags.go new file mode 100644 index 0000000000..73b08cdd5b --- /dev/null +++ b/pkg/multicloud/apsara/resource_tags.go @@ -0,0 +1,244 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apsara + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type STagResource struct { + ResourceType string `json:"ResourceType"` + TagValue string `json:"TagValue"` + ResourceID string `json:"ResourceId"` + TagKey string `json:"TagKey"` +} + +func (self *SRegion) rawListTagResources(serviceType string, resourceType string, resIds []string, token string) ([]STagResource, string, error) { + if len(resIds) > 50 { + return nil, "", errors.Wrap(cloudprovider.ErrNotSupported, "resource count exceed 50 for one request") + } + params := make(map[string]string) + params["ResourceType"] = resourceType + for i := range resIds { + params[fmt.Sprintf("ResourceId.%d", i+1)] = resIds[i] + } + if len(token) != 0 { + params["NextToken"] = token + } + ret, err := self.tagRequest(serviceType, "ListTagResources", params) + if err != nil { + return nil, "", errors.Wrapf(err, `self.tagRequest(%s,"ListTagResources", %s)`, serviceType, jsonutils.Marshal(params).String()) + } + tagResources := []STagResource{} + err = ret.Unmarshal(&tagResources, "TagResources", "TagResource") + if err != nil { + return nil, "", errors.Wrapf(err, "(%s).Unmarshal(&tagResources)", ret.String()) + } + nextToken, _ := ret.GetString("NextToken") + return tagResources, nextToken, nil +} + +func splitStringSlice(resIds []string, stride int) [][]string { + result := [][]string{} + i := 0 + for i < len(resIds)/stride { + result = append(result, resIds[i*stride:i*stride+stride]) + i++ + } + remainder := len(resIds) % stride + if remainder != 0 { + result = append(result, resIds[i*stride:i*stride+remainder]) + } + return result +} + +func splitTags(tags map[string]string, stride int) []map[string]string { + tagsGroups := []map[string]string{} + tagsGroup := map[string]string{} + for k, v := range tags { + tagsGroup[k] = v + if len(tagsGroup) == stride { + tagsGroups = append(tagsGroups, tagsGroup) + tagsGroup = map[string]string{} + } + } + if len(tagsGroup) > 0 { + tagsGroups = append(tagsGroups, tagsGroup) + } + return tagsGroups +} + +func (self *SRegion) ListResourceTags(serviceType string, resourceType string, resIds []string) (map[string]*map[string]string, error) { + tags := make(map[string]*map[string]string) + tagReources := []STagResource{} + nextToken := "" + resIdsGroups := splitStringSlice(resIds, 50) + for i := range resIdsGroups { + for { + _tagResource, nextToken, err := self.rawListTagResources(serviceType, resourceType, resIdsGroups[i], nextToken) + if err != nil { + return nil, errors.Wrapf(err, "self.rawListTagResources(%s,%s,%s)", resourceType, resIds, nextToken) + } + tagReources = append(tagReources, _tagResource...) + if len(_tagResource) == 0 || len(nextToken) == 0 { + break + } + + } + } + for _, r := range tagReources { + if tagMapPtr, ok := tags[r.ResourceID]; !ok { + tagMap := map[string]string{ + r.TagKey: r.TagValue, + } + tags[r.ResourceID] = &tagMap + } else { + tagMap := *tagMapPtr + tagMap[r.TagKey] = r.TagValue + } + } + return tags, nil +} + +func (self *SRegion) rawTagResources(serviceType string, resourceType string, resIds []string, tags map[string]string) error { + if len(resIds) > 50 { + return errors.Wrap(cloudprovider.ErrNotSupported, "resource count exceed 50 for one request") + } + if len(tags) > 20 { + return errors.Wrap(cloudprovider.ErrNotSupported, "tags count exceed 20 for one request") + } + params := make(map[string]string) + params["ResourceType"] = resourceType + for i := range resIds { + params[fmt.Sprintf("ResourceId.%d", i+1)] = resIds[i] + } + i := 0 + for k, v := range tags { + params[fmt.Sprintf("Tag.%d.Key", i+1)] = k + params[fmt.Sprintf("Tag.%d.Value", i+1)] = v + i++ + } + _, err := self.tagRequest(serviceType, "TagResources", params) + if err != nil { + return errors.Wrapf(err, `self.tagRequest(%s,"TagResources", %s)`, serviceType, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) TagResources(serviceType string, resourceType string, resIds []string, tags map[string]string) error { + if len(resIds) == 0 || len(tags) == 0 { + return nil + } + resIdsGroups := splitStringSlice(resIds, 50) + tagsGroups := splitTags(tags, 20) + for i := range resIdsGroups { + for j := range tagsGroups { + err := self.rawTagResources(serviceType, resourceType, resIdsGroups[i], tagsGroups[j]) + if err != nil { + return errors.Wrapf(err, "self.rawTagResources(resourceType, resIdsGroups[i], tagsGroups[i])") + } + } + } + return nil +} + +func (self *SRegion) rawUntagResources(serviceType string, resourceType string, resIds []string, tags []string) error { + if len(resIds) > 50 { + return errors.Wrap(cloudprovider.ErrNotSupported, "resource count exceed 50 for one request") + } + if len(tags) > 20 { + return errors.Wrap(cloudprovider.ErrNotSupported, "tags count exceed 20 for one request") + } + params := make(map[string]string) + params["ResourceType"] = resourceType + for i := range resIds { + params[fmt.Sprintf("ResourceId.%d", i+1)] = resIds[i] + } + for i := range tags { + params[fmt.Sprintf("TagKey.%d", i+1)] = tags[i] + } + _, err := self.tagRequest(serviceType, "UntagResources", params) + if err != nil { + return errors.Wrapf(err, `self.tagRequest(%s,"UntagResources", %s)`, serviceType, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) UntagResources(serviceType string, resourceType string, resIds []string, tags []string) error { + if len(resIds) == 0 || len(tags) == 0 { + return nil + } + resIdsGroups := splitStringSlice(resIds, 50) + tagsGroups := splitStringSlice(tags, 20) + for i := range resIdsGroups { + for j := range tagsGroups { + err := self.rawUntagResources(serviceType, resourceType, resIdsGroups[i], tagsGroups[j]) + if err != nil { + return errors.Wrapf(err, "self.rawTagResources(resourceType, resIdsGroups[i], tagsGroups[i])") + } + } + } + return nil +} + +func (self *SRegion) SetResourceTags(serviceType string, resourceType string, resIds []string, tags map[string]string, replace bool) error { + oldTags, err := self.ListResourceTags(serviceType, resourceType, resIds) + if err != nil { + return errors.Wrapf(err, "self.ListResourceTags(%s,%s)", resourceType, resIds) + } + for i := range resIds { + _, ok := oldTags[resIds[i]] + if !ok { + err := self.TagResources(serviceType, resourceType, []string{resIds[i]}, tags) + if err != nil { + return errors.Wrap(err, "self.TagResources(resourceType, []string{resIds[i]}, tags)") + } + } else { + oldResourceTags := *oldTags[resIds[i]] + addTags := map[string]string{} + for k, v := range tags { + if _, ok := oldResourceTags[k]; !ok { + addTags[k] = v + } else { + if oldResourceTags[k] != v { + addTags[k] = v + } + } + } + delTags := []string{} + if replace { + for k := range oldResourceTags { + if _, ok := tags[k]; !ok { + delTags = append(delTags, k) + } + } + } + err := self.UntagResources(serviceType, resourceType, []string{resIds[i]}, delTags) + if err != nil { + return errors.Wrap(err, "self.UntagResources(resourceType, []string{resIds[i]}, delTags)") + } + err = self.TagResources(serviceType, resourceType, []string{resIds[i]}, addTags) + if err != nil { + return errors.Wrap(err, "self.TagResources(resourceType, []string{resIds[i]}, addTags)") + } + } + } + return nil +} diff --git a/pkg/multicloud/apsara/routetable.go b/pkg/multicloud/apsara/routetable.go new file mode 100644 index 0000000000..7df58d3e5d --- /dev/null +++ b/pkg/multicloud/apsara/routetable.go @@ -0,0 +1,323 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +// {"CreationTime":"2017-03-19T13:37:40Z","RouteEntrys":{"RouteEntry":[{"DestinationCidrBlock":"172.31.32.0/20","InstanceId":"","NextHopType":"local","NextHops":{"NextHop":[]},"RouteTableId":"vtb-j6c60lectdi80rk5xz43g","Status":"Available","Type":"System"},{"DestinationCidrBlock":"100.64.0.0/10","InstanceId":"","NextHopType":"service","NextHops":{"NextHop":[]},"RouteTableId":"vtb-j6c60lectdi80rk5xz43g","Status":"Available","Type":"System"}]},"RouteTableId":"vtb-j6c60lectdi80rk5xz43g","RouteTableType":"System","VRouterId":"vrt-j6c00qrol733dg36iq4qj"} + +type SNextHops struct { + NextHop []string +} + +type SRouteEntry struct { + routeTable *SRouteTable + + RouteTableId string + Type string + DestinationCidrBlock string + NextHopType string + InstanceId string + RouteEntryId string + RouteEntryName string + NextHops SNextHops +} + +func (route *SRouteEntry) GetId() string { + return route.RouteEntryId +} + +func (route *SRouteEntry) GetName() string { + return route.RouteEntryName +} + +func (route *SRouteEntry) GetGlobalId() string { + return route.GetId() +} + +func (route *SRouteEntry) GetStatus() string { + return "" +} + +func (route *SRouteEntry) Refresh() error { + return nil +} + +func (route *SRouteEntry) IsEmulated() bool { + return false +} + +func (route *SRouteEntry) GetMetadata() *jsonutils.JSONDict { + return nil +} + +// Custom:自定义路由。 System:系统路由。 +func (route *SRouteEntry) GetType() string { + return route.Type +} + +func (route *SRouteEntry) GetCidr() string { + return route.DestinationCidrBlock +} + +func (route *SRouteEntry) GetNextHopType() string { + return route.NextHopType +} + +func (route *SRouteEntry) GetNextHop() string { + return route.InstanceId +} + +type SRouteEntrys struct { + RouteEntry []*SRouteEntry +} + +type SRouteTable struct { + region *SRegion + vpc *SVpc + routes []cloudprovider.ICloudRoute + + VpcId string + CreationTime time.Time + RouteEntrys SRouteEntrys + VRouterId string + Description string + + RouteTableId string + RouteTableName string + RouteTableType string + RouterId string + RouterType string + VSwitchIds SRouteTableVSwitchIds +} + +type SRouteTableVSwitchIds struct { + VSwitchId []string +} + +type sDescribeRouteTablesResponseRouteTables struct { + RouteTable []SRouteTable +} + +type sDescribeRouteTablesResponse struct { + RouteTables sDescribeRouteTablesResponseRouteTables + TotalCount int +} + +func (self *SRouteTable) GetDescription() string { + return self.Description +} + +func (self *SRouteTable) GetId() string { + return self.GetGlobalId() +} + +func (self *SRouteTable) GetGlobalId() string { + return self.RouteTableId +} + +func (self *SRouteTable) GetName() string { + return self.RouteTableName +} + +func (self *SRouteTable) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SRouteTable) GetRegionId() string { + return self.region.RegionId +} + +// VRouter:VPC路由器。 VBR:边界路由器。 +func (self *SRouteTable) GetType() cloudprovider.RouteTableType { + switch self.RouteTableType { + case "System": + return cloudprovider.RouteTableTypeSystem + case "Custom": + return cloudprovider.RouteTableTypeCustom + default: + return cloudprovider.RouteTableTypeSystem + } +} + +func (self *SRouteTable) GetVpcId() string { + return self.VpcId +} + +func (self *SRouteTable) GetIRoutes() ([]cloudprovider.ICloudRoute, error) { + if self.routes == nil { + err := self.fetchRoutes() + if err != nil { + return nil, err + } + } + return self.routes, nil +} + +func (self *SRouteTable) GetStatus() string { + return "" +} + +func (self *SRouteTable) IsEmulated() bool { + return false +} + +func (self *SRouteTable) Refresh() error { + return nil +} + +func (self *SRouteTable) fetchRoutes() error { + routes := make([]*SRouteEntry, 0) + for { + parts, total, err := self.RemoteGetRoutes(len(routes), 50) + if err != nil { + return err + } + routes = append(routes, parts...) + if len(routes) >= total { + break + } + } + self.routes = make([]cloudprovider.ICloudRoute, len(routes)) + for i := 0; i < len(routes); i++ { + routes[i].routeTable = self + self.routes[i] = routes[i] + } + return nil +} + +func (self *SRouteTable) GetAssociations() []cloudprovider.RouteTableAssociation { + result := []cloudprovider.RouteTableAssociation{} + switch self.RouterType { + case "VRouter": + for i := range self.VSwitchIds.VSwitchId { + association := cloudprovider.RouteTableAssociation{ + AssociationId: self.RouteTableId + ":" + self.VSwitchIds.VSwitchId[i], + AssociationType: cloudprovider.RouteTableAssociaToSubnet, + AssociatedResourceId: self.VSwitchIds.VSwitchId[i], + } + result = append(result, association) + } + case "VBR": + association := cloudprovider.RouteTableAssociation{ + AssociationId: self.RouteTableId + ":" + self.RouterId, + AssociationType: cloudprovider.RouteTableAssociaToRouter, + AssociatedResourceId: self.RouterId, + } + result = append(result, association) + } + + return result +} + +func (self *SRouteTable) CreateRoute(route cloudprovider.RouteSet) error { + return cloudprovider.ErrNotSupported +} + +func (self *SRouteTable) UpdateRoute(route cloudprovider.RouteSet) error { + return cloudprovider.ErrNotSupported +} + +func (self *SRouteTable) RemoveRoute(route cloudprovider.RouteSet) error { + return cloudprovider.ErrNotSupported +} + +func (self *SRouteTable) RemoteGetRoutes(offset int, limit int) ([]*SRouteEntry, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RouteTableId"] = self.RouteTableId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + body, err := self.region.ecsRequest("DescribeRouteTables", params) + if err != nil { + log.Errorf("RemoteGetRoutes fail %s", err) + return nil, 0, err + } + + resp := sDescribeRouteTablesResponse{} + err = body.Unmarshal(&resp) + if err != nil { + log.Errorf("Unmarshal routeEntrys fail %s", err) + return nil, 0, err + } + routeTables := resp.RouteTables.RouteTable + if len(routeTables) != 1 { + return nil, 0, fmt.Errorf("expecting 1 route table, got %d", len(routeTables)) + } + routeTable := routeTables[0] + return routeTable.RouteEntrys.RouteEntry, resp.TotalCount, nil +} + +func (self *SVpc) RemoteGetRouteTableList(offset int, limit int) ([]*SRouteTable, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["VpcId"] = self.VpcId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + body, err := self.region.vpcRequest("DescribeRouteTableList", params) + if err != nil { + log.Errorf("RemoteGetRouteTableList fail %s", err) + return nil, 0, err + } + + routeTables := make([]*SRouteTable, 0) + err = body.Unmarshal(&routeTables, "RouterTableList", "RouterTableListType") + if err != nil { + log.Errorf("Unmarshal routeTables fail %s", err) + return nil, 0, err + } + for _, routeTable := range routeTables { + routeTable.region = self.region + } + total, _ := body.Int("TotalCount") + return routeTables, int(total), nil +} + +func (region *SRegion) AssociateRouteTable(rtableId string, vswitchId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["RouteTableId"] = rtableId + params["VSwitchId"] = vswitchId + _, err := region.vpcRequest("AssociateRouteTable", params) + return err +} + +func (region *SRegion) UnassociateRouteTable(rtableId string, vswitchId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["RouteTableId"] = rtableId + params["VSwitchId"] = vswitchId + _, err := region.vpcRequest("UnassociateRouteTable", params) + return err +} + +func (routeTable *SRouteTable) IsSystem() bool { + return strings.ToLower(routeTable.RouteTableType) == "system" +} diff --git a/pkg/multicloud/apsara/securitygroup.go b/pkg/multicloud/apsara/securitygroup.go new file mode 100644 index 0000000000..0c31fd0417 --- /dev/null +++ b/pkg/multicloud/apsara/securitygroup.go @@ -0,0 +1,512 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/secrules" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// {"CreationTime":"2017-03-19T13:37:48Z","Description":"System created security group.","SecurityGroupId":"sg-j6cannq0xxj2r9z0yxwl","SecurityGroupName":"sg-j6cannq0xxj2r9z0yxwl","Tags":{"Tag":[]},"VpcId":"vpc-j6c86z3sh8ufhgsxwme0q"} +// {"Description":"System created security group.","InnerAccessPolicy":"Accept","Permissions":{"Permission":[{"CreateTime":"2017-03-19T13:37:54Z","Description":"","DestCidrIp":"","DestGroupId":"","DestGroupName":"","DestGroupOwnerAccount":"","Direction":"ingress","IpProtocol":"ALL","NicType":"intranet","Policy":"Accept","PortRange":"-1/-1","Priority":110,"SourceCidrIp":"0.0.0.0/0","SourceGroupId":"","SourceGroupName":"","SourceGroupOwnerAccount":""},{"CreateTime":"2017-03-19T13:37:55Z","Description":"","DestCidrIp":"0.0.0.0/0","DestGroupId":"","DestGroupName":"","DestGroupOwnerAccount":"","Direction":"egress","IpProtocol":"ALL","NicType":"intranet","Policy":"Accept","PortRange":"-1/-1","Priority":110,"SourceCidrIp":"","SourceGroupId":"","SourceGroupName":"","SourceGroupOwnerAccount":""}]},"RegionId":"cn-hongkong","RequestId":"FBFE0950-5F2D-40DE-8C3C-E5A62AE7F7DA","SecurityGroupId":"sg-j6cannq0xxj2r9z0yxwl","SecurityGroupName":"sg-j6cannq0xxj2r9z0yxwl","VpcId":"vpc-j6c86z3sh8ufhgsxwme0q"} + +type SecurityGroupPermissionNicType string + +const ( + IntranetNicType SecurityGroupPermissionNicType = "intranet" + InternetNicType SecurityGroupPermissionNicType = "internet" +) + +type SPermission struct { + CreateTime time.Time + Description string + DestCidrIp string + DestGroupId string + DestGroupName string + DestGroupOwnerAccount string + Direction string + IpProtocol string + NicType SecurityGroupPermissionNicType + Policy string + PortRange string + Priority int + SourceCidrIp string + SourceGroupId string + SourceGroupName string + SourceGroupOwnerAccount string +} + +type SPermissions struct { + Permission []SPermission +} + +type Tags struct { + Tag []Tag +} + +type Tag struct { + TagKey string + TagValue string +} + +type SSecurityGroup struct { + multicloud.SSecurityGroup + + vpc *SVpc + CreationTime time.Time + Description string + SecurityGroupId string + SecurityGroupName string + VpcId string + InnerAccessPolicy string + Permissions SPermissions + RegionId string + Tags Tags +} + +func (self *SSecurityGroup) GetVpcId() string { + return self.VpcId +} + +func (self *SSecurityGroup) GetMetadata() *jsonutils.JSONDict { + if len(self.Tags.Tag) == 0 { + return nil + } + data := jsonutils.NewDict() + for _, value := range self.Tags.Tag { + data.Add(jsonutils.NewString(value.TagValue), value.TagKey) + } + return data +} + +func (self *SSecurityGroup) GetId() string { + return self.SecurityGroupId +} + +func (self *SSecurityGroup) GetGlobalId() string { + return self.SecurityGroupId +} + +func (self *SSecurityGroup) GetDescription() string { + return self.Description +} + +func (self *SSecurityGroup) GetRules() ([]cloudprovider.SecurityRule, error) { + rules := make([]cloudprovider.SecurityRule, 0) + secgrp, err := self.vpc.region.GetSecurityGroupDetails(self.SecurityGroupId) + if err != nil { + return nil, err + } + for _, permission := range secgrp.Permissions.Permission { + rule, err := permission.toRule() + if err != nil { + log.Errorf("convert rule %s for group %s(%s) error: %v", permission.Description, self.SecurityGroupName, self.SecurityGroupId, err) + continue + } + rules = append(rules, rule) + } + return rules, nil +} + +func (self *SSecurityGroup) GetName() string { + if len(self.SecurityGroupName) > 0 { + return self.SecurityGroupName + } + return self.SecurityGroupId +} + +func (self *SSecurityGroup) GetStatus() string { + return "" +} + +func (self *SSecurityGroup) IsEmulated() bool { + return false +} + +func (self *SSecurityGroup) Refresh() error { + group, err := self.vpc.region.GetSecurityGroupDetails(self.SecurityGroupId) + if err != nil { + return err + } + return jsonutils.Update(self, group) +} + +func (self *SRegion) GetSecurityGroups(vpcId, name string, securityGroupIds []string, offset int, limit int) ([]SSecurityGroup, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if len(vpcId) > 0 { + params["VpcId"] = vpcId + } + if len(name) > 0 { + params["SecurityGroupName"] = name + } + + if securityGroupIds != nil && len(securityGroupIds) > 0 { + params["SecurityGroupIds"] = jsonutils.Marshal(securityGroupIds).String() + } + + body, err := self.ecsRequest("DescribeSecurityGroups", params) + if err != nil { + log.Errorf("GetSecurityGroups fail %s", err) + return nil, 0, err + } + + secgrps := make([]SSecurityGroup, 0) + err = body.Unmarshal(&secgrps, "SecurityGroups", "SecurityGroup") + if err != nil { + log.Errorf("Unmarshal security groups fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return secgrps, int(total), nil +} + +func (self *SRegion) GetSecurityGroupDetails(secGroupId string) (*SSecurityGroup, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["SecurityGroupId"] = secGroupId + + body, err := self.ecsRequest("DescribeSecurityGroupAttribute", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeSecurityGroupAttribute") + } + + secgrp := SSecurityGroup{} + err = body.Unmarshal(&secgrp) + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal") + } + return &secgrp, nil +} + +func (self *SRegion) CreateSecurityGroup(vpcId string, name string, desc string) (string, error) { + params := make(map[string]string) + if len(vpcId) > 0 { + params["VpcId"] = vpcId + } + + if name == "Default" { + name = "Default-copy" + } + + if len(name) > 0 { + params["SecurityGroupName"] = name + } + if len(desc) > 0 { + params["Description"] = desc + } + params["ClientToken"] = utils.GenRequestId(20) + + body, err := self.ecsRequest("CreateSecurityGroup", params) + if err != nil { + return "", errors.Wrap(err, "CreateSecurityGroup") + } + return body.GetString("SecurityGroupId") +} + +func (self *SRegion) modifySecurityGroupRule(secGrpId string, rule *secrules.SecurityRule) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["SecurityGroupId"] = secGrpId + params["NicType"] = string(IntranetNicType) + params["Description"] = rule.Description + params["PortRange"] = fmt.Sprintf("%d/%d", rule.PortStart, rule.PortEnd) + protocol := rule.Protocol + if len(rule.Protocol) == 0 || rule.Protocol == secrules.PROTO_ANY { + protocol = "all" + } + params["IpProtocol"] = protocol + if rule.PortStart < 1 && rule.PortEnd < 1 { + if protocol == "udp" || protocol == "tcp" { + params["PortRange"] = "1/65535" + } else { + params["PortRange"] = "-1/-1" + } + } + if rule.Action == secrules.SecurityRuleAllow { + params["Policy"] = "accept" + } else { + params["Policy"] = "drop" + } + params["Priority"] = fmt.Sprintf("%d", rule.Priority) + if rule.Direction == secrules.SecurityRuleIngress { + if rule.IPNet != nil { + params["SourceCidrIp"] = rule.IPNet.String() + } else { + params["SourceCidrIp"] = "0.0.0.0/0" + } + _, err := self.ecsRequest("ModifySecurityGroupRule", params) + return err + } else { // rule.Direction == secrules.SecurityRuleEgress { + //阿里云不支持出方向API接口调用 + return nil + // if rule.IPNet != nil { + // params["DestCidrIp"] = rule.IPNet.String() + // } else { + // params["DestCidrIp"] = "0.0.0.0/0" + // } + // _, err := self.ecsRequest("ModifySecurityGroupRule", params) + // return err + } +} + +func (self *SRegion) modifySecurityGroup(secGrpId string, name string, desc string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["SecurityGroupId"] = secGrpId + params["SecurityGroupName"] = name + if len(desc) > 0 { + params["Description"] = desc + } + _, err := self.ecsRequest("ModifySecurityGroupAttribute", params) + return err +} + +func (self *SRegion) AddSecurityGroupRules(secGrpId string, rule secrules.SecurityRule) error { + if len(rule.Ports) != 0 { + for _, port := range rule.Ports { + rule.PortStart, rule.PortEnd = port, port + err := self.addSecurityGroupRule(secGrpId, rule) + if err != nil { + return errors.Wrapf(err, "addSecurityGroupRule %s", rule.String()) + } + } + return nil + } + return self.addSecurityGroupRule(secGrpId, rule) +} + +func (self *SRegion) addSecurityGroupRule(secGrpId string, rule secrules.SecurityRule) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["SecurityGroupId"] = secGrpId + params["NicType"] = string(IntranetNicType) + params["Description"] = rule.Description + params["PortRange"] = fmt.Sprintf("%d/%d", rule.PortStart, rule.PortEnd) + protocol := rule.Protocol + if len(rule.Protocol) == 0 || rule.Protocol == secrules.PROTO_ANY { + protocol = "all" + } + params["IpProtocol"] = protocol + if rule.PortStart < 1 && rule.PortEnd < 1 { + if protocol == "udp" || protocol == "tcp" { + params["PortRange"] = "1/65535" + } else { + params["PortRange"] = "-1/-1" + } + } + if rule.Action == secrules.SecurityRuleAllow { + params["Policy"] = "accept" + } else { + params["Policy"] = "drop" + } + + // 忽略地址为0.0.0.0/32这样的阿里云规则 + if rule.IPNet.IP.String() == "0.0.0.0" && rule.IPNet.String() != "0.0.0.0/0" { + return nil + } + + params["Priority"] = fmt.Sprintf("%d", rule.Priority) + if rule.Direction == secrules.SecurityRuleIngress { + if rule.IPNet != nil { + params["SourceCidrIp"] = rule.IPNet.String() + } else { + params["SourceCidrIp"] = "0.0.0.0/0" + } + _, err := self.ecsRequest("AuthorizeSecurityGroup", params) + return err + } else { // rule.Direction == secrules.SecurityRuleEgress { + if rule.IPNet != nil { + params["DestCidrIp"] = rule.IPNet.String() + } else { + params["DestCidrIp"] = "0.0.0.0/0" + } + _, err := self.ecsRequest("AuthorizeSecurityGroupEgress", params) + return err + } +} + +func (self *SRegion) DelSecurityGroupRule(secGrpId string, rule secrules.SecurityRule) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["SecurityGroupId"] = secGrpId + params["NicType"] = string(IntranetNicType) + params["PortRange"] = fmt.Sprintf("%d/%d", rule.PortStart, rule.PortEnd) + protocol := rule.Protocol + if len(rule.Protocol) == 0 || rule.Protocol == secrules.PROTO_ANY { + protocol = "all" + } + params["IpProtocol"] = protocol + if rule.PortStart < 1 && rule.PortEnd < 1 { + if protocol == "udp" || protocol == "tcp" { + params["PortRange"] = "1/65535" + } else { + params["PortRange"] = "-1/-1" + } + } + if rule.Action == secrules.SecurityRuleAllow { + params["Policy"] = "accept" + } else { + params["Policy"] = "drop" + } + params["Priority"] = fmt.Sprintf("%d", rule.Priority) + if rule.Direction == secrules.SecurityRuleIngress { + if rule.IPNet != nil { + params["SourceCidrIp"] = rule.IPNet.String() + } else { + params["SourceCidrIp"] = "0.0.0.0/0" + } + _, err := self.ecsRequest("RevokeSecurityGroup", params) + return err + } else { // rule.Direction == secrules.SecurityRuleEgress { + if rule.IPNet != nil { + params["DestCidrIp"] = rule.IPNet.String() + } else { + params["DestCidrIp"] = "0.0.0.0/0" + } + _, err := self.ecsRequest("RevokeSecurityGroupEgress", params) + return err + } +} + +func (self *SPermission) toRule() (cloudprovider.SecurityRule, error) { + rule := cloudprovider.SecurityRule{ + SecurityRule: secrules.SecurityRule{ + Action: secrules.SecurityRuleDeny, + Direction: secrules.DIR_IN, + Priority: self.Priority, + Description: self.Description, + PortStart: -1, + PortEnd: -1, + }, + } + if strings.ToLower(self.Policy) == "accept" { + rule.Action = secrules.SecurityRuleAllow + } + + cidr := self.SourceCidrIp + if self.Direction == "egress" { + rule.Direction = secrules.DIR_OUT + cidr = self.DestCidrIp + } + + rule.ParseCIDR(cidr) + + switch strings.ToLower(self.IpProtocol) { + case "tcp", "udp", "icmp": + rule.Protocol = strings.ToLower(self.IpProtocol) + case "all": + rule.Protocol = secrules.PROTO_ANY + default: + return rule, fmt.Errorf("unsupported protocal %s", self.IpProtocol) + } + + port, ports := "", strings.Split(self.PortRange, "/") + if ports[0] == ports[1] { + if ports[0] != "-1" { + port = ports[0] + } + } else if ports[0] != "1" && ports[1] != "65535" { + port = fmt.Sprintf("%s-%s", ports[0], ports[1]) + } + err := rule.ParsePorts(port) + if err != nil { + return rule, errors.Wrapf(err, "ParsePorts(%s)", port) + } + return rule, nil +} + +func (self *SRegion) AssignSecurityGroup(secgroupId, instanceId string) error { + return self.SetSecurityGroups([]string{secgroupId}, instanceId) +} + +func (self *SRegion) SetSecurityGroups(secgroupIds []string, instanceId string) error { + params := map[string]string{"InstanceId": instanceId} + for _, secgroupId := range secgroupIds { + params["SecurityGroupId"] = secgroupId + if _, err := self.ecsRequest("JoinSecurityGroup", params); err != nil { + return err + } + } + instance, err := self.GetInstance(instanceId) + if err != nil { + return err + } + for _, _secgroupId := range instance.SecurityGroupIds.SecurityGroupId { + if !utils.IsInStringArray(_secgroupId, secgroupIds) { + if err := self.leaveSecurityGroup(_secgroupId, instanceId); err != nil { + return err + } + } + } + return nil +} + +func (self *SRegion) leaveSecurityGroup(secgroupId, instanceId string) error { + params := map[string]string{"InstanceId": instanceId, "SecurityGroupId": secgroupId} + _, err := self.ecsRequest("LeaveSecurityGroup", params) + return err +} + +func (self *SRegion) DeleteSecurityGroup(secGrpId string) error { + params := make(map[string]string) + params["SecurityGroupId"] = secGrpId + + _, err := self.ecsRequest("DeleteSecurityGroup", params) + if err != nil { + log.Errorf("Delete security group fail %s", err) + return err + } + return nil +} + +func (self *SSecurityGroup) Delete() error { + return self.vpc.region.DeleteSecurityGroup(self.SecurityGroupId) +} + +func (self *SSecurityGroup) GetProjectId() string { + return "" +} + +func (self *SSecurityGroup) SyncRules(common, inAdds, outAdds, inDels, outDels []cloudprovider.SecurityRule) error { + for _, rule := range append(inDels, outDels...) { + err := self.vpc.region.DelSecurityGroupRule(self.SecurityGroupId, rule.SecurityRule) + if err != nil { + return errors.Wrapf(err, "DelSecurityGroupRule(Name:%s priority: %d %s)", rule.Name, rule.Priority, rule.String()) + } + } + for _, rule := range append(inAdds, outAdds...) { + err := self.vpc.region.AddSecurityGroupRules(self.SecurityGroupId, rule.SecurityRule) + if err != nil { + return errors.Wrapf(err, "AddSecurityGroupRules(priority: %d %s)", rule.Priority, rule.String()) + } + } + return nil +} diff --git a/pkg/multicloud/apsara/shell/bucket.go b/pkg/multicloud/apsara/shell/bucket.go new file mode 100644 index 0000000000..f44f1c4081 --- /dev/null +++ b/pkg/multicloud/apsara/shell/bucket.go @@ -0,0 +1,21 @@ +// 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/objectstore" + +func init() { + objectstore.S3Shell() +} diff --git a/pkg/multicloud/apsara/shell/dbinstance.go b/pkg/multicloud/apsara/shell/dbinstance.go new file mode 100644 index 0000000000..c975d20015 --- /dev/null +++ b/pkg/multicloud/apsara/shell/dbinstance.go @@ -0,0 +1,102 @@ +// 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 ( + "fmt" + "strings" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceListOptions struct { + Id []string `help:"IDs of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&DBInstanceListOptions{}, "dbinstance-list", "List dbintances", func(cli *apsara.SRegion, args *DBInstanceListOptions) error { + instances, total, e := cli.GetDBInstances(args.Id, args.Offset, args.Limit) + if e != nil { + return e + } + printList(instances, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type DBInstanceIdOptions struct { + ID string `help:"ID of instances to show"` + } + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-show", "Show dbintance", func(cli *apsara.SRegion, args *DBInstanceIdOptions) error { + instance, err := cli.GetDBInstanceDetail(args.ID) + if err != nil { + return err + } + printObject(instance) + return nil + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-open-public-connection", "Open dbintance public connection", func(cli *apsara.SRegion, args *DBInstanceIdOptions) error { + return cli.OpenPublicConnection(args.ID) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-close-public-connection", "Close dbintance public connection", func(cli *apsara.SRegion, args *DBInstanceIdOptions) error { + return cli.ClosePublicConnection(args.ID) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-delete", "Delete dbintance", func(cli *apsara.SRegion, args *DBInstanceIdOptions) error { + return cli.DeleteDBInstance(args.ID) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-restart", "Restart dbintance", func(cli *apsara.SRegion, args *DBInstanceIdOptions) error { + return cli.RebootDBInstance(args.ID) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-network-list", "Show dbintance network info", func(cli *apsara.SRegion, args *DBInstanceIdOptions) error { + networks, err := cli.GetDBInstanceNetInfo(args.ID) + if err != nil { + return err + } + printList(networks, 0, 0, 0, []string{}) + return nil + }) + + type DBInstanceRecoveryOptions struct { + ID string + TARGET string + BACKUP string + Databases []string + } + + shellutils.R(&DBInstanceRecoveryOptions{}, "dbinstance-recovery", "Recovery dbintance from backup", func(cli *apsara.SRegion, args *DBInstanceRecoveryOptions) error { + databases := map[string]string{} + for _, database := range args.Databases { + if len(database) > 0 { + dbInfo := strings.Split(database, ":") + if len(dbInfo) == 1 { + databases[dbInfo[0]] = dbInfo[0] + + } else if len(dbInfo) == 2 { + databases[dbInfo[0]] = dbInfo[1] + } else { + return fmt.Errorf("Invalid dbinfo: %s", database) + } + } + } + return cli.RecoveryDBInstanceFromBackup(args.ID, args.TARGET, args.BACKUP, databases) + }) + +} diff --git a/pkg/multicloud/apsara/shell/dbinstance_account.go b/pkg/multicloud/apsara/shell/dbinstance_account.go new file mode 100644 index 0000000000..08731d38cd --- /dev/null +++ b/pkg/multicloud/apsara/shell/dbinstance_account.go @@ -0,0 +1,70 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + + type DBInstanceIdExtraOptions struct { + ID string `help:"ID of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + + shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-account-list", "List dbintance accounts", func(cli *apsara.SRegion, args *DBInstanceIdExtraOptions) error { + accounts, _, err := cli.GetDBInstanceAccounts(args.ID, args.Offset, args.Limit) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, []string{}) + return nil + }) + + type DBInstanceAccountCreateOptions struct { + INSTANCE string `help:"ID of instances"` + NAME string `help:"account name"` + PASSWORD string `help:"account password"` + Desc string + } + + shellutils.R(&DBInstanceAccountCreateOptions{}, "dbinstance-account-create", "Create dbintance account", func(cli *apsara.SRegion, args *DBInstanceAccountCreateOptions) error { + return cli.CreateDBInstanceAccount(args.INSTANCE, args.NAME, args.PASSWORD, args.Desc) + }) + + type DBInstanceAccountDeleteOptions struct { + INSTANCE string + NAME string + } + + shellutils.R(&DBInstanceAccountDeleteOptions{}, "dbinstance-account-delete", "Delete dbintance account", func(cli *apsara.SRegion, args *DBInstanceAccountDeleteOptions) error { + return cli.DeleteDBInstanceAccount(args.INSTANCE, args.NAME) + }) + + type DBInstanceAccountResetOptions struct { + INSTANCE string `help:"ID of instances"` + NAME string `help:"account name"` + PASSWORD string `help:"account password"` + AccountType string `help:"account type" choices:"Normal|Super" default:"Normal"` + } + + shellutils.R(&DBInstanceAccountResetOptions{}, "dbinstance-account-reset-password", "Reset dbintance account password", func(cli *apsara.SRegion, args *DBInstanceAccountResetOptions) error { + return cli.ResetDBInstanceAccountPassword(args.INSTANCE, args.NAME, args.PASSWORD, args.AccountType) + }) + +} diff --git a/pkg/multicloud/apsara/shell/dbinstance_backup.go b/pkg/multicloud/apsara/shell/dbinstance_backup.go new file mode 100644 index 0000000000..4ee2867c27 --- /dev/null +++ b/pkg/multicloud/apsara/shell/dbinstance_backup.go @@ -0,0 +1,64 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceBackupJobListOptions struct { + INSTANCE string + JobId string + } + shellutils.R(&DBInstanceBackupJobListOptions{}, "dbinstance-backup-job-list", "Get dbinstance backup jobs", func(cli *apsara.SRegion, args *DBInstanceBackupJobListOptions) error { + jobs, err := cli.GetDBInstanceBackupJobs(args.INSTANCE, args.JobId) + if err != nil { + return err + } + printObject(jobs) + return nil + }) + + type DBInstanceBackupOptions struct { + INSTANCE string + BACKUP string + } + + shellutils.R(&DBInstanceBackupOptions{}, "dbinstance-backup-delete", "Delete dbinstance backup", func(cli *apsara.SRegion, args *DBInstanceBackupOptions) error { + return cli.DeleteDBInstanceBackup(args.INSTANCE, args.BACKUP) + }) + + shellutils.R(&DBInstanceBackupOptions{}, "dbinstance-backup-job-list", "Get dbinstance backup jobs", func(cli *apsara.SRegion, args *DBInstanceBackupOptions) error { + return cli.DeleteDBInstanceBackup(args.INSTANCE, args.BACKUP) + }) + + type DBInstanceIdExtraOptions struct { + ID string `help:"ID of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + + shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-backup-list", "List dbintance backups", func(cli *apsara.SRegion, args *DBInstanceIdExtraOptions) error { + backups, _, err := cli.GetDBInstanceBackups(args.ID, "", args.Offset, args.Limit) + if err != nil { + return err + } + printList(backups, 0, 0, 0, []string{}) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/dbinstance_database.go b/pkg/multicloud/apsara/shell/dbinstance_database.go new file mode 100644 index 0000000000..9fe1660542 --- /dev/null +++ b/pkg/multicloud/apsara/shell/dbinstance_database.go @@ -0,0 +1,59 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + + type DBInstanceIdExtraOptions struct { + ID string `help:"ID of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + + shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-database-list", "List dbintance databases", func(cli *apsara.SRegion, args *DBInstanceIdExtraOptions) error { + databases, _, err := cli.GetDBInstanceDatabases(args.ID, "", args.Offset, args.Limit) + if err != nil { + return err + } + printList(databases, 0, 0, 0, []string{}) + return nil + }) + + type DBInstanceDatabaseCreateOptions struct { + INSTANCE string `help:"ID of instances"` + NAME string `help:"database name"` + CHARACTERSET string `help:"character set for database"` + Desc string + } + + shellutils.R(&DBInstanceDatabaseCreateOptions{}, "dbinstance-database-create", "Create dbintance database", func(cli *apsara.SRegion, args *DBInstanceDatabaseCreateOptions) error { + return cli.CreateDBInstanceDatabae(args.INSTANCE, args.CHARACTERSET, args.NAME, args.Desc) + }) + + type DBInstanceDatabaseDeleteOptions struct { + INSTANCE string + NAME string + } + + shellutils.R(&DBInstanceDatabaseDeleteOptions{}, "dbinstance-database-delete", "Delete dbintance database", func(cli *apsara.SRegion, args *DBInstanceDatabaseDeleteOptions) error { + return cli.DeleteDBInstanceDatabase(args.INSTANCE, args.NAME) + }) + +} diff --git a/pkg/multicloud/apsara/shell/disk.go b/pkg/multicloud/apsara/shell/disk.go new file mode 100644 index 0000000000..c7dfaf0ee0 --- /dev/null +++ b/pkg/multicloud/apsara/shell/disk.go @@ -0,0 +1,49 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DiskListOptions struct { + Instance string `help:"Instance ID"` + Zone string `help:"Zone ID"` + Category string `help:"Disk category"` + Offset int `help:"List offset"` + Limit int `help:"List limit"` + } + shellutils.R(&DiskListOptions{}, "disk-list", "List disks", func(cli *apsara.SRegion, args *DiskListOptions) error { + disks, total, e := cli.GetDisks(args.Instance, args.Zone, args.Category, nil, args.Offset, args.Limit) + if e != nil { + return e + } + printList(disks, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type DiskDeleteOptions struct { + ID string `help:"Instance ID"` + } + shellutils.R(&DiskDeleteOptions{}, "disk-delete", "List disks", func(cli *apsara.SRegion, args *DiskDeleteOptions) error { + e := cli.DeleteDisk(args.ID) + if e != nil { + return e + } + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/doc.go b/pkg/multicloud/apsara/shell/doc.go new file mode 100644 index 0000000000..6aef15a3f8 --- /dev/null +++ b/pkg/multicloud/apsara/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/apsara/shell" diff --git a/pkg/multicloud/apsara/shell/eip.go b/pkg/multicloud/apsara/shell/eip.go new file mode 100644 index 0000000000..235ace3b0a --- /dev/null +++ b/pkg/multicloud/apsara/shell/eip.go @@ -0,0 +1,70 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EipListOptions struct { + AssociateId string `help:"Id of associate resource"` + Offset int `help:"List offset"` + Limit int `help:"List limit"` + } + shellutils.R(&EipListOptions{}, "eip-list", "List eips", func(cli *apsara.SRegion, args *EipListOptions) error { + eips, total, e := cli.GetEips("", args.AssociateId, args.Offset, args.Limit) + if e != nil { + return e + } + printList(eips, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type EipAllocateOptions struct { + BW int `help:"Bandwidth limit in Mbps"` + ResourceGroupId string `help:"Resource group Id"` + } + shellutils.R(&EipAllocateOptions{}, "eip-create", "Allocate an EIP", func(cli *apsara.SRegion, args *EipAllocateOptions) error { + eip, err := cli.AllocateEIP(args.BW, apsara.InternetChargeByTraffic, args.ResourceGroupId) + if err != nil { + return err + } + printObject(eip) + return nil + }) + + type EipReleaseOptions struct { + ID string `help:"EIP allocation ID"` + } + shellutils.R(&EipReleaseOptions{}, "eip-delete", "Release an EIP", func(cli *apsara.SRegion, args *EipReleaseOptions) error { + err := cli.DeallocateEIP(args.ID) + return err + }) + + type EipAssociateOptions struct { + ID string `help:"EIP allocation ID"` + INSTANCE string `help:"Instance ID"` + } + shellutils.R(&EipAssociateOptions{}, "eip-associate", "Associate an EIP", func(cli *apsara.SRegion, args *EipAssociateOptions) error { + err := cli.AssociateEip(args.ID, args.INSTANCE) + return err + }) + shellutils.R(&EipAssociateOptions{}, "eip-dissociate", "Dissociate an EIP", func(cli *apsara.SRegion, args *EipAssociateOptions) error { + err := cli.DissociateEip(args.ID, args.INSTANCE) + return err + }) +} diff --git a/pkg/multicloud/apsara/shell/elasticcache.go b/pkg/multicloud/apsara/shell/elasticcache.go new file mode 100644 index 0000000000..1064be910f --- /dev/null +++ b/pkg/multicloud/apsara/shell/elasticcache.go @@ -0,0 +1,87 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ElasticcacheListOptions struct { + } + shellutils.R(&ElasticcacheListOptions{}, "elasticcache-list", "List elasticcaches", func(cli *apsara.SRegion, args *ElasticcacheListOptions) error { + instances, e := cli.GetElasticCaches(nil) + if e != nil { + return e + } + printList(instances, len(instances), 0, 0, []string{}) + return nil + }) + + type ElasticcacheIdOptions struct { + ID string `help:"ID of instances to show"` + } + shellutils.R(&ElasticcacheIdOptions{}, "elasticcache-show", "Show elasticcache", func(cli *apsara.SRegion, args *ElasticcacheIdOptions) error { + instance, err := cli.GetElasticCacheById(args.ID) + if err != nil { + return err + } + printObject(instance) + return nil + }) + + type ElasticcacheBackupsListOptions struct { + ID string `help:"ID of instances to show"` + StartTime string `help:"backup start time. format: 2019-03-11T10:00Z"` + EndTime string `help:"backup end time. format: 2019-03-11T10:00Z"` + } + + shellutils.R(&ElasticcacheBackupsListOptions{}, "elasticcache-backup-list", "List elasticcache backups", func(cli *apsara.SRegion, args *ElasticcacheBackupsListOptions) error { + backups, err := cli.GetElasticCacheBackups(args.ID, args.StartTime, args.EndTime) + if err != nil { + return err + } + printList(backups, 0, 0, 0, []string{}) + return nil + }) + + shellutils.R(&ElasticcacheIdOptions{}, "elasticcache-parameter-list", "List elasticcache parameters", func(cli *apsara.SRegion, args *ElasticcacheIdOptions) error { + parameters, err := cli.GetElasticCacheParameters(args.ID) + if err != nil { + return err + } + printList(parameters, 0, 0, 0, []string{}) + return nil + }) + + shellutils.R(&ElasticcacheIdOptions{}, "elasticcache-account-list", "List elasticcache accounts", func(cli *apsara.SRegion, args *ElasticcacheIdOptions) error { + accounts, err := cli.GetElasticCacheAccounts(args.ID) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, []string{}) + return nil + }) + + shellutils.R(&ElasticcacheIdOptions{}, "elasticcache-acl-list", "List elasticcache security ip rules", func(cli *apsara.SRegion, args *ElasticcacheIdOptions) error { + acls, err := cli.GetElasticCacheAcls(args.ID) + if err != nil { + return err + } + printList(acls, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/image.go b/pkg/multicloud/apsara/shell/image.go new file mode 100644 index 0000000000..52b8f997fd --- /dev/null +++ b/pkg/multicloud/apsara/shell/image.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 shell + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ImageListOptions struct { + Status string `help:"image status type" choices:"Creating|Available|UnAvailable|CreateFailed"` + Owner string `help:"Owner type" choices:"system|self|others|marketplace"` + Id []string `help:"Image ID"` + Name string `help:"image name"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *apsara.SRegion, args *ImageListOptions) error { + images, total, e := cli.GetImages(apsara.ImageStatusType(args.Status), apsara.ImageOwnerType(args.Owner), args.Id, args.Name, args.Offset, args.Limit) + if e != nil { + return e + } + printList(images, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type ImageShowOptions struct { + ID string `help:"image ID"` + } + shellutils.R(&ImageShowOptions{}, "image-show", "Show image", func(cli *apsara.SRegion, args *ImageShowOptions) error { + img, err := cli.GetImage(args.ID) + if err != nil { + return err + } + printObject(img) + return nil + }) + + type ImageDeleteOptions struct { + ID string `help:"ID or Name to delete"` + } + shellutils.R(&ImageDeleteOptions{}, "image-delete", "Delete image", func(cli *apsara.SRegion, args *ImageDeleteOptions) error { + return cli.DeleteImage(args.ID) + }) + + type ImageCreateOptions struct { + SNAPSHOT string `help:"Snapshot id"` + NAME string `help:"Image name"` + Desc string `help:"Image desc"` + } + shellutils.R(&ImageCreateOptions{}, "image-create", "Create image", func(cli *apsara.SRegion, args *ImageCreateOptions) error { + imageId, err := cli.CreateImage(args.SNAPSHOT, args.NAME, args.Desc) + if err != nil { + return err + } + fmt.Println(imageId) + return nil + }) + + type ImageExportOptions struct { + ID string `help:"ID or Name to export"` + BUCKET string `help:"Bucket name"` + } + + shellutils.R(&ImageExportOptions{}, "image-export", "Export image", func(cli *apsara.SRegion, args *ImageExportOptions) error { + oss, err := cli.GetOssClient() + if err != nil { + return err + } + exist, err := oss.IsBucketExist(args.BUCKET) + if err != nil { + return err + } + if !exist { + return fmt.Errorf("not exist bucket %s", args.BUCKET) + } + bucket, err := oss.Bucket(args.BUCKET) + if err != nil { + return err + } + task, err := cli.ExportImage(args.ID, bucket) + if err != nil { + return err + } + printObject(task) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/instance.go b/pkg/multicloud/apsara/shell/instance.go new file mode 100644 index 0000000000..998f99ab4a --- /dev/null +++ b/pkg/multicloud/apsara/shell/instance.go @@ -0,0 +1,214 @@ +// 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 ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceListOptions struct { + Id []string `help:"IDs of instances to show"` + Zone string `help:"Zone ID"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&InstanceListOptions{}, "instance-list", "List intances", func(cli *apsara.SRegion, args *InstanceListOptions) error { + instances, total, e := cli.GetInstances(args.Zone, args.Id, args.Offset, args.Limit) + if e != nil { + return e + } + printList(instances, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type InstanceCreateOptions struct { + NAME string `help:"name of instance"` + IMAGE string `help:"image ID"` + CPU int `help:"CPU count"` + MEMORYGB int `help:"MemoryGB"` + Disk []int `help:"Data disk sizes int GB"` + STORAGE string `help:"Storage type"` + VSWITCH string `help:"Vswitch ID"` + PASSWD string `help:"password"` + PublicKey string `help:"PublicKey"` + } + shellutils.R(&InstanceCreateOptions{}, "instance-create", "Create a instance", func(cli *apsara.SRegion, args *InstanceCreateOptions) error { + instance, e := cli.CreateInstanceSimple(args.NAME, args.IMAGE, args.CPU, args.MEMORYGB, args.STORAGE, args.Disk, args.VSWITCH, args.PASSWD, args.PublicKey) + if e != nil { + return e + } + printObject(instance) + return nil + }) + + type InstanceDiskOperationOptions struct { + ID string `help:"instance ID"` + DISK string `help:"disk ID"` + } + + shellutils.R(&InstanceDiskOperationOptions{}, "instance-attach-disk", "Attach a disk to instance", func(cli *apsara.SRegion, args *InstanceDiskOperationOptions) error { + err := cli.AttachDisk(args.ID, args.DISK) + if err != nil { + return err + } + return nil + }) + + shellutils.R(&InstanceDiskOperationOptions{}, "instance-detach-disk", "Detach a disk to instance", func(cli *apsara.SRegion, args *InstanceDiskOperationOptions) error { + err := cli.DetachDisk(args.ID, args.DISK) + if err != nil { + return err + } + return nil + }) + + type InstanceOperationOptions struct { + ID string `help:"instance ID"` + } + shellutils.R(&InstanceOperationOptions{}, "instance-start", "Start a instance", func(cli *apsara.SRegion, args *InstanceOperationOptions) error { + err := cli.StartVM(args.ID) + if err != nil { + return err + } + return nil + }) + + shellutils.R(&InstanceOperationOptions{}, "instance-auto-renew-info", "Show instance auto renew info", func(cli *apsara.SRegion, args *InstanceOperationOptions) error { + info, err := cli.GetInstanceAutoRenewAttribute(args.ID) + if err != nil { + return err + } + printObject(info) + return nil + }) + + shellutils.R(&InstanceOperationOptions{}, "instance-eip-convert", "Convert instance public ip to eip", func(cli *apsara.SRegion, args *InstanceOperationOptions) error { + err := cli.ConvertPublicIpToEip(args.ID) + if err != nil { + return err + } + return nil + }) + + shellutils.R(&InstanceOperationOptions{}, "instance-vnc", "Get a instance VNC url", func(cli *apsara.SRegion, args *InstanceOperationOptions) error { + url, err := cli.GetInstanceVNCUrl(args.ID) + if err != nil { + return err + } + fmt.Println(url) + return nil + }) + + type InstanceStopOptions struct { + ID string `help:"instance ID"` + Force bool `help:"Force stop instance"` + } + shellutils.R(&InstanceStopOptions{}, "instance-stop", "Stop a instance", func(cli *apsara.SRegion, args *InstanceStopOptions) error { + err := cli.StopVM(args.ID, args.Force) + if err != nil { + return err + } + return nil + }) + shellutils.R(&InstanceOperationOptions{}, "instance-delete", "Delete a instance", func(cli *apsara.SRegion, args *InstanceOperationOptions) error { + err := cli.DeleteVM(args.ID) + if err != nil { + return err + } + return nil + }) + + /* + server-change-config 更改系统配置 + server-reset + */ + type InstanceDeployOptions struct { + ID string `help:"instance ID"` + Name string `help:"new instance name"` + Hostname string `help:"new hostname"` + Keypair string `help:"Keypair Name"` + DeleteKeypair bool `help:"Remove SSH keypair"` + Password string `help:"new password"` + // ResetPassword bool `help:"Force reset password"` + Description string `help:"new instances description"` + } + + shellutils.R(&InstanceDeployOptions{}, "instance-deploy", "Deploy keypair/password to a stopped virtual server", func(cli *apsara.SRegion, args *InstanceDeployOptions) error { + err := cli.DeployVM(args.ID, args.Name, args.Password, args.Keypair, args.DeleteKeypair, args.Description) + if err != nil { + return err + } + return nil + }) + + type InstanceRebuildRootOptions struct { + ID string `help:"instance ID"` + Image string `help:"Image ID"` + Password string `help:"pasword"` + Keypair string `help:"keypair name"` + Size int `help:"system disk size in GB"` + } + + shellutils.R(&InstanceRebuildRootOptions{}, "instance-rebuild-root", "Reinstall virtual server system image", func(cli *apsara.SRegion, args *InstanceRebuildRootOptions) error { + diskID, err := cli.ReplaceSystemDisk(args.ID, args.Image, args.Password, args.Keypair, args.Size) + if err != nil { + return err + } + fmt.Printf("New diskID is %s", diskID) + return nil + }) + + type InstanceChangeConfigOptions struct { + ID string `help:"instance ID"` + InstanceTypeId string `help:"instance type"` + Disk []int `help:"Data disk sizes int GB"` + } + + shellutils.R(&InstanceChangeConfigOptions{}, "instance-change-config", "Deploy keypair/password to a stopped virtual server", func(cli *apsara.SRegion, args *InstanceChangeConfigOptions) error { + instance, e := cli.GetInstance(args.ID) + if e != nil { + return e + } + + err := cli.ChangeVMConfig2(instance.ZoneId, args.ID, args.InstanceTypeId, nil) + if err != nil { + return err + } + return nil + }) + + type InstanceUpdatePasswordOptions struct { + ID string `help:"Instance ID"` + PASSWD string `help:"new password"` + } + shellutils.R(&InstanceUpdatePasswordOptions{}, "instance-update-password", "Update instance password", func(cli *apsara.SRegion, args *InstanceUpdatePasswordOptions) error { + err := cli.UpdateInstancePassword(args.ID, args.PASSWD) + return err + }) + + type InstanceSetAutoRenewOptions struct { + ID string `help:"Instance ID"` + AutoRenew bool `help:"Is auto renew instance"` + } + + shellutils.R(&InstanceSetAutoRenewOptions{}, "instance-set-auto-renew", "Set instance auto renew", func(cli *apsara.SRegion, args *InstanceSetAutoRenewOptions) error { + return cli.SetInstanceAutoRenew(args.ID, args.AutoRenew) + }) + +} diff --git a/pkg/multicloud/apsara/shell/instancetype.go b/pkg/multicloud/apsara/shell/instancetype.go new file mode 100644 index 0000000000..8e9aebdeb9 --- /dev/null +++ b/pkg/multicloud/apsara/shell/instancetype.go @@ -0,0 +1,48 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceTypeListOptions struct { + } + shellutils.R(&InstanceTypeListOptions{}, "instance-type-list", "List intance types", func(cli *apsara.SRegion, args *InstanceTypeListOptions) error { + instanceTypes, e := cli.GetInstanceTypes() + if e != nil { + return e + } + printList(instanceTypes, 0, 0, 0, []string{}) + return nil + }) + + type InstanceMatchOptions struct { + CPU int `help:"CPU count"` + MEM int `help:"Memory in MB"` + GPU int `help:"GPU size"` + Zone string `help:"Test in zone"` + } + shellutils.R(&InstanceMatchOptions{}, "instance-type-select", "Select matching instance types", func(cli *apsara.SRegion, args *InstanceMatchOptions) error { + instanceTypes, e := cli.GetMatchInstanceTypes(args.CPU, args.MEM, args.GPU, args.Zone) + if e != nil { + return e + } + printList(instanceTypes, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/keypair.go b/pkg/multicloud/apsara/shell/keypair.go new file mode 100644 index 0000000000..c1e523ea82 --- /dev/null +++ b/pkg/multicloud/apsara/shell/keypair.go @@ -0,0 +1,48 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type KeyPairListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&KeyPairListOptions{}, "keypair-list", "List keypairs", func(cli *apsara.SRegion, args *KeyPairListOptions) error { + keypairs, total, e := cli.GetKeypairs("", "", args.Offset, args.Limit) + if e != nil { + return e + } + printList(keypairs, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type KeyPairImportOptions struct { + NAME string `help:"Name of new keypair"` + PUBKEY string `help:"Public key string"` + } + shellutils.R(&KeyPairImportOptions{}, "keypair-import", "Import a keypair", func(cli *apsara.SRegion, args *KeyPairImportOptions) error { + keypair, err := cli.ImportKeypair(args.NAME, args.PUBKEY) + if err != nil { + return err + } + printObject(keypair) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/loadbalancer.go b/pkg/multicloud/apsara/shell/loadbalancer.go new file mode 100644 index 0000000000..1effd9f3e8 --- /dev/null +++ b/pkg/multicloud/apsara/shell/loadbalancer.go @@ -0,0 +1,47 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerListOptions struct { + Ids []string `help:"Loadbalancer ids"` + } + shellutils.R(&LoadbalancerListOptions{}, "lb-list", "List loadbalancers", func(cli *apsara.SRegion, args *LoadbalancerListOptions) error { + lbs, err := cli.GetLoadbalancers(args.Ids) + if err != nil { + return err + } + printList(lbs, len(lbs), 0, 0, []string{}) + return nil + }) + + type LoadbalancerOptions struct { + ID string `help:"ID of loadbalancer"` + } + shellutils.R(&LoadbalancerOptions{}, "lb-show", "Show loadbalancer", func(cli *apsara.SRegion, args *LoadbalancerOptions) error { + lb, err := cli.GetLoadbalancerDetail(args.ID) + if err != nil { + return err + } + printObject(lb) + return nil + }) + +} diff --git a/pkg/multicloud/azure/shell/business.go b/pkg/multicloud/apsara/shell/loadbalanceracl.go similarity index 51% rename from pkg/multicloud/azure/shell/business.go rename to pkg/multicloud/apsara/shell/loadbalanceracl.go index 7e99a15c72..72d21ddd2f 100644 --- a/pkg/multicloud/azure/shell/business.go +++ b/pkg/multicloud/apsara/shell/loadbalanceracl.go @@ -15,33 +15,19 @@ package shell import ( - "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/multicloud/apsara" "yunion.io/x/onecloud/pkg/util/shellutils" ) func init() { - type AccountBalanceOptions struct { + type LoadbalancerACLListOptions struct { } - shellutils.R(&AccountBalanceOptions{}, "balance", "Get account balance", func(cli *azure.SRegion, args *AccountBalanceOptions) error { - if result1, err := cli.GetClient().QueryAccountBalance(); err != nil { + shellutils.R(&LoadbalancerACLListOptions{}, "lb-acl-list", "List loadbalanceAcls", func(cli *apsara.SRegion, args *LoadbalancerACLListOptions) error { + acls, err := cli.GetLoadBalancerAcls() + if err != nil { return err - } else if result1 != nil { - printObject(result1) - return nil } - - // result2, err := cli.GetClient().QueryCashCoupons() - // if err != nil { - // return err - // } - // printList(result2, len(result2), 0, 0, nil) - - // result3, err := cli.GetClient().QueryPrepaidCards() - // if err != nil { - // return err - // } - // printList(result3, len(result3), 0, 0, nil) - // return nil + printList(acls, len(acls), 0, 0, []string{}) return nil }) } diff --git a/pkg/multicloud/apsara/shell/loadbalancerbackend.go b/pkg/multicloud/apsara/shell/loadbalancerbackend.go new file mode 100644 index 0000000000..a8f99fb6dc --- /dev/null +++ b/pkg/multicloud/apsara/shell/loadbalancerbackend.go @@ -0,0 +1,43 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerBackendListOptions struct { + GROUPID string `help:"LoadbalancerBackendgroup ID"` + } + shellutils.R(&LoadbalancerBackendListOptions{}, "lb-backend-list", "List loadbalanceBackends", func(cli *apsara.SRegion, args *LoadbalancerBackendListOptions) error { + backends, err := cli.GetLoadbalancerBackends(args.GROUPID) + if err != nil { + return err + } + printList(backends, len(backends), 0, 0, []string{}) + return nil + }) + + shellutils.R(&LoadbalancerBackendListOptions{}, "lb-master-slave-backend-list", "List loadbalanceMasterSlaveBackends", func(cli *apsara.SRegion, args *LoadbalancerBackendListOptions) error { + backends, err := cli.GetLoadbalancerMasterSlaveBackends(args.GROUPID) + if err != nil { + return err + } + printList(backends, len(backends), 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/loadbalancerbackendgroup.go b/pkg/multicloud/apsara/shell/loadbalancerbackendgroup.go new file mode 100644 index 0000000000..db519bc18d --- /dev/null +++ b/pkg/multicloud/apsara/shell/loadbalancerbackendgroup.go @@ -0,0 +1,44 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerBackendgroupListOptions struct { + ID string `help:"ID of Loadbalancer"` + } + shellutils.R(&LoadbalancerBackendgroupListOptions{}, "lb-backendgroup-list", "List loadbalancerBackendgroups", func(cli *apsara.SRegion, args *LoadbalancerBackendgroupListOptions) error { + backendgroups, err := cli.GetLoadbalancerBackendgroups(args.ID) + if err != nil { + return err + } + printList(backendgroups, len(backendgroups), 0, 0, []string{}) + return nil + }) + + shellutils.R(&LoadbalancerBackendgroupListOptions{}, "lb-master-slave-backendgroup-list", "List loadbalancerMasterSlaveBackendgroups", func(cli *apsara.SRegion, args *LoadbalancerBackendgroupListOptions) error { + backendgroups, err := cli.GetLoadbalancerMasterSlaveBackendgroups(args.ID) + if err != nil { + return err + } + printList(backendgroups, len(backendgroups), 0, 0, []string{}) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/loadbalancercertificate.go b/pkg/multicloud/apsara/shell/loadbalancercertificate.go new file mode 100644 index 0000000000..3fa87366ca --- /dev/null +++ b/pkg/multicloud/apsara/shell/loadbalancercertificate.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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerServerCertificateListOptions struct { + } + shellutils.R(&LoadbalancerServerCertificateListOptions{}, "lb-server-certificate-list", "List ServerCertificates", func(cli *apsara.SRegion, args *LoadbalancerServerCertificateListOptions) error { + serverCertificate, err := cli.GetLoadbalancerServerCertificates() + if err != nil { + return err + } + printList(serverCertificate, len(serverCertificate), 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/loadbalancerlistener.go b/pkg/multicloud/apsara/shell/loadbalancerlistener.go new file mode 100644 index 0000000000..1d28af9444 --- /dev/null +++ b/pkg/multicloud/apsara/shell/loadbalancerlistener.go @@ -0,0 +1,63 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerListenerListOptions struct { + ID string `help:"ID of loadbalancer"` + PORT int `help:"PORT of listenerPort"` + } + shellutils.R(&LoadbalancerListenerListOptions{}, "lb-http-listener-show", "Show LoadbalancerHTTPListener", func(cli *apsara.SRegion, args *LoadbalancerListenerListOptions) error { + listener, err := cli.GetLoadbalancerHTTPListener(args.ID, args.PORT) + if err != nil { + return err + } + printObject(listener) + return nil + }) + + shellutils.R(&LoadbalancerListenerListOptions{}, "lb-https-listener-show", "Show LoadbalancerHTTPSListener", func(cli *apsara.SRegion, args *LoadbalancerListenerListOptions) error { + listener, err := cli.GetLoadbalancerHTTPSListener(args.ID, args.PORT) + if err != nil { + return err + } + printObject(listener) + return nil + }) + + shellutils.R(&LoadbalancerListenerListOptions{}, "lb-tcp-listener-show", "Show LoadbalancerTCPListener", func(cli *apsara.SRegion, args *LoadbalancerListenerListOptions) error { + listener, err := cli.GetLoadbalancerTCPListener(args.ID, args.PORT) + if err != nil { + return err + } + printObject(listener) + return nil + }) + + shellutils.R(&LoadbalancerListenerListOptions{}, "lb-udp-listener-show", "Show LoadbalancerUDPListener", func(cli *apsara.SRegion, args *LoadbalancerListenerListOptions) error { + listener, err := cli.GetLoadbalancerUDPListener(args.ID, args.PORT) + if err != nil { + return err + } + printObject(listener) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/loadbalancerlistenerrule.go b/pkg/multicloud/apsara/shell/loadbalancerlistenerrule.go new file mode 100644 index 0000000000..c8c3f7cb66 --- /dev/null +++ b/pkg/multicloud/apsara/shell/loadbalancerlistenerrule.go @@ -0,0 +1,35 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerListenerRuleListOptions struct { + ID string `help:"ID of loadbalaner"` + PORT int `help:"Port of listener port"` + } + shellutils.R(&LoadbalancerListenerRuleListOptions{}, "lb-listener-rule-list", "List LoadbalancerListenerRules", func(cli *apsara.SRegion, args *LoadbalancerListenerRuleListOptions) error { + rules, err := cli.GetLoadbalancerListenerRules(args.ID, args.PORT) + if err != nil { + return err + } + printList(rules, len(rules), 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/monitor.go b/pkg/multicloud/apsara/shell/monitor.go new file mode 100644 index 0000000000..e32ea7af43 --- /dev/null +++ b/pkg/multicloud/apsara/shell/monitor.go @@ -0,0 +1,85 @@ +// 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 ( + "time" + + "yunion.io/x/pkg/util/timeutils" + + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/printutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NamespaceListOptions struct { + } + shellutils.R(&NamespaceListOptions{}, "namespace-list", "List monbitor metric namespaces", func(cli *apsara.SRegion, args *NamespaceListOptions) error { + nslist, err := cli.FetchNamespaces() + if err != nil { + return err + } + printList(nslist, 0, 0, 0, nil) + return nil + }) + + type MetricListOptions struct { + NAMESPACE string `help:"namespace"` + } + shellutils.R(&MetricListOptions{}, "metrics-list", "List metrics in a namespace", func(cli *apsara.SRegion, args *MetricListOptions) error { + metrics, err := cli.FetchMetrics(args.NAMESPACE) + if err != nil { + return err + } + printList(metrics, 0, 0, 0, nil) + return nil + }) + + type DescribeMetricListOptions struct { + METRIC string `help:"metric name"` + NAMESPACE string `help:"name space"` + Since string `help:"since, 2019-11-29T11:22:00Z"` + Until string `help:"since, 2019-11-30T11:22:00Z"` + } + shellutils.R(&DescribeMetricListOptions{}, "metric-data-list", "DescribeMetricList", func(cli *apsara.SRegion, args *DescribeMetricListOptions) error { + var since time.Time + var err error + if len(args.Since) > 0 { + since, err = timeutils.ParseTimeStr(args.Since) + if err != nil { + return err + } + } + var until time.Time + if len(args.Until) > 0 { + until, err = timeutils.ParseTimeStr(args.Until) + if err != nil { + return err + } + } + data, err := cli.FetchMetricData(args.METRIC, args.NAMESPACE, since, until) + if err != nil { + return err + } + result := &modulebase.ListResult{ + Data: data, + Total: len(data), + } + printutils.PrintJSONList(result, nil) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/natgateway.go b/pkg/multicloud/apsara/shell/natgateway.go new file mode 100644 index 0000000000..6f743e5179 --- /dev/null +++ b/pkg/multicloud/apsara/shell/natgateway.go @@ -0,0 +1,156 @@ +// 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/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NatGatewayListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&NatGatewayListOptions{}, "natgateway-list", "List NAT gateways", func(cli *apsara.SRegion, args *NatGatewayListOptions) error { + gws, total, e := cli.GetNatGateways("", "", args.Offset, args.Limit) + if e != nil { + return e + } + printList(gws, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type NatSEntryListOptions struct { + ID string `help:"SNat Table ID"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&NatSEntryListOptions{}, "snat-entry-list", "List SNAT entries", func(cli *apsara.SRegion, args *NatSEntryListOptions) error { + entries, total, e := cli.GetSNATEntries(args.ID, args.Offset, args.Limit) + if e != nil { + return e + } + printList(entries, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type NatDEntryListOptions struct { + ID string `help:"DNat Table ID"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&NatDEntryListOptions{}, "dnat-entry-list", "List DNAT entries", func(cli *apsara.SRegion, args *NatDEntryListOptions) error { + entries, total, e := cli.GetForwardTableEntries(args.ID, args.Offset, args.Limit) + if e != nil { + return e + } + printList(entries, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type SCreateDNatOptions struct { + GatewayID string `help:"Nat Gateway ID" positional:"true"` + Protocol string `help:"Protocol(tcp/udp)" positional:"true"` + ExternalIP string `help:"External IP" positional:"true"` + ExternalPort int `help:"External Port" positional:"true"` + InternalIP string `help:"Internal IP" positional:"true"` + InternalPort int `help:"Nat Gateway ID" positional:"true"` + } + shellutils.R(&SCreateDNatOptions{}, "dnat-entry-create", "Create DNAT entry", func(region *apsara.SRegion, args *SCreateDNatOptions) error { + rule := cloudprovider.SNatDRule{ + Protocol: args.Protocol, + ExternalIP: args.ExternalIP, + ExternalPort: args.ExternalPort, + InternalIP: args.InternalIP, + InternalPort: args.InternalPort, + } + dnat, err := region.CreateForwardTableEntry(rule, args.GatewayID) + if err != nil { + return err + } + printObject(dnat) + return nil + }) + + type SCreateSNatOptions struct { + GatewayID string `help:"Nat Gateway ID" positional:"true"` + SourceCIDR string `help:"Source cidr" positional:"true"` + ExternalIP string `help:"External IP" positional:"true"` + } + shellutils.R(&SCreateSNatOptions{}, "snat-entry-create", "Create SNAT entry", func(region *apsara.SRegion, args *SCreateSNatOptions) error { + rule := cloudprovider.SNatSRule{ + SourceCIDR: args.SourceCIDR, + ExternalIP: args.ExternalIP, + } + snat, err := region.CreateSNATTableEntry(rule, args.GatewayID) + if err != nil { + return err + } + printObject(snat) + return nil + }) + + type SShowSNatOptions struct { + TableID string `help:"SNat Table ID" positional:"true"` + NatID string `help:"SNat Entry ID" positional:"true"` + } + shellutils.R(&SShowSNatOptions{}, "snat-entry-show", "show SNAT entry", func(region *apsara.SRegion, args *SShowSNatOptions) error { + snat, err := region.GetSNATEntry(args.TableID, args.NatID) + if err != nil { + return err + } + printObject(snat) + return nil + }) + + type SShowDNatOptions struct { + TableID string `help:"DNat Table ID" positional:"true"` + NatID string `help:"DNat Entry ID" positional:"true"` + } + shellutils.R(&SShowDNatOptions{}, "dnat-entry-show", "show SNAT entry", func(region *apsara.SRegion, args *SShowDNatOptions) error { + dnat, err := region.GetForwardTableEntry(args.TableID, args.NatID) + if err != nil { + return err + } + printObject(dnat) + return nil + }) + + type SDeleteSNatOptions struct { + TableID string `help:"DNat Table ID" positional:"true"` + NatID string `help:"SNat Entry ID" positional:"true"` + } + shellutils.R(&SDeleteSNatOptions{}, "snat-entry-delete", "Delete SNAT entry", func(region *apsara.SRegion, args *SDeleteSNatOptions) error { + err := region.DeleteSnatEntry(args.TableID, args.NatID) + if err != nil { + return err + } + return nil + }) + + type SDeleteDNatOptions struct { + TableID string `help:"DNat Table ID" positional:"true"` + NatID string `help:"DNat Entry ID" positional:"true"` + } + shellutils.R(&SDeleteDNatOptions{}, "dnat-entry-delete", "Delete DNAT entry", func(region *apsara.SRegion, args *SDeleteDNatOptions) error { + err := region.DeleteForwardTableEntry(args.TableID, args.NatID) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/networkinterface.go b/pkg/multicloud/apsara/shell/networkinterface.go new file mode 100644 index 0000000000..708f5f11ef --- /dev/null +++ b/pkg/multicloud/apsara/shell/networkinterface.go @@ -0,0 +1,36 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NetworkInterfaceListOptions struct { + InstanceId string `help:"Id or instance"` + Offset int + Limit int + } + shellutils.R(&NetworkInterfaceListOptions{}, "network-interface-list", "List networkinterfaces", func(cli *apsara.SRegion, args *NetworkInterfaceListOptions) error { + interfaces, total, err := cli.GetNetworkInterfaces(args.InstanceId, args.Offset, args.Limit) + if err != nil { + return err + } + printList(interfaces, total, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/oss.go b/pkg/multicloud/apsara/shell/oss.go new file mode 100644 index 0000000000..ef8d0a3f69 --- /dev/null +++ b/pkg/multicloud/apsara/shell/oss.go @@ -0,0 +1,202 @@ +// 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 ( + "fmt" + "os" + "path/filepath" + + osslib "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type progressListener struct { +} + +func (this *progressListener) ProgressChanged(event *osslib.ProgressEvent) { + switch event.EventType { + case osslib.TransferStartedEvent: + fmt.Printf("\n") + case osslib.TransferDataEvent: + fmt.Printf("Progess: %f%%\r", (float64(event.ConsumedBytes) * 100.0 / float64(event.TotalBytes))) + case osslib.TransferCompletedEvent: + fmt.Printf("Transfer complete!\n") + case osslib.TransferFailedEvent: + fmt.Printf("Transfer failed!\n") + default: + fmt.Printf("Unknonw event type %d\n", event.EventType) + } +} + +func str2AclType(aclStr string) osslib.ACLType { + switch aclStr { + case "public-rw": + return osslib.ACLPublicReadWrite + case "public-read": + return osslib.ACLPublicRead + default: + return osslib.ACLPrivate + } +} + +func init() { + type OssListOptions struct { + } + shellutils.R(&OssListOptions{}, "oss-list", "List OSS buckets", func(cli *apsara.SRegion, args *OssListOptions) error { + buckets, err := cli.GetIBuckets() + if err != nil { + return err + } + printList(buckets, len(buckets), 0, 50, nil) + return nil + }) + + type OssListBucketOptions struct { + BUCKET string `help:"bucket name"` + } + + shellutils.R(&OssListBucketOptions{}, "oss-list-bucket", "List content of a OSS bucket", func(cli *apsara.SRegion, args *OssListBucketOptions) error { + oss, err := cli.GetOssClient() + if err != nil { + return err + } + bucket, err := oss.Bucket(args.BUCKET) + if err != nil { + return err + } + result, err := bucket.ListObjects() + if err != nil { + return err + } + printList(result.Objects, len(result.Objects), 0, len(result.Objects), nil) + return nil + }) + + type OssCreateBucketOptions struct { + BUCKET string `help:"bucket name"` + StorageClass string `help:"storage class" choices:"Standard|IA|Archive"` + + Acl string `help:"ACL" choices:"private|public-read|public-read-write"` + } + shellutils.R(&OssCreateBucketOptions{}, "oss-create-bucket", "Create a OSS bucket", func(cli *apsara.SRegion, args *OssCreateBucketOptions) error { + err := cli.CreateIBucket(args.BUCKET, args.StorageClass, args.Acl) + if err != nil { + return err + } + return nil + }) + + type OssDeleteBucketOptions struct { + BUCKET string `help:"bucket name"` + } + shellutils.R(&OssDeleteBucketOptions{}, "oss-delete-bucket", "Delete a OSS bucket", func(cli *apsara.SRegion, args *OssDeleteBucketOptions) error { + err := cli.DeleteIBucket(args.BUCKET) + if err != nil { + return err + } + return nil + }) + + type OssUploadOptions struct { + BUCKET string `help:"bucket name"` + KEY string `help:"Object key"` + FILE string `help:"Local file path"` + Progress bool `help:"show progress"` + Acl string `help:"Object ACL" choices:"private|public-read|public-rw"` + } + shellutils.R(&OssUploadOptions{}, "oss-upload", "Upload a file to a OSS bucket", func(cli *apsara.SRegion, args *OssUploadOptions) error { + oss, err := cli.GetOssClient() + if err != nil { + return err + } + bucket, err := oss.Bucket(args.BUCKET) + if err != nil { + return err + } + + options := make([]osslib.Option, 0) + if args.Progress { + listener := progressListener{} + options = append(options, osslib.Progress(&listener)) + } + if len(args.Acl) > 0 { + options = append(options, osslib.ObjectACL(str2AclType(args.Acl))) + } + if fileutils2.IsFile(args.FILE) { + err = bucket.UploadFile(args.KEY, args.FILE, 4*1024*1024, options...) + return err + } else if fileutils2.IsDir(args.FILE) { + return filepath.Walk(args.FILE, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsRegular() { + rel, _ := filepath.Rel(args.FILE, path) + src := path + dst := filepath.Join(args.KEY, rel) + fmt.Println("upload", src, "to", dst) + uploadErr := bucket.UploadFile(dst, src, + 4*1024*1024, options...) + if uploadErr != nil { + return uploadErr + } + } + return nil + }) + } else { + return fmt.Errorf("Unsupported file type %s", args.FILE) + } + }) + + type OssObjectAclOptions struct { + BUCKET string `help:"bucket name"` + KEY string `help:"object key"` + ACL string `help:"ACL" choices:"private|public-read|public-rw"` + } + shellutils.R(&OssObjectAclOptions{}, "oss-set-acl", "Set acl for a object", func(cli *apsara.SRegion, args *OssObjectAclOptions) error { + oss, err := cli.GetOssClient() + if err != nil { + return err + } + bucket, err := oss.Bucket(args.BUCKET) + if err != nil { + return err + } + err = bucket.SetObjectACL(args.KEY, str2AclType(args.ACL)) + return err + }) + + type OssDeleteOptions struct { + BUCKET string `help:"bucket name"` + KEY string `help:"Object key"` + } + + shellutils.R(&OssDeleteOptions{}, "oss-delete", "Delete a file from a OSS bucket", func(cli *apsara.SRegion, args *OssDeleteOptions) error { + oss, err := cli.GetOssClient() + if err != nil { + return err + } + bucket, err := oss.Bucket(args.BUCKET) + if err != nil { + return err + } + err = bucket.DeleteObject(args.KEY) + return err + }) +} diff --git a/pkg/multicloud/apsara/shell/printutils.go b/pkg/multicloud/apsara/shell/printutils.go new file mode 100644 index 0000000000..1b46ec704a --- /dev/null +++ b/pkg/multicloud/apsara/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/apsara/shell/quota.go b/pkg/multicloud/apsara/shell/quota.go new file mode 100644 index 0000000000..9b4e477b2f --- /dev/null +++ b/pkg/multicloud/apsara/shell/quota.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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type QuotaListOptions struct { + } + shellutils.R(&QuotaListOptions{}, "quota-list", "List quota", func(cli *apsara.SRegion, args *QuotaListOptions) error { + quotas, err := cli.GetQuotas() + if err != nil { + return err + } + printList(quotas, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/ram_group.go b/pkg/multicloud/apsara/shell/ram_group.go new file mode 100644 index 0000000000..51ef4e80ae --- /dev/null +++ b/pkg/multicloud/apsara/shell/ram_group.go @@ -0,0 +1,109 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CloudgroupCreateOptions struct { + NAME string + Comments string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create Cloud group", func(cli *apsara.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Comments) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupListOptions struct { + Offset string + Limit int + } + + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List Cloud groups", func(cli *apsara.SRegion, args *CloudgroupListOptions) error { + groups, err := cli.GetClient().ListGroups(args.Offset, args.Limit) + if err != nil { + return err + } + printList(groups.Groups.Group, 0, 0, 0, nil) + return nil + }) + + type CloudgroupDeleteOptions struct { + NAME string + } + + shellutils.R(&CloudgroupDeleteOptions{}, "cloud-group-delete", "Delete Cloud group", func(cli *apsara.SRegion, args *CloudgroupDeleteOptions) error { + return cli.GetClient().DeleteGroup(args.NAME) + }) + + type GroupExtListOptions struct { + GROUP string + Offset string + Limit int + } + shellutils.R(&GroupExtListOptions{}, "cloud-group-user-list", "List Cloud group users", func(cli *apsara.SRegion, args *GroupExtListOptions) error { + users, err := cli.GetClient().ListUsersForGroup(args.GROUP, args.Offset, args.Limit) + if err != nil { + return err + } + printList(users.Users.User, 0, 0, 0, nil) + return nil + }) + + type GroupUserOptions struct { + GROUP string + USER string + } + + shellutils.R(&GroupUserOptions{}, "cloud-group-remove-user", "Remove user from group", func(cli *apsara.SRegion, args *GroupUserOptions) error { + return cli.GetClient().RemoveUserFromGroup(args.GROUP, args.USER) + }) + + shellutils.R(&GroupUserOptions{}, "cloud-group-add-user", "Add user to group", func(cli *apsara.SRegion, args *GroupUserOptions) error { + return cli.GetClient().AddUserToGroup(args.GROUP, args.USER) + }) + + shellutils.R(&GroupExtListOptions{}, "cloud-group-policy-list", "List Cloud group policies", func(cli *apsara.SRegion, args *GroupExtListOptions) error { + policies, err := cli.GetClient().ListPoliciesForGroup(args.GROUP) + if err != nil { + return err + } + printList(policies, 0, 0, 0, nil) + return nil + }) + + type GroupPolicyOptions struct { + GROUP string + PolicyType string `default:"System" choices:"System|Custom"` + POLICY string + } + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-attach-policy", "Attach policy for group", func(cli *apsara.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().AttachPolicyToGroup(args.PolicyType, args.POLICY, args.GROUP) + }) + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-detach-policy", "Detach policy from group", func(cli *apsara.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().DetachPolicyFromGroup(args.PolicyType, args.POLICY, args.GROUP) + }) + +} diff --git a/pkg/multicloud/apsara/shell/ram_policy.go b/pkg/multicloud/apsara/shell/ram_policy.go new file mode 100644 index 0000000000..2319f280e6 --- /dev/null +++ b/pkg/multicloud/apsara/shell/ram_policy.go @@ -0,0 +1,84 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type GetPolicyOptions struct { + POLICYTYPE string + POLICYNAME string + } + shellutils.R(&GetPolicyOptions{}, "cloud-policy-show", "Show ram policy", func(cli *apsara.SRegion, args *GetPolicyOptions) error { + policy, err := cli.GetClient().GetPolicy(args.POLICYTYPE, args.POLICYNAME) + if err != nil { + return err + } + printObject(policy) + return nil + }) + + type DeletePolicyOptions struct { + POLICYTYPE string + POLICYNAME string + } + shellutils.R(&DeletePolicyOptions{}, "cloud-policy-delete", "Delete policy", func(cli *apsara.SRegion, args *DeletePolicyOptions) error { + return cli.GetClient().DeletePolicy(args.POLICYTYPE, args.POLICYNAME) + }) + + type PolicyListOptions struct { + PolicyType string `choices:"System|Custom"` + Offset string + Limit int + } + + shellutils.R(&PolicyListOptions{}, "cloud-policy-list", "List cloud policies", func(cli *apsara.SRegion, args *PolicyListOptions) error { + policies, err := cli.GetClient().ListPolicies(args.PolicyType, args.Offset, args.Limit) + if err != nil { + return err + } + printList(policies.Policies.Policy, 0, 0, 0, nil) + return nil + }) + + type PolicyCreateOptions struct { + NAME string + DOCUMENT string + Desc string + } + + shellutils.R(&PolicyCreateOptions{}, "cloud-policy-create", "Create ram policy", func(cli *apsara.SRegion, args *PolicyCreateOptions) error { + policy, err := cli.GetClient().CreatePolicy(args.NAME, args.DOCUMENT, args.Desc) + if err != nil { + return err + } + printObject(policy) + return nil + }) + + type PolicyCreateVersionOptions struct { + NAME string + DOCUMENT string + IsDefault bool + } + + shellutils.R(&PolicyCreateVersionOptions{}, "cloud-policy-version-create", "Create ram policy version", func(cli *apsara.SRegion, args *PolicyCreateVersionOptions) error { + return cli.GetClient().CreatePolicyVersion(args.NAME, args.DOCUMENT, args.IsDefault) + }) + +} diff --git a/pkg/multicloud/apsara/shell/ram_role.go b/pkg/multicloud/apsara/shell/ram_role.go new file mode 100644 index 0000000000..8aff5f041b --- /dev/null +++ b/pkg/multicloud/apsara/shell/ram_role.go @@ -0,0 +1,102 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ListRolesOptions struct { + Offset string + Limit int + } + shellutils.R(&ListRolesOptions{}, "cloud-role-list", "List ram roles", func(cli *apsara.SRegion, args *ListRolesOptions) error { + roles, err := cli.GetClient().ListRoles(args.Offset, args.Limit) + if err != nil { + return err + } + printList(roles.Roles.Role, 0, 0, 0, []string{}) + return nil + }) + + type GetRoleOptions struct { + ROLENAME string + } + shellutils.R(&GetRoleOptions{}, "cloud-role-show", "Show ram role", func(cli *apsara.SRegion, args *GetRoleOptions) error { + role, err := cli.GetClient().GetRole(args.ROLENAME) + if err != nil { + return err + } + printObject(role) + return nil + }) + + type RolePolicyOptions struct { + ROLENAME string + POLICYNAME string + POLICYTYPE string `choices:"Custom|System"` + } + + shellutils.R(&RolePolicyOptions{}, "cloud-role-attach-policy", "Attach policy for role", func(cli *apsara.SRegion, args *RolePolicyOptions) error { + return cli.GetClient().AttachPolicy2Role(args.POLICYTYPE, args.POLICYNAME, args.ROLENAME) + }) + + shellutils.R(&RolePolicyOptions{}, "cloud-role-detach-policy", "Detach policy from role", func(cli *apsara.SRegion, args *RolePolicyOptions) error { + return cli.GetClient().DetachPolicyFromRole(args.POLICYTYPE, args.POLICYNAME, args.ROLENAME) + }) + + type RolePolicyListOptions struct { + ROLE string + } + + shellutils.R(&RolePolicyListOptions{}, "cloud-role-policy-list", "List cloud role policies", func(cli *apsara.SRegion, args *RolePolicyListOptions) error { + policies, err := cli.GetClient().ListPoliciesForRole(args.ROLE) + if err != nil { + return err + } + printList(policies, 0, 0, 0, nil) + return nil + }) + + type DeleteRoleOptions struct { + NAME string + } + shellutils.R(&DeleteRoleOptions{}, "cloud-role-delete", "Delete role", func(cli *apsara.SRegion, args *DeleteRoleOptions) error { + return cli.GetClient().DeleteRole(args.NAME) + }) + + shellutils.R(&ListRolesOptions{}, "enable-image-import", "Enable image import privilege", func(cli *apsara.SRegion, args *ListRolesOptions) error { + return cli.GetClient().EnableImageImport() + }) + + shellutils.R(&ListRolesOptions{}, "enable-image-export", "Enable image export privilege", func(cli *apsara.SRegion, args *ListRolesOptions) error { + return cli.GetClient().EnableImageExport() + }) + + type CallerShowOptions struct { + } + + shellutils.R(&CallerShowOptions{}, "caller-show", "Show caller info", func(cli *apsara.SRegion, args *CallerShowOptions) error { + caller, err := cli.GetClient().GetCallerIdentity() + if err != nil { + return err + } + printObject(caller) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/ram_user.go b/pkg/multicloud/apsara/shell/ram_user.go new file mode 100644 index 0000000000..990860b2d9 --- /dev/null +++ b/pkg/multicloud/apsara/shell/ram_user.go @@ -0,0 +1,128 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserCreateOptions struct { + NAME string + MobilePhone string + Comments string + Email string + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create Cloud user", func(cli *apsara.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateUser(args.NAME, args.MobilePhone, args.Email, args.Comments) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type ClouduserListOptions struct { + Offset string + Limit int + } + + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List Cloud users", func(cli *apsara.SRegion, args *ClouduserListOptions) error { + users, err := cli.GetClient().ListUsers(args.Offset, args.Limit) + if err != nil { + return err + } + printList(users.Users.User, 0, 0, 0, nil) + return nil + }) + + type UserPolicyListOptions struct { + USER string + } + + shellutils.R(&UserPolicyListOptions{}, "cloud-user-policy-list", "List Cloud user policies", func(cli *apsara.SRegion, args *UserPolicyListOptions) error { + policies, err := cli.GetClient().ListPoliciesForUser(args.USER) + if err != nil { + return err + } + printList(policies, 0, 0, 0, nil) + return nil + }) + + type ClouduserOptions struct { + NAME string + } + + shellutils.R(&ClouduserOptions{}, "cloud-user-group-list", "List Cloud user groups", func(cli *apsara.SRegion, args *ClouduserOptions) error { + groups, err := cli.GetClient().ListGroupsForUser(args.NAME) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-delete", "Delete Cloud user", func(cli *apsara.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteClouduser(args.NAME) + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-loginprofile", "Get Cloud user loginprofile", func(cli *apsara.SRegion, args *ClouduserOptions) error { + profile, err := cli.GetClient().GetLoginProfile(args.NAME) + if err != nil { + return err + } + printObject(profile) + return nil + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-loginprofile-delete", "Delete Cloud user loginprofile", func(cli *apsara.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteLoginProfile(args.NAME) + }) + + type LoginProfileCreateOptions struct { + NAME string + PASSWORD string + } + + shellutils.R(&LoginProfileCreateOptions{}, "cloud-user-loginprofile-create", "Create Cloud user loginprofile", func(cli *apsara.SRegion, args *LoginProfileCreateOptions) error { + profile, err := cli.GetClient().CreateLoginProfile(args.NAME, args.PASSWORD) + if err != nil { + return err + } + printObject(profile) + return nil + }) + + shellutils.R(&LoginProfileCreateOptions{}, "cloud-user-rest-password", "Reset Cloud user password", func(cli *apsara.SRegion, args *LoginProfileCreateOptions) error { + return cli.GetClient().ResetClouduserPassword(args.NAME, args.PASSWORD) + }) + + type ClouduserPolicyOptions struct { + PolicyType string `default:"System" choices:"System|Custom"` + POLICY string + USER string + } + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-attach-policy", "Attach policy for user", func(cli *apsara.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().AttachPolicyToUser(args.POLICY, args.PolicyType, args.USER) + }) + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-detach-policy", "Detach policy from user", func(cli *apsara.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().DetachPolicyFromUser(args.POLICY, args.PolicyType, args.USER) + }) + +} diff --git a/pkg/multicloud/apsara/shell/region.go b/pkg/multicloud/apsara/shell/region.go new file mode 100644 index 0000000000..254a21eee2 --- /dev/null +++ b/pkg/multicloud/apsara/shell/region.go @@ -0,0 +1,32 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/multicloud/test" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + test.TestShell() + type RegionListOptions struct { + } + shellutils.R(&RegionListOptions{}, "region-list", "List regions", func(cli *apsara.SRegion, args *RegionListOptions) error { + regions := cli.GetClient().GetRegions() + printList(regions, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/resource_tag.go b/pkg/multicloud/apsara/shell/resource_tag.go new file mode 100644 index 0000000000..e09d36c576 --- /dev/null +++ b/pkg/multicloud/apsara/shell/resource_tag.go @@ -0,0 +1,59 @@ +// 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 ( + "fmt" + "strings" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type TagGetOptions struct { + SERVICE string `help:"service, eg. ecs"` + RESTYPE string `help:"resource type, eg. instance"` + ID []string `help:"resource Id, eg. ins-123xxx"` + } + shellutils.R(&TagGetOptions{}, "tag-show", "show tag of a specific resource", func(cli *apsara.SRegion, args *TagGetOptions) error { + tags, err := cli.ListResourceTags(args.SERVICE, args.RESTYPE, args.ID) + if err != nil { + return err + } + for id, tag := range tags { + fmt.Println(id, *tag) + } + return nil + }) + + type TagSetOptions struct { + TagGetOptions + Tag []string `help:"tag to set, key:value"` + Replace bool `help:"replace all tags"` + } + shellutils.R(&TagSetOptions{}, "tag-set", "set tags of a specific resource", func(cli *apsara.SRegion, args *TagSetOptions) error { + tags := make(map[string]string) + for _, t := range args.Tag { + parts := strings.Split(t, ":") + tags[parts[0]] = parts[1] + } + err := cli.SetResourceTags(args.SERVICE, args.RESTYPE, args.ID, tags, args.Replace) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/resourcegroup.go b/pkg/multicloud/apsara/shell/resourcegroup.go new file mode 100644 index 0000000000..268b40a477 --- /dev/null +++ b/pkg/multicloud/apsara/shell/resourcegroup.go @@ -0,0 +1,62 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ResourceGroupListOptions struct { + PageSize int + PageNumber int + } + shellutils.R(&ResourceGroupListOptions{}, "resource-group-list", "List resource group", func(cli *apsara.SRegion, args *ResourceGroupListOptions) error { + groups, _, err := cli.GetClient().GetResourceGroups(args.PageNumber, args.PageSize) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type ResourceGroupShowOptions struct { + ID string + } + + shellutils.R(&ResourceGroupShowOptions{}, "resource-group-show", "Show resource group", func(cli *apsara.SRegion, args *ResourceGroupShowOptions) error { + group, err := cli.GetClient().GetResourceGroup(args.ID) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type ResourceGroupCreateOptions struct { + NAME string + } + + shellutils.R(&ResourceGroupCreateOptions{}, "resource-group-create", "Create resource group", func(cli *apsara.SRegion, args *ResourceGroupCreateOptions) error { + group, err := cli.GetClient().CreateResourceGroup(args.NAME) + if err != nil { + return err + } + printObject(group) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/routetable.go b/pkg/multicloud/apsara/shell/routetable.go new file mode 100644 index 0000000000..90a975f136 --- /dev/null +++ b/pkg/multicloud/apsara/shell/routetable.go @@ -0,0 +1,52 @@ +// 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 ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RouteTableListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&RouteTableListOptions{}, "routetable-list", "List routetables", func(cli *apsara.SRegion, args *RouteTableListOptions) error { + routetables, total, e := cli.GetRouteTables(nil, args.Offset, args.Limit) + if e != nil { + return e + } + printList(routetables, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type RouteTableShowOptions struct { + ID string `help:"ID or name of routetable"` + } + shellutils.R(&RouteTableShowOptions{}, "routetable-show", "Show routetable", func(cli *apsara.SRegion, args *RouteTableShowOptions) error { + routetables, _, e := cli.GetRouteTables([]string{args.ID}, 0, 1) + if e != nil { + return e + } + if len(routetables) == 0 { + return fmt.Errorf("No such ID %s", args.ID) + } + printObject(routetables[0]) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/secgroup.go b/pkg/multicloud/apsara/shell/secgroup.go new file mode 100644 index 0000000000..1537655cfd --- /dev/null +++ b/pkg/multicloud/apsara/shell/secgroup.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 ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SecurityGroupListOptions struct { + VpcId string `help:"VPC ID"` + Name string `help:"Secgroup Name"` + SecurityGroupIds []string `help:"SecurityGroup ids"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&SecurityGroupListOptions{}, "security-group-list", "List security group", func(cli *apsara.SRegion, args *SecurityGroupListOptions) error { + secgrps, total, e := cli.GetSecurityGroups(args.VpcId, args.Name, args.SecurityGroupIds, args.Offset, args.Limit) + if e != nil { + return e + } + printList(secgrps, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type SecurityGroupShowOptions struct { + ID string `help:"ID or name of security group"` + } + shellutils.R(&SecurityGroupShowOptions{}, "security-group-show", "Show details of a security group", func(cli *apsara.SRegion, args *SecurityGroupShowOptions) error { + secgrp, err := cli.GetSecurityGroupDetails(args.ID) + if err != nil { + return err + } + printObject(secgrp) + return nil + }) + + type SecurityGroupCreateOptions struct { + NAME string `help:"SecurityGroup name"` + VpcId string `help:"VPC ID"` + Desc string `help:"SecurityGroup description"` + } + + shellutils.R(&SecurityGroupCreateOptions{}, "security-group-create", "Create details of a security group", func(cli *apsara.SRegion, args *SecurityGroupCreateOptions) error { + secgroupId, err := cli.CreateSecurityGroup(args.VpcId, args.NAME, args.Desc) + if err != nil { + return err + } + fmt.Printf("secgroupId: %s", secgroupId) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/snapshot.go b/pkg/multicloud/apsara/shell/snapshot.go new file mode 100644 index 0000000000..946d8fd125 --- /dev/null +++ b/pkg/multicloud/apsara/shell/snapshot.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 ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SnapshotListOptions struct { + DiskId string `help:"Disk ID"` + InstanceId string `help:"Instance ID"` + SnapshotIds []string `helo:"Snapshot ids"` + Name string `help:"Snapshot Name"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&SnapshotListOptions{}, "snapshot-list", "List snapshot", func(cli *apsara.SRegion, args *SnapshotListOptions) error { + if snapshots, total, err := cli.GetSnapshots(args.InstanceId, args.DiskId, args.Name, args.SnapshotIds, args.Offset, args.Limit); err != nil { + return err + } else { + printList(snapshots, total, args.Offset, args.Limit, []string{}) + return nil + } + }) + + type SnapshotDeleteOptions struct { + ID string `help:"Snapshot ID"` + } + + shellutils.R(&SnapshotDeleteOptions{}, "snapshot-delete", "Delete snapshot", func(cli *apsara.SRegion, args *SnapshotDeleteOptions) error { + if err := cli.SnapshotPreDelete(args.ID); err != nil { + return fmt.Errorf("Snapshot PreDelete error: %s", err) + } + return cli.DeleteSnapshot(args.ID) + }) + + type SnapshotCreateOptions struct { + DiskId string `help:"Disk ID"` + Name string `help:"Snapeshot Name"` + Desc string `help:"Snapshot Desc"` + } + + shellutils.R(&SnapshotCreateOptions{}, "snapshot-create", "Create snapshot", func(cli *apsara.SRegion, args *SnapshotCreateOptions) error { + snapshotId, err := cli.CreateSnapshot(args.DiskId, args.Name, args.Desc) + if err != nil { + return err + } + fmt.Println(snapshotId) + return nil + }) + +} diff --git a/pkg/multicloud/apsara/shell/snapshot_policy.go b/pkg/multicloud/apsara/shell/snapshot_policy.go new file mode 100644 index 0000000000..48aa26256e --- /dev/null +++ b/pkg/multicloud/apsara/shell/snapshot_policy.go @@ -0,0 +1,95 @@ +// 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/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SSnapshotPolicyListOptions struct { + PolicyId string `help:"snapshot policy id"` + Offset int `help:"offset"` + Limit int `help:"limit"` + } + + shellutils.R(&SSnapshotPolicyListOptions{}, "snapshot-policy-list", "list snapshot policy", + func(cli *apsara.SRegion, args *SSnapshotPolicyListOptions) error { + snapshotPolicis, num, err := cli.GetSnapshotPolicies(args.PolicyId, args.Offset, args.Limit) + if err != nil { + return err + } + printList(snapshotPolicis, num, args.Offset, args.Limit, []string{}) + return nil + }, + ) + + type SSnapshotPolicyDeleteOptions struct { + ID string `help:"snapshot id"` + } + shellutils.R(&SSnapshotPolicyDeleteOptions{}, "snapshot-policy-delete", "delete snapshot policy", + func(cli *apsara.SRegion, args *SSnapshotPolicyDeleteOptions) error { + err := cli.DeleteSnapshotPolicy(args.ID) + return err + }, + ) + + type SSnapshotPolicyCreateOptions struct { + Name string `help:"snapshot name"` + + RetentionDays int `help:"retention days"` + RepeatWeekdays []int `help:"auto snapshot which days of the week"` + TimePoints []int `help:"auto snapshot which hours of the day"` + } + shellutils.R(&SSnapshotPolicyCreateOptions{}, "snapshot-policy-create", "create snapshot policy", + func(cli *apsara.SRegion, args *SSnapshotPolicyCreateOptions) error { + input := cloudprovider.SnapshotPolicyInput{ + RetentionDays: args.RetentionDays, + RepeatWeekdays: args.RepeatWeekdays, + TimePoints: args.TimePoints, + PolicyName: args.Name, + } + _, err := cli.CreateSnapshotPolicy(&input) + if err != nil { + return err + } + return nil + }, + ) + + type SSnapshotPolicyApplyOptions struct { + SNAPSHOTPOLICYID string `help:"snapshot policy id"` + DISKID string `help:"disk id"` + } + shellutils.R(&SSnapshotPolicyApplyOptions{}, "snapshot-policy-apply", "apply snapshot policy", + func(cli *apsara.SRegion, args *SSnapshotPolicyApplyOptions) error { + err := cli.ApplySnapshotPolicyToDisks(args.SNAPSHOTPOLICYID, args.DISKID) + return err + }, + ) + + type SSnapshotPolicyCancelOptions struct { + SNAPSHOTPOLICYID string `help:"snapshot policy id"` + DISKID string `help:"disk id"` + } + shellutils.R(&SSnapshotPolicyCancelOptions{}, "snapshot-policy-cancel", "cancel snapshot policy", + func(cli *apsara.SRegion, args *SSnapshotPolicyCancelOptions) error { + err := cli.CancelSnapshotPolicyToDisks(args.SNAPSHOTPOLICYID, args.DISKID) + return err + }, + ) +} diff --git a/pkg/multicloud/apsara/shell/task.go b/pkg/multicloud/apsara/shell/task.go new file mode 100644 index 0000000000..64a963b52b --- /dev/null +++ b/pkg/multicloud/apsara/shell/task.go @@ -0,0 +1,56 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type TaskListOptions struct { + TYPE string `help:"Task types, either ImportImage or ExportImage" choices:"ImportImage|ExportImage"` + Task []string `help:"Task ID"` + Status string `help:"Task status" choices:"Finished|Processing|Waiting|Deleted|Paused|Failed"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&TaskListOptions{}, "task-list", "List tasks", func(cli *apsara.SRegion, args *TaskListOptions) error { + tasks, total, err := cli.GetTasks(apsara.TaskActionType(args.TYPE), args.Task, apsara.TaskStatusType(args.Status), args.Offset, args.Limit) + if err != nil { + return err + } + printList(tasks, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type TaskIDOptions struct { + ID string `help:"Task ID"` + } + + shellutils.R(&TaskIDOptions{}, "task-show", "Show task", func(cli *apsara.SRegion, args *TaskIDOptions) error { + task, err := cli.GetTask(args.ID) + if err != nil { + return err + } + printObject(task) + return nil + }) + + shellutils.R(&TaskIDOptions{}, "cancel-task", "Cancel task", func(cli *apsara.SRegion, args *TaskIDOptions) error { + return cli.CancelTask(args.ID) + }) + +} diff --git a/pkg/multicloud/apsara/shell/vpc.go b/pkg/multicloud/apsara/shell/vpc.go new file mode 100644 index 0000000000..84a0b43a1e --- /dev/null +++ b/pkg/multicloud/apsara/shell/vpc.go @@ -0,0 +1,44 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VpcListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&VpcListOptions{}, "vpc-list", "List vpcs", func(cli *apsara.SRegion, args *VpcListOptions) error { + vpcs, total, e := cli.GetVpcs(nil, args.Offset, args.Limit) + if e != nil { + return e + } + printList(vpcs, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type VpcOptions struct { + ID string `help:"VPC id"` + } + + shellutils.R(&VpcOptions{}, "vpc-delete", "Delete vpc", func(cli *apsara.SRegion, args *VpcOptions) error { + return cli.DeleteVpc(args.ID) + }) + +} diff --git a/pkg/multicloud/apsara/shell/vrouter.go b/pkg/multicloud/apsara/shell/vrouter.go new file mode 100644 index 0000000000..927b6abd99 --- /dev/null +++ b/pkg/multicloud/apsara/shell/vrouter.go @@ -0,0 +1,35 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VRouterListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&VRouterListOptions{}, "vrouter-list", "List vrouters", func(cli *apsara.SRegion, args *VRouterListOptions) error { + vrouters, total, e := cli.GetVRouters(args.Offset, args.Limit) + if e != nil { + return e + } + printList(vrouters, total, args.Offset, args.Limit, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/vswitch.go b/pkg/multicloud/apsara/shell/vswitch.go new file mode 100644 index 0000000000..668ca10b3a --- /dev/null +++ b/pkg/multicloud/apsara/shell/vswitch.go @@ -0,0 +1,55 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VSwitchListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&VSwitchListOptions{}, "vswitch-list", "List vswitches", func(cli *apsara.SRegion, args *VSwitchListOptions) error { + vswitches, total, e := cli.GetVSwitches(nil, "", args.Offset, args.Limit) + if e != nil { + return e + } + printList(vswitches, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type VSwitchShowOptions struct { + ID string `help:"show vswitch details"` + } + shellutils.R(&VSwitchShowOptions{}, "vswitch-show", "Show vswitch details", func(cli *apsara.SRegion, args *VSwitchShowOptions) error { + vswitch, e := cli.GetVSwitchAttributes(args.ID) + if e != nil { + return e + } + printObject(vswitch) + return nil + }) + + shellutils.R(&VSwitchShowOptions{}, "vswitch-delete", "Show vswitch details", func(cli *apsara.SRegion, args *VSwitchShowOptions) error { + e := cli.DeleteVSwitch(args.ID) + if e != nil { + return e + } + return nil + }) +} diff --git a/pkg/multicloud/apsara/shell/zone.go b/pkg/multicloud/apsara/shell/zone.go new file mode 100644 index 0000000000..cf6382f34c --- /dev/null +++ b/pkg/multicloud/apsara/shell/zone.go @@ -0,0 +1,40 @@ +// 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/apsara" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ZoneListOptions struct { + Details bool `help:"show Details"` + // ChargeType string `help:"charge type" choices:"PrePaid|PostPaid" default:"PrePaid"` + // SpotStrategy string `help:"Spot strategy, NoSpot|SpotWithPriceLimit|SpotAsPriceGo" choices:"NoSpot|SpotWithPriceLimit|SpotAsPriceGo" default:"NoSpot"` + } + shellutils.R(&ZoneListOptions{}, "zone-list", "List zones", func(cli *apsara.SRegion, args *ZoneListOptions) error { + zones, e := cli.GetIZones() + if e != nil { + return e + } + cols := []string{"zone_id", "local_name", "available_resource_creation", "available_disk_categories"} + if args.Details { + cols = []string{} + } + printList(zones, 0, 0, 0, cols) + return nil + }) +} diff --git a/pkg/multicloud/apsara/snapshot.go b/pkg/multicloud/apsara/snapshot.go new file mode 100644 index 0000000000..8e5124d4ef --- /dev/null +++ b/pkg/multicloud/apsara/snapshot.go @@ -0,0 +1,224 @@ +// 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 apsara + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SnapshotStatusType string + +const ( + SnapshotStatusAccomplished SnapshotStatusType = "accomplished" + SnapshotStatusProgress SnapshotStatusType = "progressing" + SnapshotStatusFailed SnapshotStatusType = "failed" + + SnapshotTypeSystem string = "System" + SnapshotTypeData string = "Data" +) + +type SSnapshot struct { + region *SRegion + + Progress string + SnapshotId string + SnapshotName string + SourceDiskId string + SourceDiskSize int32 + SourceDiskType string + Status SnapshotStatusType + Usage string + ResourceGroupId string +} + +func (self *SSnapshot) GetId() string { + return self.SnapshotId +} + +func (self *SSnapshot) GetName() string { + return self.SnapshotName +} + +func (self *SSnapshot) GetStatus() string { + if self.Status == SnapshotStatusAccomplished { + return api.SNAPSHOT_READY + } else if self.Status == SnapshotStatusProgress { + return api.SNAPSHOT_CREATING + } else { // if self.Status == SnapshotStatusFailed + return api.SNAPSHOT_FAILED + } +} + +func (self *SSnapshot) GetSizeMb() int32 { + return self.SourceDiskSize * 1024 +} + +func (self *SSnapshot) GetDiskId() string { + return self.SourceDiskId +} + +func (self *SSnapshot) GetDiskType() string { + if self.SourceDiskType == SnapshotTypeSystem { + return api.DISK_TYPE_SYS + } else if self.SourceDiskType == SnapshotTypeData { + return api.DISK_TYPE_DATA + } else { + return "" + } +} + +func (self *SSnapshot) Refresh() error { + if snapshots, total, err := self.region.GetSnapshots("", "", "", []string{self.SnapshotId}, 0, 1); err != nil { + return err + } else if total != 1 { + return cloudprovider.ErrNotFound + } else if err := jsonutils.Update(self, snapshots[0]); err != nil { + return err + } + return nil +} + +func (self *SSnapshot) GetGlobalId() string { + return fmt.Sprintf("%s", self.SnapshotId) +} + +func (self *SSnapshot) IsEmulated() bool { + return false +} + +func (self *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + snapshots, total, err := self.GetSnapshots("", "", "", []string{}, 0, 50) + if err != nil { + return nil, err + } + for len(snapshots) < total { + var parts []SSnapshot + parts, total, err = self.GetSnapshots("", "", "", []string{}, len(snapshots), 50) + if err != nil { + return nil, err + } + snapshots = append(snapshots, parts...) + } + ret := make([]cloudprovider.ICloudSnapshot, len(snapshots)) + for i := 0; i < len(snapshots); i += 1 { + ret[i] = &snapshots[i] + } + return ret, nil +} + +func (self *SSnapshot) Delete() error { + if self.region == nil { + return fmt.Errorf("not init region for snapshot %s", self.SnapshotId) + } + if err := self.region.SnapshotPreDelete(self.SnapshotId); err != nil { + return err + } + return self.region.DeleteSnapshot(self.SnapshotId) +} + +func (self *SSnapshot) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SRegion) GetSnapshots(instanceId string, diskId string, snapshotName string, snapshotIds []string, offset int, limit int) ([]SSnapshot, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(instanceId) > 0 { + params["InstanceId"] = instanceId + } + if len(diskId) > 0 { + params["diskId"] = diskId + } + if len(snapshotName) > 0 { + params["SnapshotName"] = snapshotName + } + if snapshotIds != nil && len(snapshotIds) > 0 { + params["SnapshotIds"] = jsonutils.Marshal(snapshotIds).String() + } + + body, err := self.ecsRequest("DescribeSnapshots", params) + if err != nil { + log.Errorf("GetSnapshots fail %s", err) + return nil, 0, err + } + + snapshots := make([]SSnapshot, 0) + if err := body.Unmarshal(&snapshots, "Snapshots", "Snapshot"); err != nil { + log.Errorf("Unmarshal snapshot details fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + for i := 0; i < len(snapshots); i += 1 { + snapshots[i].region = self + } + return snapshots, int(total), nil +} + +func (self *SRegion) GetISnapshotById(snapshotId string) (cloudprovider.ICloudSnapshot, error) { + snapshots, total, err := self.GetSnapshots("", "", "", []string{snapshotId}, 0, 1) + if err != nil { + return nil, err + } + if total == 0 { + return nil, cloudprovider.ErrNotFound + } else if total > 1 { + return nil, cloudprovider.ErrDuplicateId + } + return &snapshots[0], nil +} + +func (self *SRegion) DeleteSnapshot(snapshotId string) error { + params := make(map[string]string) + params["SnapshotId"] = snapshotId + _, err := self.ecsRequest("DeleteSnapshot", params) + return err +} + +func (self *SSnapshot) GetProjectId() string { + return self.ResourceGroupId +} + +// If snapshot linked images can't be delete +// delete images first -- Apsara +func (self *SRegion) SnapshotPreDelete(snapshotId string) error { + images, _, err := self.GetImagesBySnapshot(snapshotId, 0, 0) + if err != nil { + return fmt.Errorf("PreDelete get images by snapshot %s error: %s", snapshotId, err) + } + for _, image := range images { + image.storageCache = &SStoragecache{region: self} + if err := image.Delete(context.Background()); err != nil { + return fmt.Errorf("PreDelete image %s error: %s", image.GetId(), err) + } + if err := cloudprovider.WaitDeleted(&image, 3*time.Second, 300*time.Second); err != nil { + return fmt.Errorf("PreDelete waite image %s deleted error: %s", image.GetId(), err) + } + } + return nil +} diff --git a/pkg/multicloud/apsara/snapshot_policy.go b/pkg/multicloud/apsara/snapshot_policy.go new file mode 100644 index 0000000000..f819db33e4 --- /dev/null +++ b/pkg/multicloud/apsara/snapshot_policy.go @@ -0,0 +1,304 @@ +// 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 apsara + +import ( + "fmt" + "sort" + "strconv" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SSnapshotPolicyType string + +const ( + Creating SSnapshotPolicyType = "Creating" + Available SSnapshotPolicyType = "Available" + Normal SSnapshotPolicyType = "Normal" +) + +type SSnapshotPolicy struct { + region *SRegion + + AutoSnapshotPolicyName string + AutoSnapshotPolicyId string + RepeatWeekdays string + TimePoints string + RetentionDays int + Status SSnapshotPolicyType +} + +func (self *SSnapshotPolicy) GetId() string { + return self.AutoSnapshotPolicyId +} + +func (self *SSnapshotPolicy) GetName() string { + return self.AutoSnapshotPolicyName +} + +func (self *SSnapshotPolicy) GetStatus() string { + // XXX: apsara文档与实际返回值不符 + if self.Status == Normal || self.Status == Available { + return api.SNAPSHOT_POLICY_READY + } else if self.Status == Creating { + return api.SNAPSHOT_POLICY_CREATING + } else { + return api.SNAPSHOT_POLICY_UNKNOWN + } +} + +func (self *SSnapshotPolicy) Refresh() error { + if snapshotPolicies, total, err := self.region.GetSnapshotPolicies(self.AutoSnapshotPolicyId, 0, 1); err != nil { + return err + } else if total != 1 { + return cloudprovider.ErrNotFound + } else if err := jsonutils.Update(self, snapshotPolicies[0]); err != nil { + return err + } + return nil +} + +func (self *SSnapshotPolicy) IsEmulated() bool { + return false +} + +func (self *SSnapshotPolicy) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SSnapshotPolicy) GetGlobalId() string { + return self.AutoSnapshotPolicyId +} + +func (self *SSnapshotPolicy) GetProjectId() string { + return "" +} + +func (self *SSnapshotPolicy) GetRetentionDays() int { + return self.RetentionDays +} + +func sliceAtoi(sa []string) ([]int, error) { + si := make([]int, 0, len(sa)) + for _, a := range sa { + i, err := strconv.Atoi(a) + if err != nil { + return si, err + } + si = append(si, i) + } + return si, nil +} + +func stringToIntDays(days []string) ([]int, error) { + idays, err := sliceAtoi(days) + if err != nil { + return nil, err + } + sort.Ints(idays) + return idays, nil +} + +func parsePolicy(policy string) ([]int, error) { + tp, err := jsonutils.ParseString(policy) + if err != nil { + return nil, fmt.Errorf("Parse policy %s error %s", policy, err) + } + atp, ok := tp.(*jsonutils.JSONArray) + if !ok { + return nil, fmt.Errorf("Policy %s Wrong format", tp) + } + return stringToIntDays(atp.GetStringArray()) +} + +func (self *SSnapshotPolicy) GetRepeatWeekdays() ([]int, error) { + return parsePolicy(self.RepeatWeekdays) +} + +func (self *SSnapshotPolicy) GetTimePoints() ([]int, error) { + return parsePolicy(self.TimePoints) +} + +func (self *SSnapshotPolicy) IsActivated() bool { + return true +} + +func (self *SRegion) GetISnapshotPolicies() ([]cloudprovider.ICloudSnapshotPolicy, error) { + snapshotPolicies, total, err := self.GetSnapshotPolicies("", 0, 50) + if err != nil { + return nil, err + } + for len(snapshotPolicies) < total { + var parts []SSnapshotPolicy + parts, total, err = self.GetSnapshotPolicies("", len(snapshotPolicies), 50) + if err != nil { + return nil, err + } + snapshotPolicies = append(snapshotPolicies, parts...) + } + ret := make([]cloudprovider.ICloudSnapshotPolicy, len(snapshotPolicies)) + for i := 0; i < len(snapshotPolicies); i += 1 { + ret[i] = &snapshotPolicies[i] + } + return ret, nil +} + +func (self *SRegion) GetSnapshotPolicies(policyId string, offset int, limit int) ([]SSnapshotPolicy, int, error) { + params := make(map[string]string) + + params["RegionId"] = self.RegionId + if limit != 0 { + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + } + + if len(policyId) > 0 { + params["AutoSnapshotPolicyId"] = policyId + } + + body, err := self.ecsRequest("DescribeAutoSnapshotPolicyEx", params) + if err != nil { + return nil, 0, fmt.Errorf("GetSnapshotPolicys fail %s", err) + } + + snapshotPolicies := make([]SSnapshotPolicy, 0) + if err := body.Unmarshal(&snapshotPolicies, "AutoSnapshotPolicies", "AutoSnapshotPolicy"); err != nil { + return nil, 0, fmt.Errorf("Unmarshal snapshot policies details fail %s", err) + } + total, _ := body.Int("TotalCount") + for i := 0; i < len(snapshotPolicies); i += 1 { + snapshotPolicies[i].region = self + } + return snapshotPolicies, int(total), nil +} + +func (self *SSnapshotPolicy) Delete() error { + if self.region == nil { + return fmt.Errorf("Not init region for snapshotPolicy %s", self.AutoSnapshotPolicyId) + } + return self.region.DeleteSnapshotPolicy(self.AutoSnapshotPolicyId) +} + +func (self *SRegion) DeleteSnapshotPolicy(snapshotPolicyId string) error { + params := make(map[string]string) + params["autoSnapshotPolicyId"] = snapshotPolicyId + params["regionId"] = self.RegionId + _, err := self.ecsRequest("DeleteAutoSnapshotPolicy", params) + return err +} + +func (self *SRegion) GetISnapshotPolicyById(snapshotPolicyId string) (cloudprovider.ICloudSnapshotPolicy, error) { + policies, _, err := self.GetSnapshotPolicies(snapshotPolicyId, 0, 1) + if err != nil { + return nil, err + } + if len(policies) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &policies[0], nil +} + +func (self *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput) (string, error) { + if input.RepeatWeekdays == nil { + return "", fmt.Errorf("Can't create snapshot policy with nil repeatWeekdays") + } + if input.TimePoints == nil { + return "", fmt.Errorf("Can't create snapshot policy with nil timePoints") + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["repeatWeekdays"] = jsonutils.Marshal(input.GetStringArrayRepeatWeekdays()).String() + params["timePoints"] = jsonutils.Marshal(input.GetStringArrayTimePoints()).String() + params["retentionDays"] = strconv.Itoa(input.RetentionDays) + params["autoSnapshotPolicyName"] = input.PolicyName + if body, err := self.ecsRequest("CreateAutoSnapshotPolicy", params); err != nil { + return "", fmt.Errorf("CreateAutoSnapshotPolicy fail %s", err) + } else { + return body.GetString("AutoSnapshotPolicyId") + } +} + +func (self *SRegion) UpdateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput, snapshotPolicyId string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + if input.RetentionDays != 0 { + params["retentionDays"] = strconv.Itoa(input.RetentionDays) + } + if input.RepeatWeekdays != nil && len(input.RepeatWeekdays) != 0 { + params["repeatWeekdays"] = jsonutils.Marshal(input.GetStringArrayRepeatWeekdays()).String() + } + if input.TimePoints != nil && len(input.TimePoints) != 0 { + params["timePoints"] = jsonutils.Marshal(input.GetStringArrayTimePoints()).String() + } + _, err := self.ecsRequest("ModifyAutoSnapshotPolicyEx", params) + if err != nil { + return fmt.Errorf("ModifyAutoSnapshotPolicyEx Fail %s", err) + } + return nil +} + +//func (self *SRegion) UpdateSnapshotPolicy( +// snapshotPolicyId string, retentionDays *int, +// repeatWeekdays, timePoints *jsonutils.JSONArray, policyName string, +//) error { +// params := make(map[string]string) +// params["RegionId"] = self.RegionId +// if len(policyName) > 0 { +// params["autoSnapshotPolicyName"] = policyName +// } +// if retentionDays != nil { +// params["retentionDays"] = strconv.Itoa(*retentionDays) +// } +// if repeatWeekdays != nil { +// params["repeatWeekdays"] = repeatWeekdays.String() +// } +// if timePoints != nil { +// params["timePoints"] = timePoints.String() +// } +// _, err := self.ecsRequest("ModifyAutoSnapshotPolicyEx", params) +// if err != nil { +// return fmt.Errorf("ModifyAutoSnapshotPolicyEx Fail %s", err) +// } +// return nil +//} + +func (self *SRegion) ApplySnapshotPolicyToDisks(snapshotPolicyId string, diskId string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["autoSnapshotPolicyId"] = snapshotPolicyId + diskIds := []string{diskId} + params["diskIds"] = jsonutils.Marshal(diskIds).String() + _, err := self.ecsRequest("ApplyAutoSnapshotPolicy", params) + if err != nil { + return fmt.Errorf("ApplyAutoSnapshotPolicy Fail %s", err) + } + return nil +} + +func (self *SRegion) CancelSnapshotPolicyToDisks(snapshotPolicyId string, diskId string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + diskIds := []string{diskId} + params["diskIds"] = jsonutils.Marshal(diskIds).String() + _, err := self.ecsRequest("CancelAutoSnapshotPolicy", params) + if err != nil { + return fmt.Errorf("CancelAutoSnapshotPolicy Fail %s", err) + } + return nil +} diff --git a/pkg/multicloud/apsara/storage.go b/pkg/multicloud/apsara/storage.go new file mode 100644 index 0000000000..3e36d2f181 --- /dev/null +++ b/pkg/multicloud/apsara/storage.go @@ -0,0 +1,187 @@ +// 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. + +// +// 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 apsara + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SStorage struct { + zone *SZone + storageType string +} + +func (self *SStorage) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SStorage) GetId() string { + return fmt.Sprintf("%s-%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId(), self.storageType) +} + +func (self *SStorage) GetName() string { + return fmt.Sprintf("%s-%s-%s", self.zone.region.client.cpcfg.Name, self.zone.GetId(), self.storageType) +} + +func (self *SStorage) GetGlobalId() string { + return fmt.Sprintf("%s-%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetGlobalId(), self.storageType) +} + +func (self *SStorage) IsEmulated() bool { + return true +} + +func (self *SStorage) GetIZone() cloudprovider.ICloudZone { + return self.zone +} + +func (self *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + disks := make([]SDisk, 0) + offset := 0 + storageType := self.storageType + if self.storageType == api.STORAGE_CLOUD_ESSD_PL2 || self.storageType == api.STORAGE_CLOUD_ESSD_PL3 { + storageType = api.STORAGE_CLOUD_ESSD + } + for { + parts, total, err := self.zone.region.GetDisks("", self.zone.GetId(), storageType, nil, offset, 50) + if err != nil { + log.Errorf("GetDisks fail %s", err) + return nil, err + } + performanceLevel := "" + switch self.storageType { + case api.STORAGE_CLOUD_ESSD_PL2: + performanceLevel = "PL2" + case api.STORAGE_CLOUD_ESSD_PL3: + performanceLevel = "PL3" + } + for _, disk := range parts { + if disk.PerformanceLevel == performanceLevel { + disks = append(disks, disk) + } + } + + offset += len(parts) + + if offset >= total { + break + } + } + idisks := make([]cloudprovider.ICloudDisk, len(disks)) + for i := 0; i < len(disks); i += 1 { + disks[i].storage = self + idisks[i] = &disks[i] + } + return idisks, nil +} + +func (self *SStorage) GetStorageType() string { + //return models.STORAGE_PUBLIC_CLOUD + return self.storageType +} + +func (self *SStorage) GetMediumType() string { + if strings.Contains(self.storageType, "_ssd") { + return api.DISK_TYPE_SSD + } else { + return api.DISK_TYPE_ROTATE + } +} + +func (self *SStorage) GetCapacityMB() int64 { + return 0 // unlimited +} + +func (self *SStorage) GetCapacityUsedMB() int64 { + return 0 +} + +func (self *SStorage) GetStorageConf() jsonutils.JSONObject { + conf := jsonutils.NewDict() + return conf +} + +func (self *SStorage) GetStatus() string { + return api.STORAGE_ONLINE +} + +func (self *SStorage) Refresh() error { + // do nothing + return nil +} + +func (self *SStorage) GetEnabled() bool { + return true +} + +func (self *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return self.zone.region.getStoragecache() +} + +func (self *SStorage) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudprovider.ICloudDisk, error) { + diskId, err := self.zone.region.CreateDisk(self.zone.ZoneId, self.storageType, conf.Name, conf.SizeGb, conf.Desc, conf.ProjectId) + if err != nil { + log.Errorf("createDisk fail %s", err) + return nil, err + } + disk, err := self.zone.region.getDisk(diskId) + if err != nil { + log.Errorf("getDisk fail %s", err) + return nil, err + } + disk.storage = self + return disk, nil +} + +func (self *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) { + if disk, err := self.zone.region.getDisk(idStr); err != nil { + return nil, err + } else { + disk.storage = self + return disk, nil + } +} + +func (self *SStorage) GetMountPoint() string { + return "" +} + +func (self *SStorage) IsSysDiskStore() bool { + if utils.IsInStringArray(self.storageType, self.zone.getSysDiskCategories()) { + return true + } + return false +} diff --git a/pkg/multicloud/apsara/storagecache.go b/pkg/multicloud/apsara/storagecache.go new file mode 100644 index 0000000000..dcff4a5ebd --- /dev/null +++ b/pkg/multicloud/apsara/storagecache.go @@ -0,0 +1,385 @@ +// 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 apsara + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/qemuimg" +) + +type SStoragecache struct { + region *SRegion + + iimages []cloudprovider.ICloudImage +} + +func (self *SStoragecache) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SStoragecache) GetId() string { + return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId()) +} + +func (self *SStoragecache) GetName() string { + return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId()) +} + +func (self *SStoragecache) GetStatus() string { + return "available" +} + +func (self *SStoragecache) Refresh() error { + return nil +} + +func (self *SStoragecache) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId()) +} + +func (self *SStoragecache) IsEmulated() bool { + return false +} + +func (self *SStoragecache) fetchImages() error { + images := make([]SImage, 0) + for { + parts, total, err := self.region.GetImages(ImageStatusType(""), "", nil, "", len(images), 50) + if err != nil { + return err + } + images = append(images, parts...) + if len(images) >= total { + break + } + } + self.iimages = make([]cloudprovider.ICloudImage, len(images)) + for i := 0; i < len(images); i += 1 { + images[i].storageCache = self + self.iimages[i] = &images[i] + } + return nil +} + +func (self *SStoragecache) GetIImages() ([]cloudprovider.ICloudImage, error) { + if self.iimages == nil { + err := self.fetchImages() + if err != nil { + return nil, err + } + } + return self.iimages, nil +} + +func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { + img, err := self.region.GetImage(extId) + if err != nil { + return nil, err + } + img.storageCache = self + return img, nil +} + +func (self *SStoragecache) GetPath() string { + return "" +} + +func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { + + if len(image.ExternalId) > 0 { + status, err := self.region.GetImageStatus(image.ExternalId) + if err != nil { + log.Errorf("GetImageStatus error %s", err) + } + log.Debugf("UploadImage: Image external ID %s exists, status %s", image.ExternalId, status) + if status == ImageStatusAvailable && !isForce { + return image.ExternalId, nil + } + // 不能直接删除 ImageStatusCreating 状态的image ,需要先取消importImage Task + if status == ImageStatusCreating { + err := self.region.CancelImageImportTasks() + if err != nil { + log.Errorln(err) + } + } + if len(status) > 0 { + err = self.region.DeleteImage(image.ExternalId) + if err != nil { + log.Errorf("failed to delete image %s(%s) error: %v", image.ExternalId, status, err) + } + } + } else { + log.Debugf("UploadImage: no external ID") + } + + return self.uploadImage(ctx, userCred, image, isForce) +} + +func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { + // first upload image to oss + s := auth.GetAdminSession(ctx, options.Options.Region, "") + + meta, reader, sizeByte, err := modules.Images.Download(s, image.ImageId, string(qemuimg.QCOW2), false) + if err != nil { + return "", err + } + log.Infof("meta data %s", meta) + + bucketName := strings.ToLower(fmt.Sprintf("imgcache-%s-%s", self.region.GetId(), image.ImageId)) + exist, err := self.region.IBucketExist(bucketName) + if err != nil { + log.Errorf("IsBucketExist err %s", err) + return "", err + } + if !exist { + log.Debugf("Bucket %s not exists, to create ...", bucketName) + err = self.region.CreateIBucket(bucketName, "", "") + if err != nil { + log.Errorf("Create bucket error %s", err) + return "", err + } + } else { + log.Debugf("Bucket %s exists", bucketName) + } + + defer self.region.DeleteIBucket(bucketName) // remove bucket + + bucket, err := self.region.GetIBucketByName(bucketName) + if err != nil { + log.Errorf("Bucket error %s %s", bucketName, err) + return "", err + } + log.Debugf("To upload image to bucket %s ...", bucketName) + err = cloudprovider.UploadObject(context.Background(), bucket, image.ImageId, 0, reader, sizeByte, "", "", nil, false) + // err = bucket.PutObject(image.ImageId, reader) + if err != nil { + log.Errorf("PutObject error %s %s", image.ImageId, err) + return "", err + } + + defer bucket.DeleteObject(context.Background(), image.ImageId) // remove object + + imageBaseName := image.ImageId + if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' { + imageBaseName = fmt.Sprintf("img%s", image.ImageId) + } + imageName := imageBaseName + nameIdx := 1 + + // check image name, avoid name conflict + for { + _, err = self.region.GetImageByName(imageName) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + break + } else { + return "", err + } + } + imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx) + nameIdx += 1 + } + + log.Debugf("Import image %s", imageName) + + // ensure privileges + err = self.region.GetClient().EnableImageImport() + if err != nil { + log.Errorf("fail to enable import privileges: %s", err) + return "", err + } + + task, err := self.region.ImportImage(imageName, image.OsArch, image.OsType, image.OsDistribution, bucketName, image.ImageId) + + if err != nil { + log.Errorf("ImportImage error %s %s %s", image.ImageId, bucketName, err) + return "", err + } + + // timeout: 1hour = 3600 seconds + err = self.region.waitTaskStatus(ImportImageTask, task.TaskId, TaskStatusFinished, 15*time.Second, 3600*time.Second) + if err != nil { + log.Errorf("waitTaskStatus %s", err) + return task.ImageId, err + } + + return task.ImageId, nil +} + +func (self *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { + if imageId, err := self.region.createIImage(snapshoutId, imageName, imageDesc); err != nil { + return nil, err + } else if image, err := self.region.GetImage(imageId); err != nil { + return nil, err + } else { + image.storageCache = self + iimage := make([]cloudprovider.ICloudImage, 1) + iimage[0] = image + if err := cloudprovider.WaitStatus(iimage[0], cloudprovider.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil { + return nil, err + } + return iimage[0], nil + } +} + +func (self *SRegion) CheckBucket(bucketName string) (*oss.Bucket, error) { + return self.checkBucket(bucketName) +} + +func (self *SRegion) checkBucket(bucketName string) (*oss.Bucket, error) { + oss, err := self.GetOssClient() + if err != nil { + log.Errorf("GetOssClient err %s", err) + return nil, err + } + if exist, err := oss.IsBucketExist(bucketName); err != nil { + log.Errorf("IsBucketExist err %s", err) + return nil, err + } else if !exist { + log.Debugf("Bucket %s not exists, to create ...", bucketName) + if err := oss.CreateBucket(bucketName); err != nil { + log.Errorf("Create bucket error %s", err) + return nil, err + } + } + log.Debugf("Bucket %s exists", bucketName) + if bucket, err := oss.Bucket(bucketName); err != nil { + log.Errorf("Bucket error %s %s", bucketName, err) + return nil, err + } else { + return bucket, nil + } +} + +func (self *SRegion) CreateImage(snapshoutId, imageName, imageDesc string) (string, error) { + return self.createIImage(snapshoutId, imageName, imageDesc) +} + +func (self *SRegion) createIImage(snapshoutId, imageName, imageDesc string) (string, error) { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["OssBucket"] = strings.ToLower(fmt.Sprintf("imgcache-%s", self.GetId())) + params["SnapshotId"] = snapshoutId + params["ImageName"] = imageName + params["Description"] = imageDesc + + if _, err := self.checkBucket(params["OssBucket"]); err != nil { + return "", err + } + + if body, err := self.ecsRequest("CreateImage", params); err != nil { + log.Errorf("CreateImage fail %s", err) + return "", err + } else { + log.Infof("%s", body) + return body.GetString("ImageId") + } +} + +func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { + return self.downloadImage(userCred, imageId, extId, path) +} + +// 定义进度条监听器。 +type OssProgressListener struct { +} + +// 定义进度变更事件处理函数。 +func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) { + switch event.EventType { + case oss.TransferStartedEvent: + log.Debugf("Transfer Started, ConsumedBytes: %d, TotalBytes %d.\n", + event.ConsumedBytes, event.TotalBytes) + case oss.TransferDataEvent: + log.Debugf("\rTransfer Data, ConsumedBytes: %d, TotalBytes %d, %d%%.", + event.ConsumedBytes, event.TotalBytes, event.ConsumedBytes*100/event.TotalBytes) + case oss.TransferCompletedEvent: + log.Debugf("\nTransfer Completed, ConsumedBytes: %d, TotalBytes %d.\n", + event.ConsumedBytes, event.TotalBytes) + case oss.TransferFailedEvent: + log.Debugf("\nTransfer Failed, ConsumedBytes: %d, TotalBytes %d.\n", + event.ConsumedBytes, event.TotalBytes) + default: + } +} + +func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { + err := self.region.GetClient().EnableImageExport() + if err != nil { + log.Errorf("fail to enable export privileges: %s", err) + return nil, err + } + + tmpImageFile, err := ioutil.TempFile(path, extId) + if err != nil { + return nil, err + } + defer tmpImageFile.Close() + defer os.Remove(tmpImageFile.Name()) + bucketName := strings.ToLower(fmt.Sprintf("imgcache-%s", self.region.GetId())) + if bucket, err := self.region.checkBucket(bucketName); err != nil { + return nil, err + } else if _, err := self.region.GetImage(extId); err != nil { + return nil, err + } else if task, err := self.region.ExportImage(extId, bucket); err != nil { + return nil, err + } else if err := self.region.waitTaskStatus(ExportImageTask, task.TaskId, TaskStatusFinished, 15*time.Second, 3600*time.Second); err != nil { + return nil, err + } else if imageList, err := bucket.ListObjects(oss.Prefix(fmt.Sprintf("%sexport", strings.Replace(extId, "-", "", -1)))); err != nil { + return nil, err + } else if len(imageList.Objects) != 1 { + return nil, fmt.Errorf("exported image not find") + } else if err := bucket.DownloadFile(imageList.Objects[0].Key, tmpImageFile.Name(), 12*1024*1024, oss.Routines(3), oss.Progress(&OssProgressListener{})); err != nil { + return nil, err + } else { + s := auth.GetAdminSession(context.Background(), options.Options.Region, "") + params := jsonutils.Marshal(map[string]string{"image_id": imageId, "disk-format": "raw"}) + if result, err := modules.Images.Upload(s, params, tmpImageFile, imageList.Objects[0].Size); err != nil { + return nil, err + } else { + return result, nil + } + } +} + +func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + storageCache := region.getStoragecache() + return []cloudprovider.ICloudStoragecache{storageCache}, nil +} + +func (region *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { + storageCache := region.getStoragecache() + if id == storageCache.GetGlobalId() { + return storageCache, nil + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/multicloud/apsara/sts.go b/pkg/multicloud/apsara/sts.go new file mode 100644 index 0000000000..b43bce064c --- /dev/null +++ b/pkg/multicloud/apsara/sts.go @@ -0,0 +1,46 @@ +// 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 apsara + +import ( + "yunion.io/x/jsonutils" +) + +func (self *SApsaraClient) stsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return productRequest(cli, APSARA_PRODUCT_STS, self.endpoints.StsEndpoint, APSARA_STS_API_VERSION, apiName, params, self.debug) +} + +type SCallerIdentity struct { + Arn string + AccountId string + UserId string + RoleId string + PrincipalId string + IdentityType string +} + +func (self *SApsaraClient) GetCallerIdentity() (*SCallerIdentity, error) { + params := map[string]string{} + resp, err := self.stsRequest("GetCallerIdentity", params) + if err != nil { + return nil, err + } + id := &SCallerIdentity{} + return id, resp.Unmarshal(id) +} diff --git a/pkg/multicloud/apsara/task.go b/pkg/multicloud/apsara/task.go new file mode 100644 index 0000000000..142fe5cab9 --- /dev/null +++ b/pkg/multicloud/apsara/task.go @@ -0,0 +1,179 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/log" +) + +type TaskStatusType string + +type TaskActionType string + +const ( + ImportImageTask = TaskActionType("ImportImage") + ExportImageTask = TaskActionType("ExportImage") + + // Finished:已完成 + // Processing:运行中 + // Waiting:多任务排队中 + // Deleted:已取消 + // Paused:暂停 + // Failed:失败 + TaskStatusFinished = TaskStatusType("Finished") + TaskStatusProcessing = TaskStatusType("Processing") + TaskStatusWaiting = TaskStatusType("Waiting") + TaskStatusDeleted = TaskStatusType("Deleted") + TaskStatusPaused = TaskStatusType("Paused") + TaskStatusFailed = TaskStatusType("Failed") +) + +type STask struct { + TaskId string + TaskStatus TaskStatusType + TaskAction string + SupportCancel bool + FinishedTime time.Time + CreationTime time.Time +} + +func (self *SRegion) waitTaskStatus(action TaskActionType, taskId string, targetStatus TaskStatusType, interval time.Duration, timeout time.Duration) error { + start := time.Now() + for time.Now().Sub(start) < timeout { + status, err := self.GetTaskStatus(action, taskId) + if err != nil { + return err + } + if status == targetStatus { + return nil + } + time.Sleep(interval) + } + return fmt.Errorf("timeout for waitting task %s(%s) after %f minutes", taskId, action, timeout.Minutes()) +} + +func (self *SRegion) GetTaskStatus(action TaskActionType, taskId string) (TaskStatusType, error) { + task, err := self.GetTask(taskId) + if err != nil { + return "", err + } + return task.TaskStatus, nil +} + +func (self *SRegion) GetTasks(action TaskActionType, taskId []string, taskStatus TaskStatusType, offset int, limit int) ([]STask, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + params["TaskAction"] = string(action) + if taskId != nil && len(taskId) > 0 { + params["TaskIds"] = strings.Join(taskId, ",") + } + if len(taskStatus) > 0 { + params["TaskStatus"] = string(taskStatus) + } + + body, err := self.ecsRequest("DescribeTasks", params) + if err != nil { + log.Errorf("GetTasks fail %s", err) + return nil, 0, err + } + + log.Infof("%s", body) + tasks := make([]STask, 0) + err = body.Unmarshal(&tasks, "TaskSet", "Task") + if err != nil { + log.Errorf("Unmarshal task fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return tasks, int(total), nil +} + +type STaskError struct { + ErrorCode string + ErrorMsg string + OperationStatus string +} + +type STaskDetail struct { + CreationTime time.Time + FailedCount int + FinishedTime time.Time + RegionId string + OperationProgressSet map[string][]STaskError + RequestId string + SuccessCount int + SupportCancel bool + TaskAction string + TaskId string + TaskProcess string + TaskStatus TaskStatusType + TotalCount int +} + +func (self *SRegion) GetTask(taskId string) (*STaskDetail, error) { + params := map[string]string{ + "RegionId": self.RegionId, + "TaskId": taskId, + } + body, err := self.ecsRequest("DescribeTaskAttribute", params) + if err != nil { + return nil, err + } + log.Infof("%s", body) + detail := &STaskDetail{} + return detail, body.Unmarshal(detail) +} + +func (self *SRegion) CancelTask(taskId string) error { + params := map[string]string{ + "RegionId": self.RegionId, + "TaskId": taskId, + } + _, err := self.ecsRequest("CancelTask", params) + return err +} + +func (region *SRegion) CancelImageImportTasks() error { + tasks, _, _ := region.GetTasks(ImportImageTask, []string{}, TaskStatusProcessing, 0, 50) + for i := 0; i < len(tasks); i++ { + task, err := region.GetTask(tasks[i].TaskId) + if err != nil { + log.Errorf("failed get task %s %s error: %v", tasks[i].CreationTime, tasks[i].TaskId, err) + } + if task != nil { + log.Debugf("task info: %s(%s) cancelable %t process %s", task.TaskId, task.CreationTime, task.SupportCancel, task.TaskProcess) + } else { + log.Debugf("task info: %s(%s) cancelable %t", tasks[i].TaskId, tasks[i].CreationTime, tasks[i].SupportCancel) + } + if tasks[i].SupportCancel { + err := region.CancelTask(tasks[i].TaskId) + if err != nil { + return fmt.Errorf("failed to cancel task %s error: %v", tasks[i].TaskId, err) + } + } + } + return nil +} diff --git a/pkg/multicloud/apsara/utils.go b/pkg/multicloud/apsara/utils.go new file mode 100644 index 0000000000..80bed815e3 --- /dev/null +++ b/pkg/multicloud/apsara/utils.go @@ -0,0 +1,106 @@ +// 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 apsara + +import ( + "fmt" + "reflect" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" +) + +type jsonRequestFunc func(action string, params map[string]string) (jsonutils.JSONObject, error) + +func unmarshalResult(resp jsonutils.JSONObject, respErr error, resultKey []string, result interface{}) error { + if respErr != nil { + return respErr + } + + if result == nil { + return nil + } + + if resultKey != nil && len(resultKey) > 0 { + respErr = resp.Unmarshal(result, resultKey...) + } else { + respErr = resp.Unmarshal(result) + } + + if respErr != nil { + log.Errorf("unmarshal json error %s", respErr) + } + + return nil +} + +func doListPart(client jsonRequestFunc, action string, limit int, offset int, params map[string]string, resultKey []string, result interface{}) (int, int, error) { + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + ret, err := client(action, params) + if err != nil { + return 0, 0, err + } + + total, _ := ret.Int("TotalCount") + + var lst []jsonutils.JSONObject + lst, err = ret.GetArray(resultKey...) + if err != nil { + return 0, 0, nil + } + + resultValue := reflect.Indirect(reflect.ValueOf(result)) + elemType := resultValue.Type().Elem() + for i := range lst { + elemPtr := reflect.New(elemType) + err = lst[i].Unmarshal(elemPtr.Interface()) + if err != nil { + return 0, 0, err + } + resultValue.Set(reflect.Append(resultValue, elemPtr.Elem())) + } + return int(total), len(lst), nil +} + +// 执行操作 +func DoAction(client jsonRequestFunc, action string, params map[string]string, resultKey []string, result interface{}) error { + resp, err := client(action, params) + return unmarshalResult(resp, err, resultKey, result) +} + +// 遍历所有结果 +func DoListAll(client jsonRequestFunc, action string, params map[string]string, resultKey []string, result interface{}) error { + pageLimit := 50 + offset := 0 + + resultValue := reflect.Indirect(reflect.ValueOf(result)) + for { + total, part, err := doListPart(client, action, pageLimit, offset, params, resultKey, result) + if err != nil { + return err + } + + // total 大于零的情况下通过total字段判断列表是否遍历完成。total不存在或者为0的情况下,通过返回列表的长度判断是否遍历完成 + if (total > 0 && resultValue.Len() >= total) || (total == 0 && pageLimit > part) { + break + } + + offset = resultValue.Len() + } + + return nil +} diff --git a/pkg/multicloud/apsara/vpc.go b/pkg/multicloud/apsara/vpc.go new file mode 100644 index 0000000000..949ad76444 --- /dev/null +++ b/pkg/multicloud/apsara/vpc.go @@ -0,0 +1,292 @@ +// 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 apsara + +import ( + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +const ( + VpcAvailable = "Available" + VpcPending = "Pending" +) + +// "CidrBlock":"172.31.0.0/16","CreationTime":"2017-03-19T13:37:40Z","Description":"System created default VPC.","IsDefault":true,"RegionId":"cn-hongkong","Status":"Available","UserCidrs":{"UserCidr":[]},"VRouterId":"vrt-j6c00qrol733dg36iq4qj","VSwitchIds":{"VSwitchId":["vsw-j6c3gig5ub4fmi2veyrus"]},"VpcId":"vpc-j6c86z3sh8ufhgsxwme0q","VpcName":"" + +type SUserCIDRs struct { + UserCidr []string +} + +type SVSwitchIds struct { + VSwitchId []string +} + +type SVpc struct { + multicloud.SVpc + + region *SRegion + + iwires []cloudprovider.ICloudWire + + secgroups []cloudprovider.ICloudSecurityGroup + routeTables []cloudprovider.ICloudRouteTable + + CidrBlock string + CreationTime time.Time + Description string + IsDefault bool + RegionId string + Status string + UserCidrs SUserCIDRs + VRouterId string + VSwitchIds SVSwitchIds + VpcId string + VpcName string +} + +func (self *SVpc) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SVpc) GetId() string { + return self.VpcId +} + +func (self *SVpc) GetName() string { + if len(self.VpcName) > 0 { + return self.VpcName + } + return self.VpcId +} + +func (self *SVpc) GetGlobalId() string { + return self.VpcId +} + +func (self *SVpc) IsEmulated() bool { + return false +} + +func (self *SVpc) GetIsDefault() bool { + return self.IsDefault +} + +func (self *SVpc) GetCidrBlock() string { + return self.CidrBlock +} + +func (self *SVpc) GetStatus() string { + return strings.ToLower(self.Status) +} + +func (self *SVpc) Refresh() error { + new, err := self.region.getVpc(self.VpcId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SVpc) GetRegion() cloudprovider.ICloudRegion { + return self.region +} + +func (self *SVpc) addWire(wire *SWire) { + if self.iwires == nil { + self.iwires = make([]cloudprovider.ICloudWire, 0) + } + self.iwires = append(self.iwires, wire) +} + +func (self *SVpc) getWireByZoneId(zoneId string) *SWire { + for i := 0; i <= len(self.iwires); i += 1 { + wire := self.iwires[i].(*SWire) + if wire.zone.ZoneId == zoneId { + return wire + } + } + return nil +} + +func (self *SVpc) fetchVSwitches() error { + switches, total, err := self.region.GetVSwitches(nil, self.VpcId, 0, 50) + if err != nil { + return err + } + if total > len(switches) { + switches, _, err = self.region.GetVSwitches(nil, self.VpcId, 0, total) + if err != nil { + return err + } + } + for i := 0; i < len(switches); i += 1 { + wire := self.getWireByZoneId(switches[i].ZoneId) + switches[i].wire = wire + wire.addNetwork(&switches[i]) + } + return nil +} + +func (self *SVpc) GetIWires() ([]cloudprovider.ICloudWire, error) { + if self.iwires == nil { + err := self.fetchVSwitches() + if err != nil { + return nil, err + } + } + return self.iwires, nil +} + +func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { + if self.iwires == nil { + err := self.fetchVSwitches() + if err != nil { + return nil, err + } + } + for i := 0; i < len(self.iwires); i += 1 { + if self.iwires[i].GetGlobalId() == wireId { + return self.iwires[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SVpc) fetchSecurityGroups() error { + secgroups := make([]SSecurityGroup, 0) + for { + parts, total, err := self.region.GetSecurityGroups(self.VpcId, "", []string{}, len(secgroups), 50) + if err != nil { + return err + } + secgroups = append(secgroups, parts...) + if len(secgroups) >= total { + break + } + } + self.secgroups = make([]cloudprovider.ICloudSecurityGroup, len(secgroups)) + for i := 0; i < len(secgroups); i++ { + secgroups[i].vpc = self + self.secgroups[i] = &secgroups[i] + } + return nil +} + +func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { + if self.secgroups == nil { + err := self.fetchSecurityGroups() + if err != nil { + return nil, err + } + } + return self.secgroups, nil +} + +func (self *SVpc) fetchRouteTables() error { + routeTables := make([]*SRouteTable, 0) + for { + parts, total, err := self.RemoteGetRouteTableList(len(routeTables), 50) + if err != nil { + return err + } + routeTables = append(routeTables, parts...) + if len(routeTables) >= total { + break + } + } + self.routeTables = make([]cloudprovider.ICloudRouteTable, len(routeTables)) + for i := 0; i < len(routeTables); i++ { + routeTables[i].vpc = self + self.routeTables[i] = routeTables[i] + } + return nil +} + +func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + if self.routeTables == nil { + err := self.fetchRouteTables() + if err != nil { + return nil, err + } + } + return self.routeTables, nil +} + +func (self *SVpc) GetIRouteTableById(routeTableId string) (cloudprovider.ICloudRouteTable, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (self *SVpc) Delete() error { + err := self.fetchSecurityGroups() + if err != nil { + log.Errorf("fetchSecurityGroup for VPC delete fail %s", err) + return err + } + for i := 0; i < len(self.secgroups); i += 1 { + secgroup := self.secgroups[i].(*SSecurityGroup) + err := self.region.DeleteSecurityGroup(secgroup.SecurityGroupId) + if err != nil { + log.Errorf("deleteSecurityGroup for VPC delete fail %s", err) + return err + } + } + return self.region.DeleteVpc(self.VpcId) +} + +func (self *SVpc) getNatGateways() ([]SNatGetway, error) { + natgatways := make([]SNatGetway, 0) + gwTotal := -1 + for gwTotal < 0 || len(natgatways) < gwTotal { + parts, total, err := self.region.GetNatGateways(self.VpcId, "", len(natgatways), 50) + if err != nil { + return nil, err + } + if len(parts) > 0 { + natgatways = append(natgatways, parts...) + } + gwTotal = total + } + for i := 0; i < len(natgatways); i += 1 { + natgatways[i].vpc = self + } + return natgatways, nil +} + +func (self *SVpc) GetINatGateways() ([]cloudprovider.ICloudNatGateway, error) { + nats := []SNatGetway{} + for { + parts, total, err := self.region.GetNatGateways(self.VpcId, "", len(nats), 50) + if err != nil { + return nil, err + } + nats = append(nats, parts...) + if len(nats) >= total { + break + } + } + inats := []cloudprovider.ICloudNatGateway{} + for i := 0; i < len(nats); i++ { + nats[i].vpc = self + inats = append(inats, &nats[i]) + } + return inats, nil +} diff --git a/pkg/multicloud/apsara/vrouter.go b/pkg/multicloud/apsara/vrouter.go new file mode 100644 index 0000000000..a734c8f6c2 --- /dev/null +++ b/pkg/multicloud/apsara/vrouter.go @@ -0,0 +1,35 @@ +// 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 apsara + +import ( + "time" +) + +// "CreationTime":"2017-03-19T13:37:40Z","Description":"","RegionId":"cn-hongkong","RouteTableIds":{"RouteTableId":["vtb-j6c60lectdi80rk5xz43g"]},"VRouterId":"vrt-j6c00qrol733dg36iq4qj","VRouterName":"","VpcId":"vpc-j6c86z3sh8ufhgsxwme0q" + +type SRouteTableIds struct { + RouteTableId []string +} + +type SVRouter struct { + CreationTime time.Time + Description string + RegionId string + RouteTableIds SRouteTableIds + VRouterId string + VRouterName string + VpcId string +} diff --git a/pkg/multicloud/apsara/vswitch.go b/pkg/multicloud/apsara/vswitch.go new file mode 100644 index 0000000000..d7ec7ec20e --- /dev/null +++ b/pkg/multicloud/apsara/vswitch.go @@ -0,0 +1,277 @@ +// 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 apsara + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +// {"AvailableIpAddressCount":4091,"CidrBlock":"172.31.32.0/20","CreationTime":"2017-03-19T13:37:44Z","Description":"System created default virtual switch.","IsDefault":true,"Status":"Available","VSwitchId":"vsw-j6c3gig5ub4fmi2veyrus","VSwitchName":"","VpcId":"vpc-j6c86z3sh8ufhgsxwme0q","ZoneId":"cn-hongkong-b"} + +const ( + VSwitchPending = "Pending" + VSwitchAvailable = "Available" +) + +type SCloudResources struct { + CloudResourceSetType []string +} + +type SVSwitch struct { + wire *SWire + + AvailableIpAddressCount int + + CidrBlock string + Ipv6CidrBlock string + CreationTime time.Time + Description string + IsDefault bool + Status string + VSwitchId string + VSwitchName string + VpcId string + ZoneId string + + CloudResources SCloudResources + ResourceGroupId string + RouteTable SRouteTable +} + +func (self *SVSwitch) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SVSwitch) GetId() string { + return self.VSwitchId +} + +func (self *SVSwitch) GetName() string { + if len(self.VSwitchName) > 0 { + return self.VSwitchName + } + return self.VSwitchId +} + +func (self *SVSwitch) GetGlobalId() string { + return self.VSwitchId +} + +func (self *SVSwitch) IsEmulated() bool { + return false +} + +func (self *SVSwitch) GetStatus() string { + return strings.ToLower(self.Status) +} + +func (self *SVSwitch) Refresh() error { + log.Debugf("vsiwtch refresh %s", self.VSwitchId) + new, err := self.wire.zone.region.GetVSwitchAttributes(self.VSwitchId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SVSwitch) GetIWire() cloudprovider.ICloudWire { + return self.wire +} + +func (self *SVSwitch) GetIpStart() string { + pref, _ := netutils.NewIPV4Prefix(self.CidrBlock) + startIp := pref.Address.NetAddr(pref.MaskLen) // 0 + startIp = startIp.StepUp() // 1 + return startIp.String() +} + +func (self *SVSwitch) GetIpEnd() string { + pref, _ := netutils.NewIPV4Prefix(self.CidrBlock) + endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255 + endIp = endIp.StepDown() // 254 + endIp = endIp.StepDown() // 253 + endIp = endIp.StepDown() // 252 + return endIp.String() +} + +func (self *SVSwitch) GetIpMask() int8 { + pref, _ := netutils.NewIPV4Prefix(self.CidrBlock) + return pref.MaskLen +} + +func (self *SVSwitch) GetGateway() string { + pref, _ := netutils.NewIPV4Prefix(self.CidrBlock) + endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255 + endIp = endIp.StepDown() // 254 + return endIp.String() +} + +func (self *SVSwitch) GetServerType() string { + return api.NETWORK_TYPE_GUEST +} + +func (self *SVSwitch) GetIsPublic() bool { + // return self.IsDefault + return true +} + +func (self *SVSwitch) GetPublicScope() rbacutils.TRbacScope { + return rbacutils.ScopeDomain +} + +func (self *SRegion) createVSwitch(zoneId string, vpcId string, name string, cidr string, desc string) (string, error) { + params := make(map[string]string) + params["ZoneId"] = zoneId + params["VpcId"] = vpcId + params["CidrBlock"] = cidr + params["VSwitchName"] = name + if len(desc) > 0 { + params["Description"] = desc + } + params["ClientToken"] = utils.GenRequestId(20) + + body, err := self.vpcRequest("CreateVSwitch", params) + if err != nil { + return "", err + } + return body.GetString("VSwitchId") +} + +func (self *SRegion) DeleteVSwitch(vswitchId string) error { + params := make(map[string]string) + params["VSwitchId"] = vswitchId + + _, err := self.vpcRequest("DeleteVSwitch", params) + return err +} + +func (self *SVSwitch) Delete() error { + err := self.Refresh() + if err != nil { + log.Errorf("refresh vswitch fail %s", err) + return err + } + if len(self.RouteTable.RouteTableId) > 0 && !self.RouteTable.IsSystem() { + err = self.wire.zone.region.UnassociateRouteTable(self.RouteTable.RouteTableId, self.VSwitchId) + if err != nil { + log.Errorf("unassociate routetable fail %s", err) + return err + } + } + err = self.dissociateWithSNAT() + if err != nil { + log.Errorf("fail to dissociateWithSNAT") + return err + } + err = cloudprovider.Wait(10*time.Second, 60*time.Second, func() (bool, error) { + err := self.wire.zone.region.DeleteVSwitch(self.VSwitchId) + if err != nil { + // delete network immediately after deleting vm on it + // \"Code\":\"DependencyViolation\",\"Message\":\"Specified object has dependent resources.\"} + if isError(err, "DependencyViolation") { + return false, nil + } + return false, err + } else { + return true, nil + } + }) + return err +} + +func (self *SVSwitch) GetAllocTimeoutSeconds() int { + return 120 // 2 minutes +} + +func (self *SRegion) GetVSwitches(ids []string, vpcId string, offset int, limit int) ([]SVSwitch, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if ids != nil && len(ids) > 0 { + params["VSwitchId"] = strings.Join(ids, ",") + } + if len(vpcId) > 0 { + params["VpcId"] = vpcId + } + + body, err := self.vpcRequest("DescribeVSwitches", params) + if err != nil { + log.Errorf("GetVSwitches fail %s", err) + return nil, 0, err + } + + switches := make([]SVSwitch, 0) + err = body.Unmarshal(&switches, "VSwitches", "VSwitch") + if err != nil { + log.Errorf("Unmarshal vswitches fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return switches, int(total), nil +} + +func (self *SRegion) GetVSwitchAttributes(idstr string) (*SVSwitch, error) { + params := make(map[string]string) + params["VSwitchId"] = idstr + + body, err := self.vpcRequest("DescribeVSwitchAttributes", params) + if err != nil { + log.Errorf("DescribeVSwitchAttributes fail %s", err) + return nil, err + } + if self.client.debug { + log.Debugf("%s", body.PrettyString()) + } + switches := SVSwitch{} + err = body.Unmarshal(&switches) + if err != nil { + log.Errorf("Unmarshal vswitches fail %s", err) + return nil, err + } + return &switches, nil +} + +func (vsw *SVSwitch) dissociateWithSNAT() error { + natgatways, err := vsw.wire.vpc.getNatGateways() + if err != nil { + return err + } + for i := range natgatways { + err = natgatways[i].dissociateWithVswitch(vsw.VSwitchId) + if err != nil { + return err + } + } + return nil +} + +func (self *SVSwitch) GetProjectId() string { + return self.ResourceGroupId +} diff --git a/pkg/multicloud/apsara/wire.go b/pkg/multicloud/apsara/wire.go new file mode 100644 index 0000000000..25f9cf58ec --- /dev/null +++ b/pkg/multicloud/apsara/wire.go @@ -0,0 +1,141 @@ +// 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 apsara + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SWire struct { + zone *SZone + vpc *SVpc + + inetworks []cloudprovider.ICloudNetwork +} + +func (self *SWire) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SWire) GetId() string { + return fmt.Sprintf("%s-%s", self.vpc.GetId(), self.zone.GetId()) +} + +func (self *SWire) GetName() string { + return self.GetId() +} + +func (self *SWire) IsEmulated() bool { + return true +} + +func (self *SWire) GetStatus() string { + return "available" +} + +func (self *SWire) Refresh() error { + return nil +} + +func (self *SWire) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.vpc.GetGlobalId(), self.zone.GetGlobalId()) +} + +func (self *SWire) GetIVpc() cloudprovider.ICloudVpc { + return self.vpc +} + +func (self *SWire) GetIZone() cloudprovider.ICloudZone { + return self.zone +} + +func (self *SWire) addNetwork(vswitch *SVSwitch) { + if self.inetworks == nil { + self.inetworks = make([]cloudprovider.ICloudNetwork, 0) + } + find := false + for i := 0; i < len(self.inetworks); i += 1 { + if self.inetworks[i].GetId() == vswitch.VSwitchId { + find = true + break + } + } + if !find { + self.inetworks = append(self.inetworks, vswitch) + } +} + +func (self *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) { + if self.inetworks == nil { + err := self.vpc.fetchVSwitches() + if err != nil { + return nil, err + } + } + return self.inetworks, nil +} + +func (self *SWire) getNetworkById(vswitchId string) *SVSwitch { + networks, err := self.GetINetworks() + if err != nil { + return nil + } + log.Debugf("search for networks %d", len(networks)) + for i := 0; i < len(networks); i += 1 { + log.Debugf("search %s", networks[i].GetName()) + network := networks[i].(*SVSwitch) + if network.VSwitchId == vswitchId { + return network + } + } + return nil +} + +func (self *SWire) GetBandwidth() int { + return 10000 +} + +func (self *SWire) CreateINetwork(opts *cloudprovider.SNetworkCreateOptions) (cloudprovider.ICloudNetwork, error) { + vswitchId, err := self.zone.region.createVSwitch(self.zone.ZoneId, self.vpc.VpcId, opts.Name, opts.Cidr, opts.Desc) + if err != nil { + log.Errorf("createVSwitch error %s", err) + return nil, err + } + self.inetworks = nil + vswitch := self.getNetworkById(vswitchId) + if vswitch == nil { + log.Errorf("cannot find vswitch after create????") + return nil, cloudprovider.ErrNotFound + } + return vswitch, nil +} + +func (self *SWire) GetINetworkById(netid string) (cloudprovider.ICloudNetwork, error) { + networks, err := self.GetINetworks() + if err != nil { + return nil, err + } + for i := 0; i < len(networks); i += 1 { + if networks[i].GetGlobalId() == netid { + return networks[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/multicloud/apsara/wrapper.go b/pkg/multicloud/apsara/wrapper.go new file mode 100644 index 0000000000..dad96868fe --- /dev/null +++ b/pkg/multicloud/apsara/wrapper.go @@ -0,0 +1,41 @@ +// 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 apsara + +import ( + "runtime/debug" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" +) + +func processCommonRequest(client *sdk.Client, req *requests.CommonRequest) (response *responses.CommonResponse, err error) { + defer func() { + if r := recover(); r != nil { + log.Errorf("client.ProcessCommonRequest error: %s", r) + debug.PrintStack() + response = nil + jsonError := jsonutils.NewDict() + jsonError.Add(jsonutils.NewString("SignatureNonceUsed"), "Code") + err = errors.Error(jsonError.String()) + } + }() + return client.ProcessCommonRequest(req) +} diff --git a/pkg/multicloud/apsara/zone.go b/pkg/multicloud/apsara/zone.go new file mode 100644 index 0000000000..034860c5e4 --- /dev/null +++ b/pkg/multicloud/apsara/zone.go @@ -0,0 +1,281 @@ +// 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 apsara + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type TChargeType string + +const ( + PrePaidInstanceChargeType TChargeType = "PrePaid" + PostPaidInstanceChargeType TChargeType = "PostPaid" + PostPaidDBInstanceChargeType TChargeType = "Postpaid" + DefaultInstanceChargeType = PostPaidInstanceChargeType +) + +type SpotStrategyType string + +const ( + NoSpotStrategy SpotStrategyType = "NoSpot" + SpotWithPriceLimitStrategy SpotStrategyType = "SpotWithPriceLimit" + SpotAsPriceGoStrategy SpotStrategyType = "SpotAsPriceGo" + DefaultSpotStrategy = NoSpotStrategy +) + +type SDedicatedHostGenerations struct { + DedicatedHostGeneration []string +} + +type SVolumeCategories struct { + VolumeCategories []string +} + +type SSupportedDataDiskCategories struct { + SupportedDataDiskCategory []string +} + +type SSupportedInstanceGenerations struct { + SupportedInstanceGeneration []string +} + +type SSupportedInstanceTypeFamilies struct { + SupportedInstanceTypeFamily []string +} + +type SSupportedInstanceTypes struct { + SupportedInstanceType []string +} + +type SSupportedNetworkTypes struct { + SupportedNetworkCategory []string +} + +type SSupportedSystemDiskCategories struct { + SupportedSystemDiskCategory []string +} + +type SResourcesInfo struct { + DataDiskCategories SSupportedDataDiskCategories + InstanceGenerations SSupportedInstanceGenerations + InstanceTypeFamilies SSupportedInstanceTypeFamilies + InstanceTypes SSupportedInstanceTypes + IoOptimized bool + NetworkTypes SSupportedNetworkTypes + SystemDiskCategories SSupportedSystemDiskCategories +} + +type SResources struct { + ResourcesInfo []SResourcesInfo +} + +type SResourceCreation struct { + ResourceTypes []string +} + +type SInstanceTypes struct { + InstanceTypes []string +} + +type SDiskCategories struct { + DiskCategories []string +} + +type SDedicatedHostTypes struct { + DedicatedHostType []string +} + +type SZone struct { + region *SRegion + + iwires []cloudprovider.ICloudWire + + host *SHost + + istorages []cloudprovider.ICloudStorage + + ZoneId string + LocalName string + DedicatedHostGenerations SDedicatedHostGenerations + AvailableVolumeCategories SVolumeCategories + /* 可供创建的具体资源,AvailableResourcesType 组成的数组 */ + AvailableResources SResources + /* 允许创建的资源类型集合 */ + AvailableResourceCreation SResourceCreation + /* 允许创建的实例规格类型 */ + AvailableInstanceTypes SInstanceTypes + /* 支持的磁盘种类集合 */ + AvailableDiskCategories SDiskCategories + AvailableDedicatedHostTypes SDedicatedHostTypes +} + +func (self *SZone) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SZone) GetId() string { + return self.ZoneId +} + +func (self *SZone) GetName() string { + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_APSARA_CN, self.LocalName) +} + +func (self *SZone) GetGlobalId() string { + return fmt.Sprintf("%s/%s", self.region.GetGlobalId(), self.ZoneId) +} + +func (self *SZone) IsEmulated() bool { + return false +} + +func (self *SZone) GetStatus() string { + if len(self.AvailableResourceCreation.ResourceTypes) == 0 || !utils.IsInStringArray("Instance", self.AvailableResourceCreation.ResourceTypes) { + return api.ZONE_SOLDOUT + } else { + return api.ZONE_ENABLE + } +} + +func (self *SZone) Refresh() error { + // do nothing + return nil +} + +func (self *SZone) GetIRegion() cloudprovider.ICloudRegion { + return self.region +} + +func (self *SZone) fetchStorages() error { + categories := self.AvailableDiskCategories.DiskCategories + // if len(self.AvailableResources.ResourcesInfo) > 0 { + // categories = self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory + // } + self.istorages = []cloudprovider.ICloudStorage{} + + for _, sc := range categories { + storage := SStorage{zone: self, storageType: sc} + self.istorages = append(self.istorages, &storage) + if sc == api.STORAGE_CLOUD_ESSD { + storage_l2 := SStorage{zone: self, storageType: api.STORAGE_CLOUD_ESSD_PL2} + self.istorages = append(self.istorages, &storage_l2) + storage_l3 := SStorage{zone: self, storageType: api.STORAGE_CLOUD_ESSD_PL3} + self.istorages = append(self.istorages, &storage_l3) + } + } + return nil +} + +func (self *SZone) getStorageByCategory(category string) (*SStorage, error) { + storages, err := self.GetIStorages() + if err != nil { + return nil, err + } + for i := 0; i < len(storages); i += 1 { + storage := storages[i].(*SStorage) + if storage.storageType == category { + return storage, nil + } + } + return nil, fmt.Errorf("No such storage %s", category) +} + +func (self *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + if self.istorages == nil { + err := self.fetchStorages() + if err != nil { + return nil, errors.Wrapf(err, "fetchStorages") + } + } + return self.istorages, nil +} + +func (self *SZone) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + if self.istorages == nil { + err := self.fetchStorages() + if err != nil { + return nil, errors.Wrapf(err, "fetchStorages") + } + } + for i := 0; i < len(self.istorages); i += 1 { + if self.istorages[i].GetGlobalId() == id { + return self.istorages[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SZone) getHost() *SHost { + if self.host == nil { + self.host = &SHost{zone: self} + } + return self.host +} + +func (self *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { + return []cloudprovider.ICloudHost{self.getHost()}, nil +} + +func (self *SZone) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + host := self.getHost() + if host.GetGlobalId() == id { + return host, nil + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SZone) addWire(wire *SWire) { + if self.iwires == nil { + self.iwires = make([]cloudprovider.ICloudWire, 0) + } + self.iwires = append(self.iwires, wire) +} + +func (self *SZone) GetIWires() ([]cloudprovider.ICloudWire, error) { + return self.iwires, nil +} + +func (self *SZone) getNetworkById(vswitchId string) *SVSwitch { + log.Debugf("Search in wires %d", len(self.iwires)) + for i := 0; i < len(self.iwires); i += 1 { + log.Debugf("Search in wire %s", self.iwires[i].GetName()) + wire := self.iwires[i].(*SWire) + net := wire.getNetworkById(vswitchId) + if net != nil { + return net + } + } + return nil +} + +func (self *SZone) getSysDiskCategories() []string { + if len(self.AvailableResources.ResourcesInfo) > 0 { + if utils.IsInStringArray(api.STORAGE_CLOUD_ESSD, self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory) { + self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory = append(self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory, api.STORAGE_CLOUD_ESSD_PL2) + self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory = append(self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory, api.STORAGE_CLOUD_ESSD_PL3) + } + return self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory + } + return nil +} diff --git a/pkg/multicloud/aws/aws.go b/pkg/multicloud/aws/aws.go index ca33f622b8..5d76c2ab38 100644 --- a/pkg/multicloud/aws/aws.go +++ b/pkg/multicloud/aws/aws.go @@ -513,6 +513,7 @@ func (self *SAwsClient) GetCapabilities() []string { // cloudprovider.CLOUD_CAPABILITY_EVENT, cloudprovider.CLOUD_CAPABILITY_CLOUDID, cloudprovider.CLOUD_CAPABILITY_DNSZONE, + cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, } return caps } diff --git a/pkg/multicloud/aws/disk.go b/pkg/multicloud/aws/disk.go index ada1598716..01995ecf13 100644 --- a/pkg/multicloud/aws/disk.go +++ b/pkg/multicloud/aws/disk.go @@ -485,7 +485,7 @@ func (self *SRegion) CreateDisk(zoneId string, category string, name string, siz params.SetSnapshotId(snapshotId) } - if category == api.STORAGE_IO1_SSD { + if category == api.STORAGE_IO1_SSD || category == api.STORAGE_IO2_SSD { params.SetIops(200) } diff --git a/pkg/multicloud/aws/instance.go b/pkg/multicloud/aws/instance.go index 21002cb2ce..c75b6352e0 100644 --- a/pkg/multicloud/aws/instance.go +++ b/pkg/multicloud/aws/instance.go @@ -378,8 +378,8 @@ func (self *SInstance) StartVM(ctx context.Context) error { return cloudprovider.ErrTimeout } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { - err := self.host.zone.region.StopVM(self.InstanceId, isForce) +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := self.host.zone.region.StopVM(self.InstanceId, opts.IsForce) if err != nil { return err } @@ -763,6 +763,13 @@ func (self *SRegion) CreateInstance(name string, imageId string, instanceType st } else { ebs.SetIops(32000) } + } else if disk.Category == api.STORAGE_IO2_SSD { + iops := int64(disk.Size * 100) + if iops < 64000 { + ebs.SetIops(iops) + } else { + ebs.SetIops(64000) + } } blockDevice := &ec2.BlockDeviceMapping{ diff --git a/pkg/multicloud/aws/routetable.go b/pkg/multicloud/aws/routetable.go index ed7f2e872f..0b95df7b5a 100644 --- a/pkg/multicloud/aws/routetable.go +++ b/pkg/multicloud/aws/routetable.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -59,7 +60,7 @@ func (self *SRouteTable) GetGlobalId() string { } func (self *SRouteTable) GetStatus() string { - return "" + return api.ROUTE_TABLE_AVAILABLE } func (self *SRouteTable) Refresh() error { diff --git a/pkg/multicloud/aws/storage.go b/pkg/multicloud/aws/storage.go index 9550c9423e..b5bff5010f 100644 --- a/pkg/multicloud/aws/storage.go +++ b/pkg/multicloud/aws/storage.go @@ -91,7 +91,7 @@ func (self *SStorage) GetStorageType() string { } func (self *SStorage) GetMediumType() string { - if self.storageType == api.STORAGE_GP2_SSD || self.storageType == api.STORAGE_IO1_SSD { + if self.storageType == api.STORAGE_GP2_SSD || self.storageType == api.STORAGE_IO1_SSD || self.storageType == api.STORAGE_IO2_SSD { return api.DISK_TYPE_SSD } else { return api.DISK_TYPE_ROTATE diff --git a/pkg/multicloud/aws/zone.go b/pkg/multicloud/aws/zone.go index 6b12485bb2..91e5b01886 100644 --- a/pkg/multicloud/aws/zone.go +++ b/pkg/multicloud/aws/zone.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -65,6 +66,9 @@ func (self *SZone) getHost() *SHost { func (self *SZone) getStorageType() { if len(self.storageTypes) == 0 { self.storageTypes = StorageTypes + if !utils.IsInStringArray(self.GetId(), []string{"af-south-1", "eu-south-1", "eu-west-3", "sa-east-1", "cn-north-1", "cn-northwest-1"}) { + self.storageTypes = append(self.storageTypes, api.STORAGE_IO2_SSD) + } } } diff --git a/pkg/multicloud/azure/azure.go b/pkg/multicloud/azure/azure.go index 687e434191..c545dab365 100644 --- a/pkg/multicloud/azure/azure.go +++ b/pkg/multicloud/azure/azure.go @@ -15,11 +15,9 @@ package azure import ( + "context" "fmt" - "io/ioutil" - "net/http" "net/url" - net_url "net/url" "strconv" "strings" "time" @@ -31,9 +29,11 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/httputils" ) const ( @@ -62,7 +62,7 @@ type SAzureClient struct { env azureenv.Environment authorizer autorest.Authorizer - iregions []cloudprovider.ICloudRegion + regions []SRegion iBuckets []cloudprovider.ICloudBucket subscriptions []SSubscription @@ -70,43 +70,6 @@ type SAzureClient struct { debug bool } -var DEFAULT_API_VERSION = map[string]string{ - "vmSizes": "2018-06-01", //2015-05-01-preview,2015-06-15,2016-03-30,2016-04-30-preview,2016-08-30,2017-03-30,2017-12-01,2018-04-01,2018-06-01,2018-10-01 - "Microsoft.Compute/virtualMachineScaleSets": "2017-12-01", - "Microsoft.Compute/virtualMachines": "2018-04-01", - "Microsoft.ClassicCompute/virtualMachines": "2017-04-01", - "Microsoft.Compute/operations": "2018-10-01", - "Microsoft.ClassicCompute/operations": "2017-04-01", - "Microsoft.Network/virtualNetworks": "2018-08-01", - "Microsoft.ClassicNetwork/virtualNetworks": "2017-11-15", //avaliable 2014-01-01,2014-06-01,2015-06-01,2015-12-01,2016-04-01,2016-11-01,2017-11-15 - "Microsoft.Compute/disks": "2018-06-01", //avaliable 2016-04-30-preview,2017-03-30,2018-04-01,2018-06-01 - "Microsoft.Storage/storageAccounts": "2016-12-01", //2018-03-01-preview,2018-02-01,2017-10-01,2017-06-01,2016-12-01,2016-05-01,2016-01-01,2015-06-15,2015-05-01-preview - "Microsoft.ClassicStorage/storageAccounts": "2016-04-01", //2014-01-01,2014-04-01,2014-04-01-beta,2014-06-01,2015-06-01,2015-12-01,2016-04-01,2016-11-01 - "Microsoft.Compute/snapshots": "2018-06-01", //2016-04-30-preview,2017-03-30,2018-04-01,2018-06-01 - "Microsoft.Compute/images": "2018-10-01", //2016-04-30-preview,2016-08-30,2017-03-30,2017-12-01,2018-04-01,2018-06-01,2018-10-01 - "Microsoft.Storage": "2016-12-01", //2018-03-01-preview,2018-02-01,2017-10-01,2017-06-01,2016-12-01,2016-05-01,2016-01-01,2015-06-15,2015-05-01-preview - "Microsoft.Network/publicIPAddresses": "2018-06-01", //2014-12-01-preview, 2015-05-01-preview, 2015-06-15, 2016-03-30, 2016-06-01, 2016-07-01, 2016-08-01, 2016-09-01, 2016-10-01, 2016-11-01, 2016-12-01, 2017-03-01, 2017-04-01, 2017-06-01, 2017-08-01, 2017-09-01, 2017-10-01, 2017-11-01, 2018-01-01, 2018-02-01, 2018-03-01, 2018-04-01, 2018-05-01, 2018-06-01, 2018-07-01, 2018-08-01 - "Microsoft.Network/networkSecurityGroups": "2018-06-01", - "Microsoft.Network/networkInterfaces": "2018-06-01", //2014-12-01-preview, 2015-05-01-preview, 2015-06-15, 2016-03-30, 2016-06-01, 2016-07-01, 2016-08-01, 2016-09-01, 2016-10-01, 2016-11-01, 2016-12-01, 2017-03-01, 2017-04-01, 2017-06-01, 2017-08-01, 2017-09-01, 2017-10-01, 2017-11-01, 2018-01-01, 2018-02-01, 2018-03-01, 2018-04-01, 2018-05-01, 2018-06-01, 2018-07-01, 2018-08-01 - "Microsoft.Network": "2018-06-01", - "Microsoft.ClassicNetwork/reservedIps": "2016-04-01", //2014-01-01,2014-06-01,2015-06-01,2015-12-01,2016-04-01,2016-11-01 - "Microsoft.ClassicNetwork/networkSecurityGroups": "2016-11-01", //2015-06-01,2015-12-01,2016-04-01,2016-11-01 - "Microsoft.ClassicCompute/domainNames": "2015-12-01", //2014-01-01, 2014-06-01, 2015-06-01, 2015-10-01, 2015-12-01, 2016-04-01, 2016-11-01, 2017-11-01, 2017-11-15 - "Microsoft.Compute/locations": "2018-06-01", - "microsoft.insights/eventtypes/management/values": "2017-03-01-preview", - "Microsoft.Authorization/policyDefinitions": "2019-09-01", - "Microsoft.Authorization/policyAssignments": "2019-09-01", - "Microsoft.Billing": "2018-03-01-preview", - "Microsoft.Authorization": "2018-01-01-preview", -} - -var GRAPH_API_VERSION = map[string]string{ - "Microsoft.DirectoryServices.User": "1.6", - "users": "1.6", - "Microsoft.DirectoryServices.Group": "1.6", - "groups": "1.6", -} - type AzureClientConfig struct { cpcfg cloudprovider.ProviderConfig @@ -150,19 +113,21 @@ func NewAzureClient(cfg *AzureClientConfig) (*SAzureClient, error) { AzureClientConfig: cfg, debug: cfg.debug, } - err := client.fetchSubscriptions() + var err error + client.subscriptions, err = client.ListSubscriptions() if err != nil { - return nil, errors.Wrap(err, "fetchSubscriptions") + return nil, errors.Wrap(err, "ListSubscriptions") } - err = client.fetchRegions() + client.regions, err = client.ListRegions() if err != nil { - return nil, errors.Wrap(err, "fetchRegions") + return nil, errors.Wrapf(err, "ListRegions") } - if len(cfg.subscriptionId) > 0 { - err = client.fetchBuckets() - if err != nil { - return nil, errors.Wrap(err, "fetchBuckets") - } + for i := range client.regions { + client.regions[i].client = &client + } + client.ressourceGroups, err = client.ListResourceGroups() + if err != nil { + return nil, errors.Wrapf(err, "ListResourceGroups") } return &client, nil } @@ -172,7 +137,7 @@ func (self *SAzureClient) getClient(resource TAzureResource) (*autorest.Client, conf := auth.NewClientCredentialsConfig(self.clientId, self.clientSecret, self.tenantId) env, err := azureenv.EnvironmentFromName(self.envName) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "azureenv.EnvironmentFromName(%s)", self.envName) } httpClient := self.cpcfg.HttpClient() @@ -191,14 +156,14 @@ func (self *SAzureClient) getClient(resource TAzureResource) (*autorest.Client, { spt, err := conf.ServicePrincipalToken() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "ServicePrincipalToken") } spt.SetSender(httpClient) client.Authorizer = autorest.NewBearerAuthorizer(spt) } if self.debug { client.RequestInspector = LogRequest() - client.ResponseInspector = LogResponse() + //client.ResponseInspector = LogResponse() } return &client, nil @@ -212,76 +177,91 @@ func (self *SAzureClient) getGraphClient() (*autorest.Client, error) { return self.getClient(GraphResource) } -func (self *SAzureClient) jsonRequest(method, url string, body string) (jsonutils.JSONObject, error) { +func (self *SAzureClient) jsonRequest(method, path string, body jsonutils.JSONObject, params url.Values, showErrorMsg bool) (jsonutils.JSONObject, error) { cli, err := self.getDefaultClient() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "getDefaultClient") } - return jsonRequest(cli, method, self.domain, url, self.subscriptionId, body, DefaultResource) + defer func() { + if err != nil && showErrorMsg { + bj := "" + if body != nil { + bj = body.PrettyString() + } + log.Errorf("%s %s?%s \n%s error: %v", method, path, params.Encode(), bj, err) + } + }() + var resp jsonutils.JSONObject + for i := 0; i < 2; i++ { + resp, err = jsonRequest(cli, method, self.domain, path, body, params, self.debug) + if err != nil { + if ae, ok := err.(*AzureResponseError); ok { + switch ae.AzureError.Code { + case "SubscriptionNotRegistered": + err = self.ServiceRegister("Microsoft.Network") + if err != nil { + return nil, errors.Wrapf(err, "self.registerService(Microsoft.Network)") + } + continue + case "MissingSubscriptionRegistration": + for _, serviceType := range ae.AzureError.Details { + err = self.ServiceRegister(serviceType.Target) + if err != nil { + return nil, errors.Wrapf(err, "self.registerService(%s)", serviceType.Target) + } + } + continue + } + } + return resp, err + } + return resp, err + } + return resp, err } -func (self *SAzureClient) Put(url string, body jsonutils.JSONObject) error { - cli, err := self.getDefaultClient() +func (self *SAzureClient) gjsonRequest(method, path string, body jsonutils.JSONObject, params url.Values) (jsonutils.JSONObject, error) { + cli, err := self.getGraphClient() if err != nil { - return err + return nil, errors.Wrapf(err, "gjsonRequest") } - resp, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String(), DefaultResource) - if err != nil { - return err + if params == nil { + params = url.Values{} } - if self.debug { - log.Debugf("%s", resp) - } - return nil + params.Set("api-version", "1.6") + return jsonRequest(cli, method, self.domain, path, body, params, self.debug) } -func (self *SAzureClient) POST(url string, body jsonutils.JSONObject) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - resp, err := jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body.String(), DefaultResource) - if err != nil { - return err - } - if self.debug { - log.Debugf("%s", resp) - } - return nil +func (self *SAzureClient) put(path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := url.Values{} + params.Set("api-version", self._apiVersion(path, params)) + return self.jsonRequest("PUT", path, body, params, true) } -func (self *SAzureClient) Patch(url string, body jsonutils.JSONObject) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - resp, err := jsonRequest(cli, "PATCH", self.domain, url, self.subscriptionId, body.String(), DefaultResource) - if err != nil { - return err - } - if self.debug { - log.Debugf("%s", resp) - } - return nil +func (self *SAzureClient) post(path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := url.Values{} + params.Set("api-version", self._apiVersion(path, params)) + return self.jsonRequest("POST", path, body, params, true) } -func (self *SAzureClient) Get(resourceId string, params []string, retVal interface{}) error { +func (self *SAzureClient) patch(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := url.Values{} + params.Set("api-version", self._apiVersion(resource, params)) + return self.jsonRequest("PATCH", resource, body, params, true) +} + +func (self *SAzureClient) _get(resourceId string, params url.Values, retVal interface{}, showErrorMsg bool) error { if len(resourceId) == 0 { return cloudprovider.ErrNotFound } - path := resourceId - if len(params) > 0 { - path += fmt.Sprintf("?%s", strings.Join(params, "&")) + if params == nil { + params = url.Values{} } - cli, err := self.getDefaultClient() + params.Set("api-version", self._apiVersion(resourceId, params)) + body, err := self.jsonRequest("GET", resourceId, nil, params, showErrorMsg) if err != nil { return err } - body, err := jsonRequest(cli, "GET", self.domain, path, self.subscriptionId, "", DefaultResource) - if err != nil { - return err - } - //fmt.Println(body) err = body.Unmarshal(retVal) if err != nil { return err @@ -289,70 +269,15 @@ func (self *SAzureClient) Get(resourceId string, params []string, retVal interfa return nil } -func (self *SAzureClient) ListVmSizes(location string) (jsonutils.JSONObject, error) { - cli, err := self.getDefaultClient() - if err != nil { - return nil, err - } - if len(self.subscriptionId) == 0 { - return nil, fmt.Errorf("need subscription id") - } - url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/vmSizes", self.subscriptionId, location) - return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) +func (self *SAzureClient) get(resourceId string, params url.Values, retVal interface{}) error { + return self._get(resourceId, params, retVal, true) } -func (self *SAzureClient) ListClassicDisks() (jsonutils.JSONObject, error) { - cli, err := self.getDefaultClient() +func (self *SAzureClient) gcreate(resource string, body jsonutils.JSONObject, retVal interface{}) error { + path := fmt.Sprintf("%s/%s", self.tenantId, resource) + result, err := self.gjsonRequest("POST", path, body, url.Values{}) if err != nil { - return nil, err - } - if len(self.subscriptionId) == 0 { - return nil, fmt.Errorf("need subscription id") - } - url := fmt.Sprintf("/subscriptions/%s/services/disks", self.subscriptionId) - return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) -} - -func (self *SAzureClient) ListAll(resourceType string, retVal interface{}) error { - return self.ListResources(resourceType, retVal, []string{"value"}) -} - -func (self *SAzureClient) ListAllWithNextToken(resourceType string, retVal interface{}) (string, error) { - return self.ListResourcesWithNextLink(resourceType, retVal, []string{"value"}) -} - -func (self *SAzureClient) ListGraphResource(resource string, params url.Values, retVal interface{}) error { - cli, err := self.getGraphClient() - if err != nil { - return err - } - if params == nil { - params = url.Values{} - } - params.Set("api-version", "1.6") - url := fmt.Sprintf("%s/%s?%s", self.tenantId, resource, params.Encode()) - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", GraphResource) - if err != nil { - return err - } - if retVal != nil { - err = body.Unmarshal(retVal, "value") - if err != nil { - return err - } - } - return nil -} - -func (self *SAzureClient) CreateGraphResource(resource string, body jsonutils.JSONObject, retVal interface{}) error { - cli, err := self.getGraphClient() - if err != nil { - return err - } - url := fmt.Sprintf("%s/%s?api-version=1.6", self.tenantId, resource) - result, err := jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body.String(), GraphResource) - if err != nil { - return err + return errors.Wrapf(err, "gjsonRequest") } if retVal != nil { return result.Unmarshal(retVal) @@ -360,208 +285,212 @@ func (self *SAzureClient) CreateGraphResource(resource string, body jsonutils.JS return nil } -func (self *SAzureClient) ListResourcesWithNextLink(resourceType string, retVal interface{}, keys []string) (string, error) { - cli, err := self.getDefaultClient() - if err != nil { - return "", err - } - url := "/subscriptions" - if len(self.subscriptionId) > 0 { - url += fmt.Sprintf("/%s", self.subscriptionId) - } - if len(resourceType) > 0 { - url += fmt.Sprintf("/providers/%s", resourceType) - } - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) - if err != nil { - return "", err - } - // fmt.Printf("%s: %s\n", resourceType, body) - if retVal != nil { - err = body.Unmarshal(retVal, keys...) - if err != nil { - return "", err - } - } - nextLink, _ := body.GetString("nextLink") - return nextLink, nil +func (self *SAzureClient) gpatch(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.gjsonRequest("PATCH", resource, body, nil) } -func (self *SAzureClient) ListResources(resourceType string, retVal interface{}, keys []string) error { - _, err := self.ListResourcesWithNextLink(resourceType, retVal, keys) - return err -} - -func (self *SAzureClient) ListResourcesOfMetirc(resourceType string, external_id string, - params map[string]string) (jsonutils.JSONObject, error) { - cli, err := self.getDefaultClient() +func (self *SAzureClient) glist(resource string, params url.Values, retVal interface{}) error { + if params == nil { + params = url.Values{} + } + err := self._glist(resource, params, retVal) if err != nil { - return nil, err - } - //azure的external_id中会包含请求相关的url的处理信息 - url := external_id - if len(resourceType) > 0 { - url += fmt.Sprintf("/providers/%s", resourceType) - } - if len(params) > 0 { - values := net_url.Values{} - for param, value := range params { - values.Add(param, value) - } - url += fmt.Sprintf("?%s", values.Encode()) - } - //body, err := jsonRequest(cli, "GET", "https://management.azure.com", url, self.subscriptionId, "") - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) - if err != nil { - return nil, err - } - return body, nil -} - -func (self *SAzureClient) ListSubscriptions() (jsonutils.JSONObject, error) { - cli, err := self.getDefaultClient() - if err != nil { - return nil, err - } - return jsonRequest(cli, "GET", self.domain, "/subscriptions", self.subscriptionId, "", DefaultResource) -} - -func (self *SAzureClient) List(golbalResource string, retVal interface{}) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - url := "/subscriptions" - if len(self.subscriptionId) > 0 { - url += fmt.Sprintf("/%s", self.subscriptionId) - } - if len(self.subscriptionId) > 0 && len(golbalResource) > 0 { - url += fmt.Sprintf("/%s", golbalResource) - } - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) - if err != nil { - return err - } - return body.Unmarshal(retVal, "value") -} - -func (self *SAzureClient) listSubscriptionResource(subscriptionId, resource string, retVal interface{}) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - url := fmt.Sprintf("/subscriptions/%s/%s", subscriptionId, resource) - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) - if err != nil { - return err - } - return body.Unmarshal(retVal, "value") -} - -func (self *SAzureClient) ListByTypeWithResourceGroup(resourceGroupName string, Type string, retVal interface{}) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - if len(self.subscriptionId) == 0 { - return fmt.Errorf("Missing subscription Info") - } - url := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s", self.subscriptionId, resourceGroupName, Type) - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) - if err != nil { - return err - } - return body.Unmarshal(retVal, "value") -} - -func (self *SAzureClient) Delete(resourceId string) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, self.subscriptionId, "", DefaultResource) - return err -} - -func (self *SAzureClient) DeleteGraph(resourceId string) error { - cli, err := self.getGraphClient() - if err != nil { - return err - } - _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, self.subscriptionId, "", GraphResource) - return err -} - -func (self *SAzureClient) PerformAction(resourceId string, action string, body string) (jsonutils.JSONObject, error) { - cli, err := self.getDefaultClient() - if err != nil { - return nil, err - } - url := fmt.Sprintf("%s/%s", resourceId, action) - return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body, DefaultResource) -} - -func (self *SAzureClient) CreateResourceGroup(name, location string) (*SResourceGroup, error) { - cli, err := self.getDefaultClient() - if err != nil { - return nil, errors.Wrap(err, "getDefaultClient") - } - if len(location) == 0 { - location = self.iregions[0].GetId() - } - subscriptionId, err := self.getDefaultSubscriptionId() - if err != nil { - return nil, errors.Wrap(err, "getDefaultSubscriptionId") - } - //Create resourceGroup - _url := fmt.Sprintf("/subscriptions/%s/resourcegroups/%s", subscriptionId, name) - _, err = jsonRequest(cli, "PUT", self.domain, _url, subscriptionId, fmt.Sprintf(`{"name": "%s", "location": "%s"}`, name, location), DefaultResource) - if err != nil { - return nil, errors.Wrap(err, "jsonRequest") - } - group := &SResourceGroup{} - return group, self.Get(_url, []string{}, group) -} - -func (self *SAzureClient) CreateIProject(name string) (cloudprovider.ICloudProject, error) { - return self.CreateResourceGroup(name, "") -} - -func (self *SAzureClient) getResourceGroups() ([]SResourceGroup, error) { - subscriptionId, err := self.getDefaultSubscriptionId() - if err != nil { - return nil, errors.Wrap(err, "getDefaultSubscriptionId") - } - resourceGroups := []SResourceGroup{} - err = self.listSubscriptionResource(subscriptionId, "resourcegroups", &resourceGroups) - if err != nil { - return nil, errors.Wrap(err, "listSubscriptionResource") - } - return resourceGroups, nil -} - -func (self *SAzureClient) fetchResourceGroup() error { - if len(self.ressourceGroups) > 0 { - return nil - } - var err error - self.ressourceGroups, err = self.getResourceGroups() - if err != nil { - return errors.Wrap(err, "getResourceGroups") + return errors.Wrapf(err, "_glist(%s)", resource) } return nil } -func (self *SAzureClient) checkParams(body jsonutils.JSONObject, params []string) (map[string]string, error) { - result := map[string]string{} - for i := 0; i < len(params); i++ { - data, err := body.GetString(params[i]) - if err != nil { - return nil, fmt.Errorf("Missing %s params", params[i]) - } - result[params[i]] = data +func (self *SAzureClient) _glist(resource string, params url.Values, retVal interface{}) error { + path := fmt.Sprintf("%s/%s", self.tenantId, resource) + body, err := self.gjsonRequest("GET", path, nil, params) + if err != nil { + return err } - return result, nil + err = body.Unmarshal(retVal, "value") + if err != nil { + return errors.Wrapf(err, "body.Unmarshal") + } + return nil +} + +func (self *SAzureClient) list(resource string, params url.Values, retVal interface{}) error { + if params == nil { + params = url.Values{} + } + result := []jsonutils.JSONObject{} + var key, skipToken string + for { + resp, err := self._list(resource, params) + if err != nil { + return errors.Wrapf(err, "_list(%s)", resource) + } + keys := []string{} + if resp.Contains("value") { + keys = []string{"value"} + } + part, err := resp.GetArray(keys...) + if err != nil { + return errors.Wrapf(err, "resp.GetArray(%s)", keys) + } + result = append(result, part...) + nextLink, _ := resp.GetString("nextLink") + if len(nextLink) == 0 { + break + } + link, err := url.Parse(nextLink) + if err != nil { + return errors.Wrapf(err, "url.Parse(%s)", nextLink) + } + prevSkipToken := params.Get(key) + key, skipToken = func() (string, string) { + for _, _key := range []string{"$skipToken", "$skiptoken"} { + tokens, ok := link.Query()[_key] + if ok { + for _, token := range tokens { + if len(token) > 0 && token != prevSkipToken { + return _key, token + } + } + } + } + return "", "" + }() + if len(skipToken) == 0 { + break + } + params.Del("$skipToken") + params.Del("$skiptoken") + params.Set(key, skipToken) + } + return jsonutils.Update(retVal, result) +} + +func (self *SAzureClient) _apiVersion(resource string, params url.Values) string { + version := params.Get("api-version") + if len(version) > 0 { + return version + } + info := strings.Split(strings.ToLower(resource), "/") + if utils.IsInStringArray("microsoft.compute", info) { + if utils.IsInStringArray("publishers", info) { + return "2020-06-01" + } + if utils.IsInStringArray("virtualmachines", info) { + return "2018-04-01" + } + if utils.IsInStringArray("skus", info) { + return "2019-04-01" + } + return "2018-06-01" + } else if utils.IsInStringArray("microsoft.classiccompute", info) { + return "2016-04-01" + } else if utils.IsInStringArray("microsoft.network", info) { + if utils.IsInStringArray("virtualnetworks", info) { + return "2018-08-01" + } + if utils.IsInStringArray("publicipaddresses", info) { + return "2018-03-01" + } + return "2018-06-01" + } else if utils.IsInStringArray("microsoft.classicnetwork", info) { + return "2016-04-01" + } else if utils.IsInStringArray("microsoft.storage", info) { + if utils.IsInStringArray("storageaccounts", info) { + return "2016-12-01" + } + if utils.IsInStringArray("checknameavailability", info) { + return "2019-04-01" + } + if utils.IsInStringArray("skus", info) { + return "2019-04-01" + } + if utils.IsInStringArray("usages", info) { + return "2018-07-01" + } + } else if utils.IsInStringArray("microsoft.classicstorage", info) { + if utils.IsInStringArray("storageaccounts", info) { + return "2016-04-01" + } + } else if utils.IsInStringArray("microsoft.billing", info) { + return "2018-03-01-preview" + } else if utils.IsInStringArray("microsoft.insights", info) { + return "2017-03-01-preview" + } else if utils.IsInStringArray("microsoft.authorization", info) { + return "2018-01-01-preview" + } + return AZURE_API_VERSION +} + +func (self *SAzureClient) _list(resource string, params url.Values) (jsonutils.JSONObject, error) { + subId := self.subscriptionId + if len(subId) == 0 { + for _, sub := range self.subscriptions { + if sub.State == "Enabled" { + subId = sub.SubscriptionId + } + } + } + path := "subscriptions" + switch resource { + case "subscriptions", "providers/Microsoft.Billing/enrollmentAccounts": + path = resource + case "locations", "resourcegroups", "providers": + if len(subId) == 0 { + return nil, fmt.Errorf("no avaiable subscriptions") + } + path = fmt.Sprintf("subscriptions/%s/%s", subId, resource) + default: + if len(subId) == 0 { + return nil, fmt.Errorf("no avaiable subscriptions") + } + path = fmt.Sprintf("subscriptions/%s/providers/%s", self.subscriptionId, resource) + } + params.Set("api-version", self._apiVersion(resource, params)) + return self.jsonRequest("GET", path, nil, params, true) +} + +func (self *SAzureClient) del(resourceId string) error { + params := url.Values{} + params.Set("api-version", self._apiVersion(resourceId, params)) + _, err := self.jsonRequest("DELETE", resourceId, nil, params, true) + return err +} + +func (self *SAzureClient) GDelete(resourceId string) error { + return self.gdel(resourceId) +} + +func (self *SAzureClient) gdel(resourceId string) error { + _, err := self.gjsonRequest("DELETE", resourceId, nil, url.Values{}) + if err != nil { + return errors.Wrapf(err, "gdel(%s)", resourceId) + } + return nil +} + +func (self *SAzureClient) perform(resourceId string, action string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + path := fmt.Sprintf("%s/%s", resourceId, action) + return self.post(path, body) +} + +func (self *SAzureClient) CreateIProject(name string) (cloudprovider.ICloudProject, error) { + if len(self.regions) > 0 { + _, err := self.regions[0].CreateResourceGroup(name) + if err != nil { + return nil, errors.Wrapf(err, "CreateResourceGroup") + } + return self.regions[0].GetResourceGroupDetail(name) + } + return nil, fmt.Errorf("no region found ???") +} + +func (self *SAzureClient) ListResourceGroups() ([]SResourceGroup, error) { + resourceGroups := []SResourceGroup{} + err := self.list("resourcegroups", url.Values{}, &resourceGroups) + if err != nil { + return nil, errors.Wrap(err, "list") + } + return resourceGroups, nil } type AzureErrorDetail struct { @@ -580,482 +509,193 @@ func (e *AzureError) Error() string { return jsonutils.Marshal(e).String() } -func (self *SAzureClient) getUniqName(cli *autorest.Client, resourceGroup string, resourceType, name string, body jsonutils.JSONObject) (string, string, error) { - url := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", self.subscriptionId, resourceGroup, resourceType, name) - if _, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource); err != nil { - if errors.Cause(err) == cloudprovider.ErrNotFound { - return url, body.String(), nil - } - return "", "", err - } +func (self *SAzureClient) getUniqName(resourceGroup, resourceType, name string) (string, error) { + prefix := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/", self.subscriptionId, resourceGroup, resourceType) + newName := name for i := 0; i < 20; i++ { - url = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s-%d", self.subscriptionId, resourceGroup, resourceType, name, i) - if _, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource); err != nil { - if errors.Cause(err) == cloudprovider.ErrNotFound { - data := body.(*jsonutils.JSONDict) - data.Set("name", jsonutils.NewString(fmt.Sprintf("%s-%d", name, i))) - return url, body.String(), nil - } - return "", "", err + err := self._get(prefix+newName, nil, url.Values{}, false) + if errors.Cause(err) == cloudprovider.ErrNotFound { + return newName, nil } - } - return "", "", fmt.Errorf("not find uniq name for %s[%s]", resourceType, name) -} - -func (self *SAzureClient) GetOrCreateResourceGroup(resourceGroup string, location string) error { - err := self.fetchResourceGroup() - if err != nil { - return errors.Wrap(err, "fetchResourceGroup") - } - for _, group := range self.ressourceGroups { - if strings.ToLower(group.Name) == strings.ToLower(resourceGroup) { - return nil - } - } - _, err = self.CreateResourceGroup(resourceGroup, location) - return err -} - -func (self *SAzureClient) CreateWithResourceGroup(resourceGroup string, body jsonutils.JSONObject, retVal interface{}) error { - cli, err := self.getDefaultClient() - if err != nil { - return errors.Wrap(err, "getDefaultClient") - } - if len(self.subscriptionId) == 0 { - return fmt.Errorf("Missing subscription info") - } - params, err := self.checkParams(body, []string{"type", "name", "location"}) - if err != nil { - return fmt.Errorf("Azure create resource failed: %s", err.Error()) - } - - err = self.fetchResourceGroup() - if err != nil { - return errors.Wrap(err, "fetchResourceGroup") - } - - if len(resourceGroup) == 0 { - if len(self.ressourceGroups) == 0 { - err = self.GetOrCreateResourceGroup("Default", params["location"]) - if err != nil { - return errors.Wrap(err, "GetOrCreateResourceGroup(Default)") - } - resourceGroup = "Default" + info := strings.Split(newName, "-") + num, err := strconv.Atoi(info[len(info)-1]) + if err != nil { + info = append(info, "1") } else { - resourceGroup = self.ressourceGroups[0].Name - } - } else { - err = self.GetOrCreateResourceGroup(resourceGroup, params["location"]) - if err != nil { - return errors.Wrapf(err, "GetOrCreateResourceGroup(%s)", resourceGroup) + info[len(info)-1] = fmt.Sprintf("%d", num+1) } + newName = strings.Join(info, "-") } + return "", fmt.Errorf("not find uniq name for %s[%s]", resourceType, name) +} - url, reqString, err := self.getUniqName(cli, resourceGroup, params["type"], params["name"], body) +func (self *SAzureClient) create(resourceGroup, resourceType, name string, body jsonutils.JSONObject, retVal interface{}) error { + resource := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", self.subscriptionId, resourceGroup, resourceType, name) + params := url.Values{} + params.Set("api-version", self._apiVersion(resourceType, params)) + resp, err := self.jsonRequest("PUT", resource, body, params, true) if err != nil { - return err - } - - result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, reqString, DefaultResource) - if err != nil { - return err + return errors.Wrapf(err, "jsonRequest") } if retVal != nil { - return result.Unmarshal(retVal) - } - return nil - -} - -func (self *SAzureClient) Create(body jsonutils.JSONObject, retVal interface{}) error { - return self.CreateWithResourceGroup("", body, retVal) -} - -func (self *SAzureClient) CheckNameAvailability(Type string, body string) (jsonutils.JSONObject, error) { - cli, err := self.getDefaultClient() - if err != nil { - return nil, err - } - if len(self.subscriptionId) == 0 { - return nil, fmt.Errorf("Missing subscription ID") - } - url := fmt.Sprintf("/subscriptions/%s/providers/%s/checkNameAvailability", self.subscriptionId, Type) - return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body, DefaultResource) -} - -func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) error { - cli, err := self.getDefaultClient() - if err != nil { - return err - } - url, err := body.GetString("id") - if err != nil { - return errors.Wrap(err, "failed to found id for update operation") - } - result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String(), DefaultResource) - if err != nil { - return err - } - if retVal != nil { - return result.Unmarshal(retVal) + return resp.Unmarshal(retVal) } return nil } -func waitRegisterComplete(client *autorest.Client, domain, subscriptionId string, serviceType string) error { - for i := 1; i < 10; i++ { - result, err := _jsonRequest(client, "GET", domain, fmt.Sprintf("/subscriptions/%s/providers", subscriptionId), "", DefaultResource) - if err != nil { - return err - } - value, err := result.GetArray("value") - if err != nil { - return err - } - for _, v := range value { - namespace, _ := v.GetString("namespace") - if namespace == serviceType { - state, _ := v.GetString("registrationState") - if state == "Registered" { - return nil - } - log.Debugf("service %s state %s waite %d second ...", serviceType, state, i*10) - } - } - time.Sleep(time.Second * time.Duration(i*10)) +func (self *SAzureClient) CheckNameAvailability(resourceType, name string) (bool, error) { + path := fmt.Sprintf("/subscriptions/%s/providers/%s/checkNameAvailability", self.subscriptionId, strings.Split(resourceType, "/")[0]) + body := map[string]string{ + "Name": name, + "Type": resourceType, } - return fmt.Errorf("wait service %s register timeout", serviceType) + resp, err := self.post(path, jsonutils.Marshal(body)) + if err != nil { + return false, errors.Wrapf(err, "post(%s)", path) + } + output := sNameAvailableOutput{} + err = resp.Unmarshal(&output) + if err != nil { + return false, errors.Wrap(err, "resp.Unmarshal") + } + if output.NameAvailable { + return true, nil + } + if output.Reason == "AlreadyExists" { + return false, nil + } + return true, nil } -func registerService(client *autorest.Client, domain, subscriptionId string, serviceType string) error { - registryUrl := fmt.Sprintf("/subscriptions/%s/providers/%s/register", subscriptionId, serviceType) - result, err := _jsonRequest(client, "POST", domain, registryUrl, "", DefaultResource) - if err != nil || result.Contains("error") { - return fmt.Errorf("failed to register %s service", serviceType) +func (self *SAzureClient) update(body jsonutils.JSONObject, retVal interface{}) error { + id, _ := body.GetString("id") + if len(id) == 0 { + return fmt.Errorf("failed to found id for update operation") } - if state, _ := result.GetString("registrationState"); state == "Registered" { - return nil + params := url.Values{} + params.Set("api-version", self._apiVersion(id, params)) + resp, err := self.jsonRequest("PUT", id, body, params, true) + if err != nil { + return err } - return waitRegisterComplete(client, domain, subscriptionId, serviceType) + if retVal != nil { + return resp.Unmarshal(retVal) + } + return nil } -func recoverFromError(client *autorest.Client, domain, subscriptionId string, azureErr AzureError) bool { - switch azureErr.Code { - case "SubscriptionNotRegistered": - services := []string{"Microsoft.Network"} - for _, service := range services { - if err := registerService(client, domain, subscriptionId, service); err != nil { - log.Errorf("register %s error: %v", service, err) - return false - } - } - return true - case "MissingSubscriptionRegistration": - for _, detail := range azureErr.Details { - log.Errorf("The subscription is not registered to use namespace '%s', try register it", detail.Target) - if err := registerService(client, domain, subscriptionId, detail.Target); err != nil { - log.Errorf("register %s error: %v", detail.Target, err) - return false - } - } - return true - default: - return false - } -} - -func jsonRequest(client *autorest.Client, method, domain, baseUrl string, subscriptionId string, body string, resourceType TAzureResource) (jsonutils.JSONObject, error) { - result, err := _jsonRequest(client, method, domain, baseUrl, body, resourceType) +func jsonRequest(client *autorest.Client, method, domain, baseUrl string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) { + result, err := _jsonRequest(client, method, domain, baseUrl, body, params, debug) if err != nil { return nil, err } - for _, errKey := range []string{"error", "odata.error"} { - if result.Contains(errKey) { - azureError := AzureError{} - err := result.Unmarshal(&azureError, errKey) - if err != nil { - return nil, fmt.Errorf(result.String()) - } - if recoverFromError(client, domain, subscriptionId, azureError) { - return _jsonRequest(client, method, domain, baseUrl, body, resourceType) - } - log.Errorf("Azure %s request: %s \nbody: %s error: %v", method, baseUrl, body, result.String()) - return nil, fmt.Errorf(result.String()) - } - } return result, nil } -func waitForComplatetion(client *autorest.Client, req *http.Request, resp *http.Response, timeout time.Duration) (jsonutils.JSONObject, error) { - location := resp.Header.Get("Location") - asyncoperation := resp.Header.Get("Azure-Asyncoperation") - startTime := time.Now() - if len(location) > 0 || (len(asyncoperation) > 0 && resp.StatusCode != 200 || strings.Index(req.URL.String(), "enablevmaccess") > 0) { - if len(asyncoperation) > 0 { - location = asyncoperation +// {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."},"requestId":"b776ba11-5cae-4fb9-b80d-29552e3caedd","date":"2020-10-29T09:05:23"}} +type sMessage struct { + Lang string + Value string +} +type sOdataError struct { + Code string + Message sMessage + RequestId string + Date time.Time +} +type AzureResponseError struct { + OdataError sOdataError `json:"odata.error"` + AzureError AzureError `json:"error"` +} + +func (ae AzureResponseError) Error() string { + return jsonutils.Marshal(ae).String() +} + +func (ae *AzureResponseError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { + if body != nil { + body.Unmarshal(ae) + } + if statusCode == 404 { + msg := "" + if body != nil { + msg = body.String() } - for { - if strings.HasSuffix(location, "Microsoft.DirectoryServices.User") || strings.HasSuffix(location, "Microsoft.DirectoryServices.Group") { - location = location + "?api-version=1.6" + return errors.Wrap(cloudprovider.ErrNotFound, msg) + } + if len(ae.OdataError.Code) > 0 || len(ae.AzureError.Code) > 0 { + return ae + } + return nil +} + +func _jsonRequest(client *autorest.Client, method, domain, path string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) { + url := fmt.Sprintf("%s/%s?%s", strings.TrimSuffix(domain, "/"), strings.TrimPrefix(path, "/"), params.Encode()) + req := httputils.NewJsonRequest(httputils.THttpMethod(method), url, body) + ae := AzureResponseError{} + cli := httputils.NewJsonClient(client) + header, body, err := cli.Send(context.TODO(), req, &ae, debug) + if err != nil { + return nil, err + } + location := func() string { + for _, k := range []string{"Azure-Asyncoperation", "Location"} { + link := header.Get(k) + if len(link) > 0 { + return link } - asyncReq, err := http.NewRequest("GET", location, nil) - if err != nil { - return nil, err - } - asyncResp, err := client.Do(asyncReq) - if err != nil { - return nil, err - } - defer asyncResp.Body.Close() - if asyncResp.StatusCode == 202 { - if _location := asyncResp.Header.Get("Location"); len(_location) > 0 { - location = _location + } + return "" + }() + if len(location) > 0 { + startTime := time.Now() + for time.Now().Sub(startTime) < time.Minute*30 { + req := httputils.NewJsonRequest(httputils.GET, location, nil) + lae := AzureResponseError{} + _header, _body, _err := cli.Send(context.TODO(), req, &lae, debug) + if _err != nil { + if utils.IsInStringArray(lae.AzureError.Code, []string{"OSProvisioningTimedOut", "OSProvisioningClientError", "OSProvisioningInternalError"}) { + return body, nil } - if time.Now().Sub(startTime) > timeout { - return nil, fmt.Errorf("Process request %s %s timeout", req.Method, req.URL.String()) - } - timeSleep := time.Second * 5 - if _timeSleep := asyncResp.Header.Get("Retry-After"); len(_timeSleep) > 0 { - if _time, err := strconv.Atoi(_timeSleep); err != nil { - timeSleep = time.Second * time.Duration(_time) - } - } - time.Sleep(timeSleep) - continue + return nil, errors.Wrapf(_err, "cli.Send(%s)", location) } - if asyncResp.ContentLength == 0 { - return nil, nil + if retryAfter := _header.Get("Retry-After"); len(retryAfter) > 0 { + sleepTime, _ := strconv.Atoi(retryAfter) + time.Sleep(time.Second * time.Duration(sleepTime)) } - data, err := ioutil.ReadAll(asyncResp.Body) - if err != nil { - return nil, err - } - asyncData, err := jsonutils.Parse(data) - if err != nil { - return nil, err - } - if len(asyncoperation) > 0 && asyncData.Contains("status") { - status, _ := asyncData.GetString("status") + if _body != nil && _body.Contains("status") { + status, _ := _body.GetString("status") switch status { case "InProgress": - log.Debugf("process %s %s InProgress", req.Method, req.URL.String()) - time.Sleep(time.Second * 5) - continue + log.Debugf("process %s %s InProgress", method, path) case "Succeeded": - log.Debugf("process %s %s Succeeded", req.Method, req.URL.String()) - output, err := asyncData.Get("properties", "output") - if err == nil { - return output, nil + log.Debugf("process %s %s Succeeded", method, path) + if _body.Contains("properties", "output") { + return _body.Get("properties", "output") } - return nil, nil + return body, nil case "Failed": - if asyncData.Contains("error") { - azureError := AzureError{} - if err := asyncData.Unmarshal(&azureError, "error"); err != nil { - log.Errorf("process %s %s error: %s", req.Method, req.URL.String(), asyncData.String()) - return nil, fmt.Errorf("%s", asyncData.String()) - } - switch azureError.Code { - // 忽略创建机器时初始化超时问题 - case "OSProvisioningTimedOut", "OSProvisioningClientError", "OSProvisioningInternalError": - // {"code":"OSProvisioningInternalError","message":"OS Provisioning failed for VM 'stress-testvm-azure-1' due to an internal error: [000004] cloud-init appears to be running, this is not expected, cannot continue."} - log.Debugf("ignore OSProvisioning error: %s", azureError) - return nil, nil - default: - log.Errorf("process %s %s error: %s", req.Method, req.URL.String(), azureError) - return nil, &azureError - } - } + return nil, fmt.Errorf("%s %s failed", method, path) default: - log.Errorf("Unknow status %s when process %s %s", status, req.Method, req.URL.String()) + log.Errorf("Unknow status %s when process %s %s", status, method, path) return nil, fmt.Errorf("Unknow status %s", status) } - return nil, fmt.Errorf("Create failed: %s", data) } - log.Debugf("process %s %s return: %s", req.Method, req.URL.String(), data) - return asyncData, nil + time.Sleep(time.Second * 10) } + return nil, fmt.Errorf("time out for wait task %s %s", method, path) } - return nil, nil + return body, nil } -func _jsonRequest(client *autorest.Client, method, domain, baseURL, body string, resourceType TAzureResource) (result jsonutils.JSONObject, err error) { - version := AZURE_API_VERSION - switch resourceType { - case GraphResource: - version = "1.6" - default: - for resourceType, _version := range DEFAULT_API_VERSION { - if strings.Index(strings.ToLower(baseURL), strings.ToLower(resourceType)) > 0 { - version = _version - } - } - } - url := fmt.Sprintf("%s%s?api-version=%s", domain, baseURL, version) - if strings.Index(baseURL, "?") > 0 { - if strings.Contains(baseURL, "api-version") { - url = domain + baseURL - } else { - url = fmt.Sprintf("%s%s&api-version=%s", domain, baseURL, version) - } - } - req := &http.Request{} - if len(body) != 0 { - req, err = http.NewRequest(method, url, strings.NewReader(body)) - if err != nil { - log.Errorf("Azure %s new request: %s body: %s error: %v", method, url, body, err) - return nil, err - } - } else { - req, err = http.NewRequest(method, url, nil) - if err != nil { - log.Errorf("Azure %s new request: %s error: %v", method, url, err) - return nil, err - } - } - req.Header.Add("Content-Type", "application/json; charset=utf-8") - resp, err := client.Do(req) - if err != nil { - log.Errorf("Azure %s request: %s \nbody: %s error: %v", req.Method, req.URL.String(), body, err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode == 404 { - data := []byte{} - if resp.ContentLength != 0 { - data, _ = ioutil.ReadAll(resp.Body) - } - log.Infof("failed find %s error: %s", url, string(data)) - return nil, cloudprovider.ErrNotFound - } - // 异步任务最多耗时半小时,否则以失败处理 - asyncData, err := waitForComplatetion(client, req, resp, time.Minute*30) - if err != nil { - return nil, err - } - if asyncData != nil { - return asyncData, nil - } - if resp.ContentLength == 0 { - return jsonutils.NewDict(), nil - } - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - _data := strings.Replace(string(data), "\r", "", -1) - return jsonutils.Parse([]byte(_data)) -} - -/*func (self *SAzureClient) UpdateAccount(envName, tenantId, appId, appKey, subscriptionId string) error { - if self.tenantId != tenantId || self.secret != secret || self.envName != envName { - if clientInfo, accountInfo := strings.Split(secret, "/"), strings.Split(tenantId, "/"); len(clientInfo) >= 2 && len(accountInfo) >= 1 { - self.clientId, self.clientSecret = clientInfo[0], strings.Join(clientInfo[1:], "/") - self.tenantId = accountInfo[0] - if len(accountInfo) == 2 { - self.subscriptionId = accountInfo[1] - } - err := self.fetchRegions() - if err != nil { - return err - } - return nil - } else { - return httperrors.NewUnauthorizedError("clientId、clientSecret or subscriptId input error") - } - } - return nil -}*/ - -func (self *SAzureClient) getDefaultSubscriptionId() (string, error) { - if len(self.subscriptionId) > 0 { - return self.subscriptionId, nil - } - if len(self.subscriptions) == 0 { - return "", errors.Errorf("no subscriptions found for this azure account") - } - return self.subscriptions[0].SubscriptionId, nil -} - -func (self *SAzureClient) getRegions() ([]SRegion, error) { - subscriptionId, err := self.getDefaultSubscriptionId() - if err != nil { - return nil, errors.Wrap(err, "getDefaultSubscriptionId") - } +func (self *SAzureClient) ListRegions() ([]SRegion, error) { regions := []SRegion{} - err = self.listSubscriptionResource(subscriptionId, "locations", ®ions) - if err != nil { - return nil, errors.Wrap(err, "listSubscriptionResource") - } - return regions, nil -} - -func (self *SAzureClient) fetchRegions() error { - regions, err := self.getRegions() - if err != nil { - return errors.Wrap(err, "getRegions") - } - self.iregions = make([]cloudprovider.ICloudRegion, len(regions)) - for i := 0; i < len(regions); i++ { - regions[i].client = self - regions[i].SubscriptionID = self.subscriptionId - self.iregions[i] = ®ions[i] - } - return nil -} - -func (self *SAzureClient) invalidateIBuckets() { - self.iBuckets = nil -} - -func (self *SAzureClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) { - if self.iBuckets == nil { - err := self.fetchBuckets() - if err != nil { - return nil, errors.Wrap(err, "fetchBuckets") - } - } - return self.iBuckets, nil -} - -func (client *SAzureClient) fetchBuckets() error { - accounts := []SStorageAccount{} - err := client.ListAll("Microsoft.Storage/storageAccounts", &accounts) - if err != nil { - return errors.Wrap(err, "client.ListAll") - } - buckets := make([]cloudprovider.ICloudBucket, 0) - for i := range accounts { - log.Debugf("%s %s %#v", jsonutils.Marshal(accounts[i]), accounts[i].Location, accounts[i]) - region, err := client.getIRegionByRegionId(accounts[i].Location) - if err != nil { - log.Errorf("fail to find region '%s'", accounts[i].Location) - continue - } - accounts[i].region = region.(*SRegion) - buckets = append(buckets, &accounts[i]) - } - client.iBuckets = buckets - return nil + err := self.list("locations", url.Values{}, ®ions) + return regions, err } func (self *SAzureClient) GetRegions() []SRegion { - regions := make([]SRegion, len(self.iregions)) - for i := 0; i < len(regions); i += 1 { - region := self.iregions[i].(*SRegion) - regions[i] = *region - } - return regions -} - -func (self *SAzureClient) fetchSubscriptions() error { - var err error - self.subscriptions, err = self.GetSubscriptions() - if err != nil { - return errors.Wrap(err, "GetSubscriptions") - } - return nil + return self.regions } func (self *SAzureClient) GetSubAccounts() (subAccounts []cloudprovider.SSubAccount, err error) { @@ -1083,46 +723,50 @@ func (self *SAzureClient) GetIamLoginUrl() string { } func (self *SAzureClient) GetIRegions() []cloudprovider.ICloudRegion { - return self.iregions + ret := []cloudprovider.ICloudRegion{} + for i := range self.regions { + ret = append(ret, &self.regions[i]) + } + return ret } func (self *SAzureClient) getDefaultRegion() (cloudprovider.ICloudRegion, error) { - if len(self.iregions) > 0 { - return self.iregions[0], nil + if len(self.regions) > 0 { + return &self.regions[0], nil } return nil, cloudprovider.ErrNotFound } func (self *SAzureClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) { - for i := 0; i < len(self.iregions); i += 1 { - if self.iregions[i].GetId() == id { - return self.iregions[i], nil + for i := 0; i < len(self.regions); i += 1 { + if self.regions[i].GetId() == id { + return &self.regions[i], nil } } return nil, cloudprovider.ErrNotFound } func (self *SAzureClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { - for i := 0; i < len(self.iregions); i += 1 { - if self.iregions[i].GetGlobalId() == id { - return self.iregions[i], nil + for i := 0; i < len(self.regions); i += 1 { + if self.regions[i].GetGlobalId() == id { + return &self.regions[i], nil } } return nil, cloudprovider.ErrNotFound } func (self *SAzureClient) GetRegion(regionId string) *SRegion { - for i := 0; i < len(self.iregions); i += 1 { - if self.iregions[i].GetId() == regionId { - return self.iregions[i].(*SRegion) + for i := 0; i < len(self.regions); i += 1 { + if self.regions[i].GetId() == regionId { + return &self.regions[i] } } return nil } func (self *SAzureClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) { - for i := 0; i < len(self.iregions); i += 1 { - ihost, err := self.iregions[i].GetIHostById(id) + for i := 0; i < len(self.regions); i += 1 { + ihost, err := self.regions[i].GetIHostById(id) if err == nil { return ihost, nil } else if err != cloudprovider.ErrNotFound { @@ -1133,8 +777,8 @@ func (self *SAzureClient) GetIHostById(id string) (cloudprovider.ICloudHost, err } func (self *SAzureClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { - for i := 0; i < len(self.iregions); i += 1 { - ihost, err := self.iregions[i].GetIVpcById(id) + for i := 0; i < len(self.regions); i += 1 { + ihost, err := self.regions[i].GetIVpcById(id) if err == nil { return ihost, nil } else if err != cloudprovider.ErrNotFound { @@ -1145,8 +789,8 @@ func (self *SAzureClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error } func (self *SAzureClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { - for i := 0; i < len(self.iregions); i += 1 { - ihost, err := self.iregions[i].GetIStorageById(id) + for i := 0; i < len(self.regions); i += 1 { + ihost, err := self.regions[i].GetIStorageById(id) if err == nil { return ihost, nil } else if err != cloudprovider.ErrNotFound { @@ -1156,18 +800,6 @@ func (self *SAzureClient) GetIStorageById(id string) (cloudprovider.ICloudStorag return nil, cloudprovider.ErrNotFound } -type SAccountBalance struct { - AvailableAmount float64 - AvailableCashAmount float64 - CreditAmount float64 - MybankCreditAmount float64 - Currency string -} - -func (self *SAzureClient) QueryAccountBalance() (*SAccountBalance, error) { - return nil, cloudprovider.ErrNotSupported -} - func getResourceGroup(id string) string { if info := strings.Split(id, "/resourceGroups/"); len(info) == 2 { if resourcegroupInfo := strings.Split(info[1], "/"); len(resourcegroupInfo) > 0 { @@ -1178,13 +810,23 @@ func getResourceGroup(id string) string { } func (self *SAzureClient) GetIProjects() ([]cloudprovider.ICloudProject, error) { - err := self.fetchResourceGroup() - if err != nil { - return nil, errors.Wrap(err, "fetchResourceGroup") + subscriptionId := self.subscriptionId + groups := map[string]*SResourceGroup{} + for _, sub := range self.subscriptions { + self.subscriptionId = sub.SubscriptionId + resourceGroups, err := self.ListResourceGroups() + if err != nil { + return nil, errors.Wrapf(err, "ListResourceGroups") + } + for i := range resourceGroups { + groups[strings.ToLower(resourceGroups[i].Name)] = &resourceGroups[i] + } } + self.subscriptionId = subscriptionId iprojects := []cloudprovider.ICloudProject{} - for i := 0; i < len(self.ressourceGroups); i++ { - iprojects = append(iprojects, &self.ressourceGroups[i]) + for _, group := range groups { + group.client = self + iprojects = append(iprojects, group) } return iprojects, nil } diff --git a/pkg/multicloud/azure/classic_disk.go b/pkg/multicloud/azure/classic_disk.go index 154c90812a..dd4d262d3c 100644 --- a/pkg/multicloud/azure/classic_disk.go +++ b/pkg/multicloud/azure/classic_disk.go @@ -16,13 +16,12 @@ package azure import ( "context" + "net/url" "strings" "time" - "github.com/Azure/azure-sdk-for-go/storage" - "yunion.io/x/jsonutils" - "yunion.io/x/log" + "yunion.io/x/pkg/errors" billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -30,10 +29,7 @@ import ( "yunion.io/x/onecloud/pkg/multicloud" ) -type SClassicDisk struct { - storage *SClassicStorage - multicloud.SDisk - +type ClassicProperties struct { DiskName string Caching string OperatingSystem string @@ -44,81 +40,17 @@ type SClassicDisk struct { CreatedTime string SourceImageName string VhdUri string - diskType string StorageAccount SubResource } -func (self *SRegion) GetStorageAccountsDisksWithSnapshots(storageaccounts ...*SStorageAccount) ([]SClassicDisk, []SClassicSnapshot, error) { - disks, snapshots := []SClassicDisk{}, []SClassicSnapshot{} - for i := 0; i < len(storageaccounts); i++ { - _disks, _snapshots, err := self.GetStorageAccountDisksWithSnapshots(storageaccounts[i]) - if err != nil { - return nil, nil, err - } - disks = append(disks, _disks...) - snapshots = append(snapshots, _snapshots...) - } - return disks, snapshots, nil -} +type SClassicDisk struct { + multicloud.SDisk + region *SRegion -func (self *SRegion) GetStorageAccountDisksWithSnapshots(storageaccount *SStorageAccount) ([]SClassicDisk, []SClassicSnapshot, error) { - disks, snapshots := []SClassicDisk{}, []SClassicSnapshot{} - containers, err := storageaccount.GetContainers() - if err != nil { - return nil, nil, err - } - for _, container := range containers { - if container.Name == "vhds" { - files, err := container.ListAllFiles(&storage.IncludeBlobDataset{Snapshots: true, Metadata: true}) - if err != nil { - log.Errorf("List storage %s container %s files error: %v", storageaccount.Name, container.Name, err) - return nil, nil, err - } - - for _, file := range files { - if strings.HasSuffix(file.Name, ".vhd") { - diskType := api.DISK_TYPE_DATA - if _diskType, ok := file.Metadata["microsoftazurecompute_disktype"]; ok && _diskType == "OSDisk" { - diskType = api.DISK_TYPE_SYS - } - diskName := file.Name - if _diskName, ok := file.Metadata["microsoftazurecompute_diskname"]; ok { - diskName = _diskName - } - if file.Snapshot.IsZero() { - disks = append(disks, SClassicDisk{ - DiskName: diskName, - diskType: diskType, - DiskSizeGB: int32(file.Properties.ContentLength / 1024 / 1024 / 1024), - diskSizeMB: int32(file.Properties.ContentLength / 1024 / 1024), - VhdUri: file.GetURL(), - }) - } else { - snapshots = append(snapshots, SClassicSnapshot{ - region: self, - Name: file.Snapshot.String(), - sizeMB: int32(file.Properties.ContentLength / 1024 / 1024), - diskID: file.GetURL(), - diskName: diskName, - }) - } - } - } - } - } - return disks, snapshots, nil -} - -func (self *SRegion) GetClassicDisks() ([]SClassicDisk, error) { - storageaccounts, err := self.GetClassicStorageAccounts() - if err != nil { - return nil, err - } - disks, _, err := self.GetStorageAccountsDisksWithSnapshots(storageaccounts...) - if err != nil { - return nil, err - } - return disks, nil + Id string + Name string + Type string + Properties ClassicProperties } func (self *SClassicDisk) GetMetadata() *jsonutils.JSONDict { @@ -131,6 +63,15 @@ func (self *SClassicDisk) CreateISnapshot(ctx context.Context, name, desc string return nil, cloudprovider.ErrNotSupported } +func (self *SRegion) GetClassicDisk(id string) (*SClassicDisk, error) { + disk := &SClassicDisk{region: self} + err := self.get(id, url.Values{}, disk) + if err != nil { + return nil, errors.Wrapf(err, "get(%s)", id) + } + return disk, nil +} + func (self *SClassicDisk) Delete(ctx context.Context) error { return cloudprovider.ErrNotImplemented } @@ -164,10 +105,10 @@ func (self *SClassicDisk) GetDiskFormat() string { } func (self *SClassicDisk) GetDiskSizeMB() int { - if self.DiskSizeGB > 0 { - return int(self.DiskSizeGB * 1024) + if self.Properties.DiskSizeGB > 0 { + return int(self.Properties.DiskSizeGB * 1024) } - return int(self.diskSizeMB) + return 0 } func (self *SClassicDisk) GetIsAutoDelete() bool { @@ -179,7 +120,7 @@ func (self *SClassicDisk) GetTemplateId() string { } func (self *SClassicDisk) GetDiskType() string { - return self.diskType + return self.Properties.OperatingSystem } func (self *SClassicDisk) GetCreatedAt() time.Time { @@ -191,32 +132,32 @@ func (self *SClassicDisk) GetExpiredAt() time.Time { } func (self *SClassicDisk) GetGlobalId() string { - return self.VhdUri + return strings.ToLower(self.Id) } func (self *SClassicDisk) GetId() string { - return self.VhdUri -} - -func (self *SClassicDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { - return nil, cloudprovider.ErrNotSupported -} - -func (region *SRegion) GetClassicSnapShots(diskId string) ([]SClassicSnapshot, error) { - result := []SClassicSnapshot{} - return result, nil + return self.GetGlobalId() } func (self *SClassicDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { - return nil, cloudprovider.ErrNotSupported + return []cloudprovider.ICloudSnapshot{}, nil } func (self *SClassicDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { - return self.storage, nil + storage := struct { + Properties struct { + AccountType string + } + }{} + err := self.region.get(self.Properties.StorageAccount.ID, url.Values{}, &storage) + if err != nil { + return nil, errors.Wrapf(err, "get(%s)", self.Properties.StorageAccount.ID) + } + return &SClassicStorage{region: self.region, AccountType: storage.Properties.AccountType}, nil } func (self *SClassicDisk) GetName() string { - return self.DiskName + return self.Properties.DiskName } func (self *SClassicDisk) GetStatus() string { diff --git a/pkg/multicloud/azure/classic_eip.go b/pkg/multicloud/azure/classic_eip.go index 56929f655a..9173ef2dc8 100644 --- a/pkg/multicloud/azure/classic_eip.go +++ b/pkg/multicloud/azure/classic_eip.go @@ -15,6 +15,7 @@ package azure import ( + "net/url" "strings" "time" @@ -134,7 +135,7 @@ func (self *SClassicEipAddress) IsEmulated() bool { func (region *SRegion) GetClassicEip(eipId string) (*SClassicEipAddress, error) { eip := SClassicEipAddress{region: region} - return &eip, region.client.Get(eipId, []string{}, &eip) + return &eip, region.get(eipId, url.Values{}, &eip) } func (self *SClassicEipAddress) Refresh() error { @@ -147,7 +148,7 @@ func (self *SClassicEipAddress) Refresh() error { func (region *SRegion) GetClassicEips() ([]SClassicEipAddress, error) { eips := []SClassicEipAddress{} - err := region.client.ListAll("Microsoft.ClassicNetwork/reservedIps", &eips) + err := region.list("Microsoft.ClassicNetwork/reservedIps", url.Values{}, &eips) if err != nil { return nil, err } diff --git a/pkg/multicloud/azure/classic_host.go b/pkg/multicloud/azure/classic_host.go index 171f1936f6..ad9a5769cd 100644 --- a/pkg/multicloud/azure/classic_host.go +++ b/pkg/multicloud/azure/classic_host.go @@ -43,7 +43,7 @@ func (self *SClassicHost) GetName() string { } func (self *SClassicHost) GetGlobalId() string { - return fmt.Sprintf("%s/%s-classic", self.zone.region.GetGlobalId(), self.zone.region.SubscriptionID) + return fmt.Sprintf("%s/%s-classic", self.zone.region.GetGlobalId(), self.zone.region.client.subscriptionId) } func (self *SClassicHost) IsEmulated() bool { @@ -101,19 +101,7 @@ func (self *SClassicHost) GetHostType() string { } func (self *SClassicHost) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { - storageaccount, err := self.zone.region.GetStorageAccountDetail(id) - if err != nil { - return nil, err - } - storage := &SClassicStorage{ - zone: self.zone, - ID: storageaccount.ID, - Name: storageaccount.Name, - Type: storageaccount.Type, - Location: storageaccount.Location, - Properties: storageaccount.Properties.ClassicStorageProperties, - } - return storage, nil + return self.zone.GetIStorageById(id) } func (self *SClassicHost) GetSysInfo() jsonutils.JSONObject { @@ -123,23 +111,7 @@ func (self *SClassicHost) GetSysInfo() jsonutils.JSONObject { } func (self *SClassicHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { - storageaccounts, err := self.zone.region.GetClassicStorageAccounts() - if err != nil { - return nil, err - } - istorages := make([]cloudprovider.ICloudStorage, len(storageaccounts)) - for i := 0; i < len(storageaccounts); i++ { - storage := SClassicStorage{ - zone: self.zone, - ID: storageaccounts[i].ID, - Name: storageaccounts[i].Name, - Type: storageaccounts[i].Type, - Location: storageaccounts[i].Location, - Properties: storageaccounts[i].Properties.ClassicStorageProperties, - } - istorages[i] = &storage - } - return istorages, nil + return self.zone.GetIClassicStorages(), nil } func (self *SClassicHost) GetIVMById(instanceId string) (cloudprovider.ICloudVM, error) { diff --git a/pkg/multicloud/azure/classic_instance.go b/pkg/multicloud/azure/classic_instance.go index 6f21e741e9..4bbe5bbb65 100644 --- a/pkg/multicloud/azure/classic_instance.go +++ b/pkg/multicloud/azure/classic_instance.go @@ -17,11 +17,13 @@ package azure import ( "context" "fmt" + "net/url" "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/osprofile" billing_api "yunion.io/x/onecloud/pkg/apis/billing" @@ -165,73 +167,36 @@ func (self *SClassicInstance) GetHypervisor() string { return api.HYPERVISOR_AZURE } -func (self *SClassicInstance) IsEmulated() bool { - return false -} - func (self *SClassicInstance) GetInstanceType() string { return self.Properties.HardwareProfile.Size } func (self *SRegion) GetClassicInstances() ([]SClassicInstance, error) { - result := []SClassicInstance{} instances := []SClassicInstance{} - err := self.client.ListAll("Microsoft.ClassicCompute/virtualMachines", &instances) + err := self.list("Microsoft.ClassicCompute/virtualMachines", url.Values{}, &instances) if err != nil { return nil, err } - for i := 0; i < len(instances); i++ { - if instances[i].Location == self.Name { - result = append(result, instances[i]) - } - } - return result, nil + return instances, nil } func (self *SRegion) GetClassicInstance(instanceId string) (*SClassicInstance, error) { instance := SClassicInstance{} - return &instance, self.client.Get(instanceId, []string{"$expand=instanceView"}, &instance) + params := url.Values{} + params.Add("$expand", "instanceView") + return &instance, self.get(instanceId, params, &instance) } -type ClassicInstanceDiskProperties struct { - DiskName string - Caching string - OperatingSystem string - IoType string - DiskSize int32 - SourceImageName string - VhdUri string -} - -type ClassicInstanceDisk struct { - Properties ClassicInstanceDiskProperties - ID string - Name string - Type string -} - -func (self *SClassicInstance) getDisks() ([]SClassicDisk, error) { - disks := []SClassicDisk{} - body, err := self.host.zone.region.client.jsonRequest("GET", fmt.Sprintf("%s/disks", self.ID), "") +func (self *SRegion) GetClassicInstanceDisks(instanceId string) ([]SClassicDisk, error) { + result := struct { + Value []SClassicDisk + }{} + resource := fmt.Sprintf("%s/disks", instanceId) + err := self.get(resource, url.Values{}, &result) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "list") } - _disks, err := body.GetArray("value") - if err != nil { - return nil, err - } - for i := 0; i < len(_disks); i++ { - disk := SClassicDisk{} - err = _disks[i].Unmarshal(&disk, "properties") - if err != nil { - return nil, err - } - storage := SClassicStorage{zone: self.host.zone, Name: disk.StorageAccount.Name, ID: disk.StorageAccount.ID} - disk.DiskSizeGB = disk.DiskSize - disk.storage = &storage - disks = append(disks, disk) - } - return disks, nil + return result.Value, nil } func (self *SClassicInstance) getNics() ([]SClassicInstanceNic, error) { @@ -350,33 +315,25 @@ func (self *SClassicInstance) DeleteVM(ctx context.Context) error { return err } if self.Properties.NetworkProfile.NetworkSecurityGroup != nil { - self.host.zone.region.client.Delete(self.Properties.NetworkProfile.NetworkSecurityGroup.ID) + self.host.zone.region.del(self.Properties.NetworkProfile.NetworkSecurityGroup.ID) } if self.Properties.DomainName != nil { - self.host.zone.region.client.Delete(self.Properties.DomainName.ID) - } - return nil -} - -func (self *SClassicInstance) fetchDisks() error { - disks, err := self.getDisks() - if err != nil { - return err - } - self.idisks = make([]cloudprovider.ICloudDisk, len(disks)) - for i := 0; i < len(disks); i++ { - self.idisks[i] = &disks[i] + self.host.zone.region.del(self.Properties.DomainName.ID) } return nil } func (self *SClassicInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { - if self.idisks == nil { - if err := self.fetchDisks(); err != nil { - return nil, err - } + disks, err := self.host.zone.region.GetClassicInstanceDisks(self.ID) + if err != nil { + return nil, errors.Wrapf(err, "GetClassicInstanceDisks") } - return self.idisks, nil + ret := []cloudprovider.ICloudDisk{} + for i := range disks { + disks[i].region = self.host.zone.region + ret = append(ret, &disks[i]) + } + return ret, nil } func (self *SClassicInstance) GetOSType() string { @@ -448,8 +405,8 @@ func (self *SClassicInstance) StartVM(ctx context.Context) error { return cloudprovider.WaitStatus(self, api.VM_RUNNING, 10*time.Second, 300*time.Second) } -func (self *SClassicInstance) StopVM(ctx context.Context, isForce bool) error { - err := self.host.zone.region.StopClassicVM(self.ID, isForce) +func (self *SClassicInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := self.host.zone.region.StopClassicVM(self.ID, opts.IsForce) if err != nil { return err } @@ -457,7 +414,7 @@ func (self *SClassicInstance) StopVM(ctx context.Context, isForce bool) error { } func (self *SRegion) StopClassicVM(instanceId string, isForce bool) error { - _, err := self.client.PerformAction(instanceId, "shutdown", "") + _, err := self.perform(instanceId, "shutdown", nil) return err } @@ -511,7 +468,7 @@ func (self *SClassicInstance) AssignSecurityGroup(secgroupId string) error { if self.Properties.NetworkProfile.NetworkSecurityGroup.ID == secgroupId { return nil } - self.host.zone.region.client.Delete(fmt.Sprintf("%s/associatedNetworkSecurityGroups/%s", self.ID, self.Properties.NetworkProfile.NetworkSecurityGroup.Name)) + self.host.zone.region.del(fmt.Sprintf("%s/associatedNetworkSecurityGroups/%s", self.ID, self.Properties.NetworkProfile.NetworkSecurityGroup.Name)) } secgroup, err := self.host.zone.region.GetClassicSecurityGroupDetails(secgroupId) @@ -528,7 +485,7 @@ func (self *SClassicInstance) AssignSecurityGroup(secgroupId string) error { }, }, } - return self.host.zone.region.client.Update(jsonutils.Marshal(data), nil) + return self.host.zone.region.update(jsonutils.Marshal(data), nil) } func (self *SClassicInstance) GetBillingType() string { diff --git a/pkg/multicloud/azure/classic_network.go b/pkg/multicloud/azure/classic_network.go index b67e7013c7..efacf66c94 100644 --- a/pkg/multicloud/azure/classic_network.go +++ b/pkg/multicloud/azure/classic_network.go @@ -67,7 +67,7 @@ func (self *SClassicNetwork) Delete() error { } subnets = append(subnets, network) } - return self.wire.vpc.region.client.Update(jsonutils.Marshal(vpc), self.wire.vpc) + return self.wire.vpc.region.update(jsonutils.Marshal(vpc), self.wire.vpc) } func (self *SClassicNetwork) GetGateway() string { diff --git a/pkg/multicloud/azure/classic_secruitygroup.go b/pkg/multicloud/azure/classic_secruitygroup.go index 7c0a3fdd97..6e53c6f325 100644 --- a/pkg/multicloud/azure/classic_secruitygroup.go +++ b/pkg/multicloud/azure/classic_secruitygroup.go @@ -17,6 +17,7 @@ package azure import ( "fmt" "net" + "net/url" "strconv" "strings" "unicode" @@ -186,12 +187,12 @@ func (region *SRegion) CreateClassicSecurityGroup(name string) (*SClassicSecurit Type: "Microsoft.ClassicNetwork/networkSecurityGroups", Location: region.Name, } - return &secgroup, region.client.Create(jsonutils.Marshal(secgroup), &secgroup) + return &secgroup, region.create("", jsonutils.Marshal(secgroup), &secgroup) } func (region *SRegion) GetClassicSecurityGroups(name string) ([]SClassicSecurityGroup, error) { secgroups := []SClassicSecurityGroup{} - err := region.client.ListAll("Microsoft.ClassicNetwork/networkSecurityGroups", &secgroups) + err := region.client.list("Microsoft.ClassicNetwork/networkSecurityGroups", url.Values{}, &secgroups) if err != nil { return nil, err } @@ -206,11 +207,11 @@ func (region *SRegion) GetClassicSecurityGroups(name string) ([]SClassicSecurity func (region *SRegion) GetClassicSecurityGroupDetails(secgroupId string) (*SClassicSecurityGroup, error) { secgroup := SClassicSecurityGroup{region: region} - return &secgroup, region.client.Get(secgroupId, []string{}, &secgroup) + return &secgroup, region.get(secgroupId, url.Values{}, &secgroup) } func (region *SRegion) deleteClassicSecurityGroup(secgroupId string) error { - return region.client.Delete(secgroupId) + return region.del(secgroupId) } func (self *SClassicSecurityGroup) Delete() error { @@ -289,16 +290,19 @@ func convertClassicSecurityGroupRules(rule cloudprovider.SecurityRule) ([]SClass func (self *SRegion) getClassicSecurityGroupRules(secgroupId string) ([]SClassicSecurityGroupRule, error) { rules := []SClassicSecurityGroupRule{} - result, err := self.client.jsonRequest("GET", fmt.Sprintf("%s/securityRules?api-version=2015-06-01", secgroupId), "") + params := url.Values{} + params.Set("api-version", "2015-06-01") + resource := fmt.Sprintf("%s/securityRules", secgroupId) + err := self.client.list(resource, params, &rules) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "list") } - return rules, result.Unmarshal(&rules, "value") + return rules, nil } func (self *SRegion) addClassicSecgroupRule(secgroupId string, rule SClassicSecurityGroupRule) error { - url := fmt.Sprintf("%s/securityRules/%s?api-version=2015-06-01", secgroupId, rule.Name) - _, err := self.client.jsonRequest("PUT", url, jsonutils.Marshal(rule).String()) + resource := fmt.Sprintf("%s/securityRules/%s", secgroupId, rule.Name) + _, err := self.put(resource, jsonutils.Marshal(rule)) return err } @@ -308,7 +312,7 @@ func (self *SClassicSecurityGroup) GetProjectId() string { func (self *SClassicSecurityGroup) SyncRules(common, inAdds, outAdds, inDels, outDels []cloudprovider.SecurityRule) error { for _, r := range append(inDels, outDels...) { - err := self.region.client.Delete(r.ExternalId) + err := self.region.del(r.ExternalId) if err != nil { return errors.Wrapf(err, "Delete(%s)", r.ExternalId) } diff --git a/pkg/multicloud/azure/classic_storage.go b/pkg/multicloud/azure/classic_storage.go index 1206043b5c..32e8cfa970 100644 --- a/pkg/multicloud/azure/classic_storage.go +++ b/pkg/multicloud/azure/classic_storage.go @@ -15,50 +15,40 @@ package azure import ( + "fmt" "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/multicloud" ) -type ClassicStorageProperties struct { - ProvisioningState string - Status string - Endpoints []string - AccountType string `json:"accountType"` - GeoPrimaryRegion string - StatusOfPrimaryRegion string - GeoSecondaryRegion string - StatusOfSecondaryRegion string - //CreationTime time.Time -} +const ( + STORAGE_LRS = "Standard-LRS" + STORAGE_GRS = "Standard-GRS" +) type SClassicStorage struct { - zone *SZone + multicloud.SResourceBase + region *SRegion - Properties ClassicStorageProperties - Name string - ID string - Type string - Location string -} - -func (self *SClassicStorage) GetMetadata() *jsonutils.JSONDict { - return nil + AccountType string } func (self *SClassicStorage) GetId() string { - return self.ID + zone := self.region.getZone() + return fmt.Sprintf("%s-%s-classic", zone.GetGlobalId(), self.AccountType) } func (self *SClassicStorage) GetName() string { - return self.Name + return self.AccountType } func (self *SClassicStorage) GetGlobalId() string { - return strings.ToLower(self.ID) + return self.GetId() } func (self *SClassicStorage) IsEmulated() bool { @@ -66,7 +56,7 @@ func (self *SClassicStorage) IsEmulated() bool { } func (self *SClassicStorage) GetIZone() cloudprovider.ICloudZone { - return self.zone + return self.region.getZone() } func (self *SClassicStorage) GetEnabled() bool { @@ -86,38 +76,23 @@ func (self *SClassicStorage) CreateIDisk(conf *cloudprovider.DiskCreateConfig) ( } func (self *SClassicStorage) GetIDiskById(diskId string) (cloudprovider.ICloudDisk, error) { - disks, err := self.GetIDisks() + disk, err := self.region.GetClassicDisk(diskId) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "GetDisk(%s)", diskId) } - for i := 0; i < len(disks); i++ { - if disks[i].GetId() == diskId { - return disks[i], nil - } - } - return nil, cloudprovider.ErrNotFound + return disk, nil } func (self *SClassicStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { - storageaccount, err := self.zone.region.GetStorageAccountDetail(self.ID) - disks, _, err := self.zone.region.GetStorageAccountDisksWithSnapshots(storageaccount) - if err != nil { - return nil, err - } - idisks := make([]cloudprovider.ICloudDisk, len(disks)) - for i := 0; i < len(disks); i++ { - disks[i].storage = self - idisks[i] = &disks[i] - } - return idisks, nil + return []cloudprovider.ICloudDisk{}, nil } func (self *SClassicStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { - return self.zone.region.getStoragecache() + return self.region.getStoragecache() } func (self *SClassicStorage) GetMediumType() string { - if strings.Contains(self.Properties.AccountType, "Premium") { + if self.AccountType == STORAGE_LRS { return api.DISK_TYPE_SSD } return api.DISK_TYPE_ROTATE @@ -133,7 +108,7 @@ func (self *SClassicStorage) GetStatus() string { } func (self *SClassicStorage) GetStorageType() string { - return strings.ToLower(self.Properties.AccountType) + return strings.ToLower(self.AccountType) } func (self *SClassicStorage) Refresh() error { diff --git a/pkg/multicloud/azure/classic_vpc.go b/pkg/multicloud/azure/classic_vpc.go index 91ab3a638f..0677955bcb 100644 --- a/pkg/multicloud/azure/classic_vpc.go +++ b/pkg/multicloud/azure/classic_vpc.go @@ -18,7 +18,7 @@ import ( "fmt" "strings" - "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -48,9 +48,6 @@ type SClassicVpc struct { region *SRegion - iwires []cloudprovider.ICloudWire - secgroups []cloudprovider.ICloudSecurityGroup - ID string Name string Type string @@ -58,10 +55,6 @@ type SClassicVpc struct { Properties ClassicVpcProperties } -func (self *SClassicVpc) GetMetadata() *jsonutils.JSONDict { - return nil -} - func (self *SClassicVpc) GetId() string { return self.ID } @@ -79,8 +72,7 @@ func (self *SClassicVpc) IsEmulated() bool { } func (self *SClassicVpc) GetIsDefault() bool { - // TODO - return true + return false } func (self *SClassicVpc) GetCidrBlock() string { @@ -91,68 +83,11 @@ func (self *SClassicVpc) GetCidrBlock() string { } func (self *SClassicVpc) Delete() error { - return self.region.client.Delete(self.ID) -} - -func (self *SClassicVpc) getWire() *SClassicWire { - if self.iwires == nil { - self.fetchWires() - } - return self.iwires[0].(*SClassicWire) -} - -func (region *SRegion) GetClassicVpc(vpcId string) (*SClassicVpc, error) { - vpc := SClassicVpc{region: region} - return &vpc, region.client.Get(vpcId, []string{}, &vpc) -} - -func (self *SClassicVpc) fetchNetworks() error { - vpc, err := self.region.GetClassicVpc(self.ID) - if err != nil { - return err - } - for i := 0; i < len(vpc.Properties.Subnets); i++ { - network := vpc.Properties.Subnets[i] - network.id = fmt.Sprintf("%s/%s", vpc.ID, network.Name) - wire := self.getWire() - network.wire = wire - wire.addNetwork(&network) - } - return nil -} - -func (self *SClassicVpc) getClassicSecurityGroups() ([]SClassicSecurityGroup, error) { - securityGroups, err := self.region.GetClassicSecurityGroups("") - if err != nil { - return nil, err - } - for i := 0; i < len(securityGroups); i++ { - securityGroups[i].vpc = self - securityGroups[i].region = self.region - } - return securityGroups, nil -} - -func (self *SClassicVpc) fetchSecurityGroups() error { - self.secgroups = make([]cloudprovider.ICloudSecurityGroup, 0) - secgrps, err := self.getClassicSecurityGroups() - if err != nil { - return err - } - for i := 0; i < len(secgrps); i++ { - self.secgroups = append(self.secgroups, &secgrps[i]) - } - return nil + return self.region.del(self.ID) } func (self *SClassicVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { - if self.secgroups == nil { - err := self.fetchSecurityGroups() - if err != nil { - return nil, err - } - } - return self.secgroups, nil + return []cloudprovider.ICloudSecurityGroup{}, nil } func (self *SClassicVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { @@ -164,41 +99,20 @@ func (self *SClassicVpc) GetIRouteTableById(routeTableId string) (cloudprovider. return nil, cloudprovider.ErrNotSupported } -func (self *SClassicVpc) fetchWires() error { - networks := make([]cloudprovider.ICloudNetwork, len(self.Properties.Subnets)) - wire := SClassicWire{zone: self.region.izones[0].(*SZone), vpc: self} - for i := 0; i < len(self.Properties.Subnets); i++ { - network := self.Properties.Subnets[i] - network.id = fmt.Sprintf("%s/%s", self.ID, self.Properties.Subnets[i].Name) - network.wire = &wire - networks[i] = &network - } - wire.inetworks = networks - self.iwires = []cloudprovider.ICloudWire{&wire} - return nil +func (self *SClassicVpc) getWire() *SClassicWire { + return &SClassicWire{vpc: self, zone: self.region.getZone()} } func (self *SClassicVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { - if self.iwires == nil { - if err := self.fetchNetworks(); err != nil { - return nil, err - } + wire := self.getWire() + if wire.GetGlobalId() != wireId { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, wireId) } - for i := 0; i < len(self.iwires); i++ { - if self.iwires[i].GetGlobalId() == wireId { - return self.iwires[i], nil - } - } - return nil, cloudprovider.ErrNotFound + return wire, nil } func (self *SClassicVpc) GetIWires() ([]cloudprovider.ICloudWire, error) { - if self.iwires == nil { - if err := self.fetchWires(); err != nil { - return nil, err - } - } - return self.iwires, nil + return []cloudprovider.ICloudWire{self.getWire()}, nil } func (self *SClassicVpc) GetRegion() cloudprovider.ICloudRegion { @@ -209,21 +123,6 @@ func (self *SClassicVpc) GetStatus() string { return api.VPC_STATUS_AVAILABLE } -func (self *SClassicVpc) Refresh() error { - vpc, err := self.region.GetClassicVpc(self.ID) - if err != nil { - return err - } - return jsonutils.Update(self, vpc) -} - -func (self *SClassicVpc) addWire(wire *SClassicWire) { - if self.iwires == nil { - self.iwires = make([]cloudprovider.ICloudWire, 0) - } - self.iwires = append(self.iwires, wire) -} - func (self *SClassicVpc) GetNetworks() []SClassicNetwork { for i := 0; i < len(self.Properties.Subnets); i++ { self.Properties.Subnets[i].id = fmt.Sprintf("%s/%s", self.ID, self.Properties.Subnets[i].Name) diff --git a/pkg/multicloud/azure/classic_wire.go b/pkg/multicloud/azure/classic_wire.go index b6bb2beee1..212859d655 100644 --- a/pkg/multicloud/azure/classic_wire.go +++ b/pkg/multicloud/azure/classic_wire.go @@ -18,22 +18,20 @@ import ( "fmt" "strings" - "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SClassicWire struct { + multicloud.SResourceBase + zone *SZone vpc *SClassicVpc inetworks []cloudprovider.ICloudNetwork } -func (self *SClassicWire) GetMetadata() *jsonutils.JSONDict { - return nil -} - func (self *SClassicWire) GetId() string { return fmt.Sprintf("%s/%s/%s-classic", self.zone.region.GetGlobalId(), self.zone.region.client.subscriptionId, self.vpc.GetName()) } @@ -54,34 +52,8 @@ func (self *SClassicWire) GetStatus() string { return "available" } -func (self *SClassicWire) Refresh() error { - return nil -} - -func (self *SClassicWire) addNetwork(network *SClassicNetwork) { - if self.inetworks == nil { - self.inetworks = make([]cloudprovider.ICloudNetwork, 0) - } - find := false - for i := 0; i < len(self.inetworks); i += 1 { - if self.inetworks[i].GetName() == network.Name { - find = true - break - } - } - if !find { - self.inetworks = append(self.inetworks, network) - } -} - func (self *SClassicWire) CreateINetwork(opts *cloudprovider.SNetworkCreateOptions) (cloudprovider.ICloudNetwork, error) { return nil, cloudprovider.ErrNotImplemented - // if network, err := self.zone.region.createNetwork(self.vpc, name, cidr, desc); err != nil { - // return nil, err - // } else { - // network.wire = self - // return network, nil - // } } func (self *SClassicWire) GetBandwidth() int { @@ -102,10 +74,13 @@ func (self *SClassicWire) GetINetworkById(netid string) (cloudprovider.ICloudNet } func (self *SClassicWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) { - if err := self.vpc.fetchNetworks(); err != nil { - return nil, err + networks := self.vpc.GetNetworks() + ret := []cloudprovider.ICloudNetwork{} + for i := range networks { + networks[i].wire = self + ret = append(ret, &networks[i]) } - return self.inetworks, nil + return ret, nil } func (self *SClassicWire) GetIVpc() cloudprovider.ICloudVpc { diff --git a/pkg/multicloud/azure/cloudgroup.go b/pkg/multicloud/azure/cloudgroup.go index 1892026dac..be595f07ed 100644 --- a/pkg/multicloud/azure/cloudgroup.go +++ b/pkg/multicloud/azure/cloudgroup.go @@ -122,7 +122,7 @@ func (group *SCloudgroup) DetachSystemPolicy(policyId string) error { return errors.Wrapf(err, "GetRule(%s)", assignment.Properties.RoleDefinitionId) } if role.Properties.RoleName == policyId { - return group.client.Delete(assignment.Id) + return group.client.gdel(assignment.Id) } } return nil @@ -142,7 +142,7 @@ func (self *SAzureClient) GetCloudgroups(name string) ([]SCloudgroup, error) { if len(name) > 0 { params.Set("$filter", fmt.Sprintf("displayName eq '%s'", name)) } - err := self.ListGraphResource("groups", params, &groups) + err := self.glist("groups", params, &groups) if err != nil { return nil, err } @@ -180,7 +180,7 @@ func (self *SAzureClient) GetICloudgroupByName(name string) (cloudprovider.IClou func (self *SAzureClient) ListGroupMemebers(id string) ([]SClouduser, error) { users := []SClouduser{} resource := fmt.Sprintf("groups/%s/members", id) - err := self.ListGraphResource(resource, nil, &users) + err := self.glist(resource, nil, &users) if err != nil { return nil, err } @@ -188,7 +188,7 @@ func (self *SAzureClient) ListGroupMemebers(id string) ([]SClouduser, error) { } func (self *SAzureClient) DeleteGroup(id string) error { - return self.DeleteGraph(fmt.Sprintf("%s/groups/%s?api-version=1.6", self.tenantId, id)) + return self.gdel(fmt.Sprintf("%s/groups/%s", self.tenantId, id)) } func (self *SAzureClient) CreateGroup(name, desc string) (*SCloudgroup, error) { @@ -202,7 +202,7 @@ func (self *SAzureClient) CreateGroup(name, desc string) (*SCloudgroup, error) { params["Description"] = desc } group := SCloudgroup{client: self} - err := self.CreateGraphResource("groups", jsonutils.Marshal(params), &group) + err := self.gcreate("groups", jsonutils.Marshal(params), &group) if err != nil { return nil, errors.Wrap(err, "Create") } @@ -220,7 +220,7 @@ func (self *SAzureClient) RemoveGroupUser(id, userName string) error { if len(users) > 1 { return cloudprovider.ErrDuplicateId } - return self.DeleteGraph(fmt.Sprintf("%s/groups/%s/$links/members/%s", self.tenantId, id, users[0].ObjectId)) + return self.gdel(fmt.Sprintf("%s/groups/%s/$links/members/%s", self.tenantId, id, users[0].ObjectId)) } func (self *SAzureClient) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { @@ -247,7 +247,7 @@ func (self *SAzureClient) AddGroupUser(id, userName string) error { params := map[string]string{ "url": fmt.Sprintf("%s%s/directoryObjects/%s", self.domain, self.tenantId, users[0].ObjectId), } - err = self.CreateGraphResource(resource, jsonutils.Marshal(params), nil) + err = self.gcreate(resource, jsonutils.Marshal(params), nil) if err != nil && !strings.Contains(err.Error(), "One or more added object references already exist for the following modified properties") { return err } diff --git a/pkg/multicloud/azure/cloudpolicy.go b/pkg/multicloud/azure/cloudpolicy.go index 25446a2fde..b933453806 100644 --- a/pkg/multicloud/azure/cloudpolicy.go +++ b/pkg/multicloud/azure/cloudpolicy.go @@ -74,11 +74,6 @@ func (role *SCloudpolicy) Delete() error { func (cli *SAzureClient) GetRoles(name, policyType string) ([]SCloudpolicy, error) { ret := []SCloudpolicy{} - subscriptionId, err := cli.getDefaultSubscriptionId() - if err != nil { - return nil, errors.Wrap(err, "getDefaultSubscriptionId") - } - params := url.Values{} filter := []string{} if len(name) > 0 { filter = append(filter, fmt.Sprintf("roleName eq '%s'", name)) @@ -86,16 +81,14 @@ func (cli *SAzureClient) GetRoles(name, policyType string) ([]SCloudpolicy, erro if len(policyType) > 0 { filter = append(filter, fmt.Sprintf("Type eq '%s'", policyType)) } + params := url.Values{} if len(filter) > 0 { params.Set("$filter", strings.Join(filter, " and ")) } - resource := "providers/Microsoft.Authorization/roleDefinitions" - if len(params) > 0 { - resource = fmt.Sprintf("%s?%s", resource, params.Encode()) - } - err = cli.listSubscriptionResource(subscriptionId, resource, &ret) + resource := "Microsoft.Authorization/roleDefinitions" + err := cli.list(resource, params, &ret) if err != nil { - return nil, errors.Wrap(err, "listSubscriptionResource") + return nil, errors.Wrap(err, "list") } return ret, nil } @@ -150,7 +143,7 @@ func (cli *SAzureClient) AssignPolicy(objectId, roleName, subscriptionId string) } for _, subscriptionId := range subscriptionIds { resource := fmt.Sprintf("subscriptions/%s/providers/Microsoft.Authorization/roleAssignments/%s", subscriptionId, stringutils.UUID4()) - err = cli.Put(resource, jsonutils.Marshal(body)) + _, err = cli.put(resource, jsonutils.Marshal(body)) if err != nil { return errors.Wrapf(err, "AssignPolicy %s for subscription %s", roleName, subscriptionId) } @@ -174,28 +167,21 @@ type SAssignment struct { func (cli *SAzureClient) GetAssignments(objectId string) ([]SAssignment, error) { ret := []SAssignment{} - subscriptionId, err := cli.getDefaultSubscriptionId() - if err != nil { - return nil, errors.Wrap(err, "getDefaultSubscriptionId") - } params := url.Values{} if len(objectId) > 0 { params.Set("$filter", fmt.Sprintf("principalId eq '%s'", objectId)) } - resource := "providers/Microsoft.Authorization/roleAssignments" - if len(params) > 0 { - resource = fmt.Sprintf("%s?%s", resource, params.Encode()) - } - err = cli.listSubscriptionResource(subscriptionId, resource, &ret) + resource := "Microsoft.Authorization/roleAssignments" + err := cli.list(resource, params, &ret) if err != nil { - return nil, errors.Wrap(err, "listSubscriptionResource") + return nil, errors.Wrap(err, "list") } return ret, nil } func (cli *SAzureClient) GetRole(roleId string) (*SCloudpolicy, error) { role := &SCloudpolicy{} - err := cli.Get(roleId, nil, role) + err := cli.get(roleId, nil, role) if err != nil { return nil, errors.Wrapf(err, "GetRole(%s)", roleId) } diff --git a/pkg/multicloud/azure/clouduser.go b/pkg/multicloud/azure/clouduser.go index 638f2c125e..7fa60062a8 100644 --- a/pkg/multicloud/azure/clouduser.go +++ b/pkg/multicloud/azure/clouduser.go @@ -119,19 +119,23 @@ func (user *SClouduser) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, } func (user *SClouduser) AttachSystemPolicy(policyId string) error { - subscriptionId, err := user.client.getDefaultSubscriptionId() - if err != nil { - return errors.Wrapf(err, "getDefaultSubscriptionId") + for _, subscription := range user.client.subscriptions { + err := user.client.AssignPolicy(user.ObjectId, policyId, subscription.SubscriptionId) + if err != nil { + return errors.Wrapf(err, "AssignPolicy for subscription %s", subscription.SubscriptionId) + } } - return user.client.AssignPolicy(user.ObjectId, policyId, subscriptionId) + return nil } func (user *SClouduser) AttachCustomPolicy(policyId string) error { - subscriptionId, err := user.client.getDefaultSubscriptionId() - if err != nil { - return errors.Wrapf(err, "getDefaultSubscriptionId") + for _, subscription := range user.client.subscriptions { + err := user.client.AssignPolicy(user.ObjectId, policyId, subscription.SubscriptionId) + if err != nil { + return errors.Wrapf(err, "AssignPolicy for subscription %s", subscription.SubscriptionId) + } } - return user.client.AssignPolicy(user.ObjectId, policyId, subscriptionId) + return nil } func (user *SClouduser) DetachSystemPolicy(policyId string) error { @@ -145,7 +149,7 @@ func (user *SClouduser) DetachSystemPolicy(policyId string) error { return errors.Wrapf(err, "GetRule(%s)", assignment.Properties.RoleDefinitionId) } if role.Properties.RoleName == policyId { - return user.client.Delete(assignment.Id) + return user.client.gdel(assignment.Id) } } return nil @@ -182,30 +186,13 @@ func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { } func (self *SAzureClient) GetUserGroups(userId string) ([]SCloudgroup, error) { - cli, err := self.getGraphClient() - if err != nil { - return nil, err - } - resource := fmt.Sprintf("%s/users/%s/memberOf", self.tenantId, userId) - resp, err := jsonRequest(cli, "GET", self.domain, resource, self.subscriptionId, "", GraphResource) - if err != nil { - return nil, err - } - groups := []SCloudgroup{} - err = resp.Unmarshal(&groups, "value") - if err != nil { - return nil, errors.Wrap(err, "resp.Unmarshal") - } - return groups, nil + err := self.glist(resource, url.Values{}, groups) + return groups, err } func (self *SAzureClient) ResetClouduserPassword(id, password string) error { - cli, err := self.getGraphClient() - if err != nil { - return err - } body := jsonutils.Marshal(map[string]interface{}{ "passwordPolicies": "DisablePasswordExpiration, DisableStrongPassword", "passwordProfile": map[string]interface{}{ @@ -213,7 +200,7 @@ func (self *SAzureClient) ResetClouduserPassword(id, password string) error { }, }) resource := fmt.Sprintf("%s/users/%s", self.tenantId, id) - _, err = jsonRequest(cli, "PATCH", self.domain, resource, self.subscriptionId, body.String(), GraphResource) + _, err := self.gpatch(resource, body) return err } @@ -223,7 +210,7 @@ func (self *SAzureClient) GetCloudusers(name string) ([]SClouduser, error) { if len(name) > 0 { params.Set("$filter", fmt.Sprintf("userPrincipalName eq '%s'", name)) } - err := self.ListGraphResource("users", params, &users) + err := self.glist("users", params, &users) if err != nil { return nil, err } @@ -231,7 +218,7 @@ func (self *SAzureClient) GetCloudusers(name string) ([]SClouduser, error) { } func (self *SAzureClient) DeleteClouduser(id string) error { - return self.DeleteGraph(fmt.Sprintf("%s/users/%s?api-version=1.6", self.tenantId, id)) + return self.gdel(fmt.Sprintf("%s/users/%s", self.tenantId, id)) } func (self *SAzureClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { @@ -272,9 +259,9 @@ type SDomain struct { func (self *SAzureClient) GetDomains() ([]SDomain, error) { domains := []SDomain{} - err := self.ListGraphResource("domains", nil, &domains) + err := self.glist("domains", nil, &domains) if err != nil { - return nil, errors.Wrap(err, "ListGraphResource") + return nil, errors.Wrap(err, "glist") } return domains, nil } @@ -302,7 +289,7 @@ func (self *SAzureClient) CreateClouduser(name, password string) (*SClouduser, e } params["userPrincipalName"] = fmt.Sprintf("%s@%s", name, domains[0].Name) user := SClouduser{client: self} - err = self.CreateGraphResource("users", jsonutils.Marshal(params), &user) + err = self.gcreate("users", jsonutils.Marshal(params), &user) if err != nil { return nil, errors.Wrap(err, "Create") } diff --git a/pkg/multicloud/azure/data_disk.go b/pkg/multicloud/azure/data_disk.go new file mode 100644 index 0000000000..d87b018677 --- /dev/null +++ b/pkg/multicloud/azure/data_disk.go @@ -0,0 +1,164 @@ +// 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 azure + +import ( + "context" + "strings" + + "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 SDataDisk struct { + multicloud.SDisk + region *SRegion + + Lun int32 + Name string `json:"name,omitempty"` + DiskName string `json:"diskName,omitempty"` + Vhd *VirtualHardDisk `json:"vhd,omitempty"` + Caching string `json:"caching,omitempty"` + DiskSizeGB TAzureInt32 `json:"diskSizeGB,omitempty"` + IoType string `json:"ioType,omitempty"` + CreateOption string `json:"createOption,omitempty"` + ManagedDisk *ManagedDiskParameters `json:"managedDisk,omitempty"` + VhdUri string `json:"vhdUri,omitempty"` + StorageAccount *SubResource `json:"storageAccount,omitempty"` +} + +func (self *SDataDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { + if self.ManagedDisk != nil { + snapshot, err := self.region.CreateSnapshot(self.ManagedDisk.ID, name, desc) + if err != nil { + return nil, errors.Wrapf(err, "CreateSnapshot") + } + return snapshot, nil + } + return nil, cloudprovider.ErrNotSupported +} + +func (self *SDataDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + return []cloudprovider.ICloudSnapshot{}, nil +} + +func (self *SDataDisk) Delete(ctx context.Context) error { + if self.ManagedDisk != nil { + return self.region.del(self.ManagedDisk.ID) + } + return cloudprovider.ErrNotSupported +} + +func (self *SDataDisk) GetStatus() string { + return api.DISK_READY +} + +func (self *SDataDisk) GetId() string { + if self.ManagedDisk != nil { + return strings.ToLower(self.ManagedDisk.ID) + } + return self.Vhd.Uri +} + +func (self *SDataDisk) GetGlobalId() string { + return self.GetId() +} + +func (self *SDataDisk) GetName() string { + return self.Name +} + +func (self *SDataDisk) Resize(ctx context.Context, sizeMb int64) error { + if self.ManagedDisk != nil { + return self.region.ResizeDisk(self.ManagedDisk.ID, int32(sizeMb/1024)) + } + return cloudprovider.ErrNotSupported +} + +func (self *SDataDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { + storageType := "Standard_LRS" + if self.ManagedDisk != nil && len(self.ManagedDisk.StorageAccountType) > 0 { + storageType = self.ManagedDisk.StorageAccountType + } + return &SStorage{storageType: storageType, zone: self.region.getZone()}, nil +} + +func (self *SDataDisk) GetFsFormat() string { + return "" +} + +func (self *SDataDisk) GetIsNonPersistent() bool { + return false +} + +func (self *SDataDisk) GetDriver() string { + return "scsi" +} + +func (self *SDataDisk) GetCacheMode() string { + return "none" +} + +func (self *SDataDisk) GetMountpoint() string { + return "" +} + +func (self *SDataDisk) GetDiskFormat() string { + return "vhd" +} + +func (self *SDataDisk) GetDiskSizeMB() int { + return int(self.DiskSizeGB.Int32()) * 1024 +} + +func (self *SDataDisk) GetIsAutoDelete() bool { + return true +} + +func (self *SDataDisk) GetTemplateId() string { + if self.ManagedDisk != nil { + disk, err := self.region.GetDisk(self.ManagedDisk.ID) + if err == nil { + return disk.GetTemplateId() + } + } + return "" +} + +func (self *SDataDisk) Reset(ctx context.Context, snapshotId string) (string, error) { + return "", cloudprovider.ErrNotSupported +} + +func (self *SDataDisk) GetDiskType() string { + return api.DISK_TYPE_DATA +} + +func (disk *SDataDisk) GetAccessPath() string { + return "" +} + +func (self *SDataDisk) Rebuild(ctx context.Context) error { + return cloudprovider.ErrNotSupported +} + +func (self *SDataDisk) GetProjectId() string { + if self.ManagedDisk != nil { + return getResourceGroup(self.ManagedDisk.ID) + } + return "" +} diff --git a/pkg/multicloud/azure/debug.go b/pkg/multicloud/azure/debug.go index 504a842a92..7e82c8dbef 100644 --- a/pkg/multicloud/azure/debug.go +++ b/pkg/multicloud/azure/debug.go @@ -30,8 +30,10 @@ func LogRequest() autorest.PrepareDecorator { if err != nil { log.Errorln(err) } - dump, _ := httputil.DumpRequestOut(r, true) - log.Errorf("%s", string(dump)) + auth := r.Header.Get("Authorization") + if len(auth) > 0 { + log.Debugf("Authorization: %s", auth) + } return r, err }) } diff --git a/pkg/multicloud/azure/disk.go b/pkg/multicloud/azure/disk.go index e0add6c354..81a8a9eb09 100644 --- a/pkg/multicloud/azure/disk.go +++ b/pkg/multicloud/azure/disk.go @@ -17,6 +17,8 @@ package azure import ( "context" "fmt" + "net/url" + "strconv" "strings" "time" @@ -49,11 +51,18 @@ type CreationData struct { SourceResourceID string `json:"sourceResourceId,omitempty"` } +type TAzureInt32 string + +func (ai TAzureInt32) Int32() int32 { + num, _ := strconv.Atoi(strings.Trim(string(ai), "\t")) + return int32(num) +} + type DiskProperties struct { //TimeCreated time.Time //??? 序列化出错? OsType string `json:"osType,omitempty"` CreationData CreationData `json:"creationData,omitempty"` - DiskSizeGB int32 `json:"diskSizeGB,omitempty"` + DiskSizeGB TAzureInt32 `json:"diskSizeGB,omitempty"` ProvisioningState string `json:"provisioningState,omitempty"` DiskState string `json:"diskState,omitempty"` } @@ -74,135 +83,102 @@ type SDisk struct { Tags map[string]string `json:"tags,omitempty"` } -func (self *SRegion) CreateDisk(storageType string, name string, sizeGb int32, desc string, imageId, resourceGroup string) (*SDisk, error) { - disk := SDisk{ - Name: name, - Location: self.Name, - Sku: DiskSku{ - Name: storageType, +func (self *SRegion) CreateDisk(storageType string, name string, sizeGb int32, desc string, imageId, snapshotId, resourceGroup string) (*SDisk, error) { + params := jsonutils.Marshal(map[string]interface{}{ + "Name": name, + "Location": self.Name, + "Sku": map[string]string{ + "Name": storageType, }, - Properties: DiskProperties{ - CreationData: CreationData{ - CreateOption: "Empty", - }, - DiskSizeGB: sizeGb, + "Type": "Microsoft.Compute/disks", + }).(*jsonutils.JSONDict) + properties := map[string]interface{}{ + "CreationData": map[string]string{ + "CreateOption": "Empty", }, - Type: "Microsoft.Compute/disks", + "DiskSizeGB": sizeGb, } if len(imageId) > 0 { image, err := self.GetImageById(imageId) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "GetImageById(%s)", imageId) } - if isPrivateImageID(image.ID) { - blobUrl := image.GetBlobUri() - if len(blobUrl) == 0 { - return nil, fmt.Errorf("failed to find blobUri for image %s", image.Name) - } - disk.Properties.CreationData = CreationData{ - CreateOption: "Import", - SourceURI: blobUrl, - } - } else { - // 通过镜像创建的磁盘只能传ID参数,不能通过sku,offer等参数创建. - _imageId, err := self.getOfferedImageId(&image) - if err != nil { - return nil, err - } - disk.Properties.CreationData = CreationData{ - CreateOption: "FromImage", - ImageReference: &ImageReference{ - ID: _imageId, + // 通过镜像创建的磁盘只能传ID参数,不能通过sku,offer等参数创建. + imageId, err := self.getOfferedImageId(&image) + if err != nil { + return nil, errors.Wrapf(err, "getOfferedImageId") + } + properties = map[string]interface{}{ + "CreationData": map[string]interface{}{ + "CreateOption": "FromImage", + "ImageReference": map[string]string{ + "Id": imageId, }, - } + }, + } + } else if len(snapshotId) > 0 { + properties = map[string]interface{}{ + "CreationData": map[string]interface{}{ + "CreateOption": "Copy", + "sourceResourceId": snapshotId, + }, } - disk.Properties.OsType = image.GetOsType() } - return &disk, self.client.CreateWithResourceGroup(resourceGroup, jsonutils.Marshal(disk), &disk) + params.Add(jsonutils.Marshal(properties), "Properties") + disk := &SDisk{} + return disk, self.create(resourceGroup, params, disk) } func (self *SRegion) DeleteDisk(diskId string) error { - return self.deleteDisk(diskId) -} - -func (self *SRegion) deleteDisk(diskId string) error { - if !strings.HasPrefix(diskId, "https://") { - startTime := time.Now() - timeout := 5 * time.Minute - for { - err := self.client.Delete(diskId) - if err == nil { - return nil - } - // Disk vdisk_stress-testvm-azure-1-1_1555940308395625000 is attached to VM /subscriptions/d4f0ec08-3e28-4ae5-bdf9-3dc7c5b0eeca/resourceGroups/Default/providers/Microsoft.Compute/virtualMachines/stress-testvm-azure-1. - // 更换系统盘后,数据未刷新会出现如上错误,多尝试几次即可 - if strings.Contains(err.Error(), "is attached to VM") { - time.Sleep(time.Second * 5) - } else { - return err - } - if time.Now().Sub(startTime) > timeout { - return err - } + return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) { + err := self.del(diskId) + if err == nil { + return true, nil } - } - //TODO - return cloudprovider.ErrNotImplemented + // Disk vdisk_stress-testvm-azure-1-1_1555940308395625000 is attached to VM /subscriptions/d4f0ec08-3e28-4ae5-bdf9-3dc7c5b0eeca/resourceGroups/Default/providers/Microsoft.Compute/virtualMachines/stress-testvm-azure-1. + // 更换系统盘后,数据未刷新会出现如上错误,多尝试几次即可 + if strings.Contains(err.Error(), "is attached to VM") { + return false, nil + } + return false, err + }) } func (self *SRegion) ResizeDisk(diskId string, sizeGb int32) error { - if !strings.HasPrefix(diskId, "https://") { - disk, err := self.GetDisk(diskId) - if err != nil { - return err - } - disk.Properties.DiskSizeGB = sizeGb - disk.Properties.ProvisioningState = "" - return self.client.Update(jsonutils.Marshal(disk), nil) + disk, err := self.GetDisk(diskId) + if err != nil { + return err } - return cloudprovider.ErrNotSupported + disk.Properties.DiskSizeGB = TAzureInt32(sizeGb) + disk.Properties.ProvisioningState = "" + return self.update(jsonutils.Marshal(disk), nil) } func (self *SRegion) GetDisk(diskId string) (*SDisk, error) { disk := SDisk{} - return &disk, self.client.Get(diskId, []string{}, &disk) + return &disk, self.get(diskId, url.Values{}, &disk) } func (self *SRegion) GetDisks() ([]SDisk, error) { - result := []SDisk{} disks := []SDisk{} - err := self.client.ListAll("Microsoft.Compute/disks", &disks) + err := self.list("Microsoft.Compute/disks", url.Values{}, &disks) if err != nil { return nil, err } - for i := 0; i < len(disks); i++ { - if disks[i].Location == self.Name { - result = append(result, disks[i]) - } - } - return result, nil -} - -func (self *SDisk) GetMetadata() *jsonutils.JSONDict { - data := jsonutils.NewDict() - data.Add(jsonutils.NewString(api.HYPERVISOR_AZURE), "hypervisor") - return data + return disks, nil } func (self *SDisk) GetStatus() string { - if !strings.HasPrefix(self.ID, "https://") { - status := self.Properties.ProvisioningState - switch status { - case "Updating": - return api.DISK_ALLOCATING - case "Succeeded": - return api.DISK_READY - default: - log.Errorf("Unknow azure disk %s status: %s", self.ID, status) - return api.DISK_UNKNOWN - } + status := self.Properties.ProvisioningState + switch status { + case "Updating": + return api.DISK_ALLOCATING + case "Succeeded": + return api.DISK_READY + default: + log.Errorf("Unknow azure disk %s status: %s", self.ID, status) + return api.DISK_UNKNOWN } - return api.DISK_READY } func (self *SDisk) GetId() string { @@ -210,18 +186,15 @@ func (self *SDisk) GetId() string { } func (self *SDisk) Refresh() error { - if !strings.HasPrefix(self.ID, "https://") { - disk, err := self.storage.zone.region.GetDisk(self.ID) - if err != nil { - return cloudprovider.ErrNotFound - } - return jsonutils.Update(self, disk) + disk, err := self.storage.zone.region.GetDisk(self.ID) + if err != nil { + return errors.Wrapf(err, "GetDisk(%s)", self.ID) } - return nil + return jsonutils.Update(self, disk) } func (self *SDisk) Delete(ctx context.Context) error { - return self.storage.zone.region.deleteDisk(self.ID) + return self.storage.zone.region.DeleteDisk(self.ID) } func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error { @@ -272,7 +245,7 @@ func (self *SDisk) GetDiskFormat() string { } func (self *SDisk) GetDiskSizeMB() int { - return int(self.Properties.DiskSizeGB) * 1024 + return int(self.Properties.DiskSizeGB.Int32()) * 1024 } func (self *SDisk) GetIsAutoDelete() bool { @@ -294,31 +267,26 @@ func (self *SDisk) GetDiskType() string { } func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { - if snapshot, err := self.storage.zone.region.CreateSnapshot(self.ID, name, desc); err != nil { - log.Errorf("createSnapshot fail %s", err) - return nil, err - } else { - return snapshot, nil + snapshot, err := self.storage.zone.region.CreateSnapshot(self.ID, name, desc) + if err != nil { + return nil, errors.Wrapf(err, "CreateSnapshot") } -} - -func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { - return self.GetSnapshotDetail(snapshotId) + return snapshot, nil } func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { - isnapshots := make([]cloudprovider.ICloudSnapshot, 0) - if !strings.HasPrefix(self.ID, "https://") { - snapshots, err := self.storage.zone.region.GetSnapShots(self.ID) - if err != nil { - return nil, err - } - - for i := 0; i < len(snapshots); i++ { - isnapshots = append(isnapshots, &snapshots[i]) + snapshots, err := self.storage.zone.region.ListSnapshots() + if err != nil { + return nil, errors.Wrapf(err, "ListSnapshots") + } + ret := []cloudprovider.ICloudSnapshot{} + for i := range snapshots { + if strings.ToLower(snapshots[i].Properties.CreationData.SourceResourceID) == strings.ToLower(self.ID) { + snapshots[i].region = self.storage.zone.region + ret = append(ret, &snapshots[i]) } } - return isnapshots, nil + return ret, nil } func (self *SDisk) GetBillingType() string { @@ -333,77 +301,21 @@ func (self *SDisk) GetExpiredAt() time.Time { return time.Time{} } -func (self *SDisk) GetSnapshotDetail(snapshotId string) (*SSnapshot, error) { - snapshot, err := self.storage.zone.region.GetSnapshotDetail(snapshotId) - if err != nil { - return nil, err - } - if snapshot.Properties.CreationData.SourceResourceID != self.ID { - return nil, cloudprovider.ErrNotFound - } - return snapshot, nil -} - -func (region *SRegion) GetSnapshotDetail(snapshotId string) (*SSnapshot, error) { - snapshot := SSnapshot{region: region} - return &snapshot, region.client.Get(snapshotId, []string{}, &snapshot) -} - -func (region *SRegion) GetSnapShots(diskId string) ([]SSnapshot, error) { - result := []SSnapshot{} - if !strings.HasPrefix(diskId, "https://") { - snapshots := []SSnapshot{} - err := region.client.ListAll("Microsoft.Compute/snapshots", &snapshots) - if err != nil { - return nil, err - } - for i := 0; i < len(snapshots); i++ { - if snapshots[i].Location == region.Name { - if len(diskId) == 0 || diskId == snapshots[i].Properties.CreationData.SourceResourceID { - snapshots[i].region = region - result = append(result, snapshots[i]) - } - } - } - } - return result, nil -} - func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { if self.Properties.DiskState != "Unattached" { return "", fmt.Errorf("Azure reset disk needs to be done in the Unattached state, current status: %s", self.Properties.DiskState) } - disk, err := self.storage.zone.region.CreateDiskBySnapshot(self.Name, snapshotId) + disk, err := self.storage.zone.region.CreateDisk(self.Sku.Name, self.Name, 0, "", "", snapshotId, self.GetProjectId()) if err != nil { - return "", errors.Wrap(err, "Reset") + return "", errors.Wrap(err, "CreateDisk") } - err = self.storage.zone.region.deleteDisk(self.ID) + err = self.storage.zone.region.DeleteDisk(self.ID) if err != nil { log.Warningf("delete old disk %s error: %v", self.ID, err) } return disk.ID, nil } -func (self *SRegion) CreateDiskBySnapshot(diskName, snapshotId string) (*SDisk, error) { - params := map[string]interface{}{ - "name": diskName, - "location": self.Name, - "properties": map[string]interface{}{ - "creationData": map[string]string{ - "createOption": "Copy", - "sourceResourceId": snapshotId, - }, - }, - "type": "Microsoft.Compute/disks", - } - disk := &SDisk{} - err := self.client.Create(jsonutils.Marshal(params), disk) - if err != nil { - return nil, errors.Wrapf(err, "CreateDiskBySnapshot.Create") - } - return disk, nil -} - func (disk *SDisk) GetAccessPath() string { return "" } diff --git a/pkg/multicloud/azure/eip.go b/pkg/multicloud/azure/eip.go index 66d35fe2f0..06b67e86f1 100644 --- a/pkg/multicloud/azure/eip.go +++ b/pkg/multicloud/azure/eip.go @@ -16,11 +16,13 @@ package azure import ( "fmt" + "net/url" "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -42,11 +44,11 @@ type IPConfiguration struct { } type PublicIPAddressPropertiesFormat struct { - PublicIPAddressVersion string `json:"publicIPAddressVersion,omitempty"` - IPAddress string `json:"ipAddress,omitempty"` - PublicIPAllocationMethod string `json:"publicIPAllocationMethod,omitempty"` - ProvisioningState string `json:"provisioningState,omitempty"` - IPConfiguration *IPConfiguration `json:"ipConfiguration,omitempty"` + PublicIPAddressVersion string `json:"publicIPAddressVersion,omitempty"` + IPAddress string `json:"ipAddress,omitempty"` + PublicIPAllocationMethod string `json:"publicIPAllocationMethod,omitempty"` + ProvisioningState string `json:"provisioningState,omitempty"` + IPConfiguration IPConfiguration `json:"ipConfiguration,omitempty"` } type SEipAddress struct { @@ -61,22 +63,22 @@ type SEipAddress struct { Sku *PublicIPAddressSku } -func (region *SRegion) AllocateEIP(eipName, projectId string) (*SEipAddress, error) { - eip := SEipAddress{ - region: region, - Location: region.Name, - Name: eipName, - Properties: PublicIPAddressPropertiesFormat{ - PublicIPAddressVersion: "IPv4", - PublicIPAllocationMethod: "Static", +func (self *SRegion) AllocateEIP(name, projectId string) (*SEipAddress, error) { + params := map[string]interface{}{ + "Location": self.Name, + "Name": name, + "Properties": map[string]string{ + "PublicIPAddressVersion": "IPv4", + "PublicIPAllocationMethod": "Static", }, - Type: "Microsoft.Network/publicIPAddresses", + "Type": "Microsoft.Network/publicIPAddresses", } - err := region.client.CreateWithResourceGroup(projectId, jsonutils.Marshal(eip), &eip) + eip := &SEipAddress{region: self} + err := self.create(projectId, jsonutils.Marshal(params), eip) if err != nil { return nil, err } - return &eip, cloudprovider.WaitStatus(&eip, api.EIP_STATUS_READY, 10*time.Second, 300*time.Second) + return eip, cloudprovider.WaitStatus(eip, api.EIP_STATUS_READY, 10*time.Second, 300*time.Second) } func (region *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { @@ -85,7 +87,7 @@ func (region *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudE func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) { eip := SEipAddress{region: region} - return &eip, region.client.Get(eipId, []string{}, &eip) + return &eip, region.get(eipId, url.Values{}, &eip) } func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error { @@ -98,14 +100,13 @@ func (region *SRegion) AssociateEip(eipId string, instanceId string) error { return err } if len(instance.Properties.NetworkProfile.NetworkInterfaces) > 0 { - nic, err := region.GetNetworkInterfaceDetail(instance.Properties.NetworkProfile.NetworkInterfaces[0].ID) + nic, err := region.GetNetworkInterface(instance.Properties.NetworkProfile.NetworkInterfaces[0].ID) if err != nil { return err } - log.Errorf("nic: %s", jsonutils.Marshal(nic).PrettyString()) if len(nic.Properties.IPConfigurations) > 0 { nic.Properties.IPConfigurations[0].Properties.PublicIPAddress = &PublicIPAddress{ID: eipId} - return region.client.Update(jsonutils.Marshal(nic), nil) + return region.update(jsonutils.Marshal(nic), nil) } return fmt.Errorf("network interface with no IPConfigurations") } @@ -126,24 +127,18 @@ func (self *SEipAddress) Delete() error { } func (region *SRegion) DeallocateEIP(eipId string) error { - startTime := time.Now() - timeout := time.Minute * 3 - for { - err := region.client.Delete(eipId) + return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) { + err := region.del(eipId) if err == nil { - return nil + return true, nil } // {"error":{"code":"PublicIPAddressCannotBeDeleted","details":[],"message":"Public IP address /subscriptions/d4f0ec08-3e28-4ae5-bdf9-3dc7c5b0eeca/resourceGroups/Default/providers/Microsoft.Network/publicIPAddresses/eip-for-test-wwl can not be deleted since it is still allocated to resource /subscriptions/d4f0ec08-3e28-4ae5-bdf9-3dc7c5b0eeca/resourceGroups/Default/providers/Microsoft.Network/networkInterfaces/test-wwl-ipconfig."}} // 刚解绑eip后可能数据未刷新,需要再次尝试 if strings.Contains(err.Error(), "it is still allocated to resource") { - time.Sleep(time.Second * 5) - } else { - return err + return false, nil } - if time.Now().Sub(startTime) > timeout { - return err - } - } + return false, errors.Wrapf(err, "del(%s)", eipId) + }) } func (self *SEipAddress) Dissociate() error { @@ -153,17 +148,13 @@ func (self *SEipAddress) Dissociate() error { func (region *SRegion) DissociateEip(eipId string) error { eip, err := region.GetEip(eipId) if err != nil { - return err - } - if eip.Properties.IPConfiguration == nil { - log.Debugf("eip %s not associate any instance", eip.Name) - return nil + return errors.Wrapf(err, "GetEip(%s)", eipId) } interfaceId := eip.Properties.IPConfiguration.ID if strings.Index(interfaceId, "/ipConfigurations/") > 0 { interfaceId = strings.Split(interfaceId, "/ipConfigurations/")[0] } - nic, err := region.GetNetworkInterfaceDetail(interfaceId) + nic, err := region.GetNetworkInterface(interfaceId) if err != nil { return err } @@ -173,23 +164,19 @@ func (region *SRegion) DissociateEip(eipId string) error { break } } - return region.client.Update(jsonutils.Marshal(nic), nil) + return region.update(jsonutils.Marshal(nic), nil) } func (self *SEipAddress) GetAssociationExternalId() string { - if self.Properties.IPConfiguration != nil { - interfaceId := self.Properties.IPConfiguration.ID - if strings.Index(interfaceId, "/ipConfigurations/") > 0 { - interfaceId = strings.Split(interfaceId, "/ipConfigurations/")[0] - } - nic, err := self.region.GetNetworkInterfaceDetail(interfaceId) + interfaceId := self.Properties.IPConfiguration.ID + if len(interfaceId) > 0 && strings.Index(interfaceId, "/ipConfigurations/") > 0 { + interfaceId = strings.Split(interfaceId, "/ipConfigurations/")[0] + nic, err := self.region.GetNetworkInterface(interfaceId) if err != nil { - log.Errorf("Failt to find NetworkInterface for eip %s", self.Name) + log.Errorf("Failt to find NetworkInterface for eip %s nic %s", self.Name, interfaceId) return "" } - if nic.Properties.VirtualMachine != nil { - return nic.Properties.VirtualMachine.ID - } + return strings.ToLower(nic.Properties.VirtualMachine.ID) } return "" } @@ -230,11 +217,9 @@ func (self *SEipAddress) GetMode() string { if self.IsEmulated() { return api.EIP_MODE_INSTANCE_PUBLICIP } - if self.Properties.IPConfiguration != nil { - nic, err := self.region.GetNetworkInterfaceDetail(self.Properties.IPConfiguration.ID) - if err == nil && nic.Properties.VirtualMachine != nil && len(nic.Properties.VirtualMachine.ID) > 0 { - return api.EIP_MODE_INSTANCE_PUBLICIP - } + nic, err := self.region.GetNetworkInterface(self.Properties.IPConfiguration.ID) + if err == nil && len(nic.Properties.VirtualMachine.ID) > 0 { + return api.EIP_MODE_INSTANCE_PUBLICIP } return api.EIP_MODE_STANDALONE_EIP } diff --git a/pkg/multicloud/azure/enrollment_account.go b/pkg/multicloud/azure/enrollment_account.go index bd07abe7ec..467276c00b 100644 --- a/pkg/multicloud/azure/enrollment_account.go +++ b/pkg/multicloud/azure/enrollment_account.go @@ -38,19 +38,17 @@ type SEnrollmentAccount struct { } func (cli *SAzureClient) GetEnrollmentAccounts() ([]cloudprovider.SEnrollmentAccount, error) { - accounts := struct { - Value []SEnrollmentAccount - }{} - err := cli.Get("providers/Microsoft.Billing/enrollmentAccounts", nil, &accounts) + accounts := []SEnrollmentAccount{} + err := cli.list("providers/Microsoft.Billing/enrollmentAccounts", nil, &accounts) if err != nil { return nil, err } eas := []cloudprovider.SEnrollmentAccount{} - for i := range accounts.Value { + for i := range accounts { ea := cloudprovider.SEnrollmentAccount{ - Id: accounts.Value[i].Name, - Name: accounts.Value[i].Properties.PrincipalName, - Type: accounts.Value[i].Type, + Id: accounts[i].Name, + Name: accounts[i].Properties.PrincipalName, + Type: accounts[i].Type, } eas = append(eas, ea) } @@ -72,7 +70,8 @@ func (cli *SAzureClient) CreateSubscription(name string, eaId string, offerType "owners": owners, } resource := fmt.Sprintf("providers/Microsoft.Billing/enrollmentAccounts/%s/providers/Microsoft.Subscription/createSubscription", eaId) - return cli.POST(resource, jsonutils.Marshal(body)) + _, err = cli.post(resource, jsonutils.Marshal(body)) + return err } type SServicePrincipal struct { @@ -97,9 +96,5 @@ func (cli *SAzureClient) ListServicePrincipal(appId string) ([]SServicePrincipal params.Set("$filter", fmt.Sprintf(`appId eq '%s'`, cli.clientId)) } result := []SServicePrincipal{} - err := cli.ListGraphResource("servicePrincipals", params, &result) - if err != nil { - return result, errors.Wrap(err, "ListGraphResource.servicePrincipals") - } - return result, nil + return result, cli.glist("servicePrincipals", params, &result) } diff --git a/pkg/multicloud/azure/event.go b/pkg/multicloud/azure/event.go index 4fe5141245..ff9ab09c55 100644 --- a/pkg/multicloud/azure/event.go +++ b/pkg/multicloud/azure/event.go @@ -137,29 +137,18 @@ func (region *SRegion) GetICloudEvents(start time.Time, end time.Time, withReadE func (region *SRegion) GetEvents(start time.Time, end time.Time) ([]SEvent, error) { events := []SEvent{} - params := url.Values{} if start.IsZero() { start = time.Now().AddDate(0, 0, -7) } if end.IsZero() { end = time.Now() } + params := url.Values{} params.Set("$filter", fmt.Sprintf("eventTimestamp ge '%s' and eventTimestamp le '%s' and eventChannels eq 'Admin, Operation' and levels eq 'Critical,Error,Warning,Informational'", start.Format("2006-01-02T15:04:05Z"), end.Format("2006-01-02T15:04:05Z"))) - nextLink := fmt.Sprintf("microsoft.insights/eventtypes/management/values?%s", params.Encode()) - var err error - for { - _events := []SEvent{} - nextLink, err = region.client.ListAllWithNextToken(nextLink, &_events) - if err != nil { - return nil, err - } - events = append(events, _events...) - if len(nextLink) > 0 { - nextLink = nextLink[strings.Index(nextLink, "microsoft.insights"):] - } - if len(nextLink) == 0 || len(_events) == 0 { - break - } + resource := fmt.Sprintf("microsoft.insights/eventtypes/management/values") + err := region.client.list(resource, params, &events) + if err != nil { + return nil, err } return events, nil } diff --git a/pkg/multicloud/azure/host.go b/pkg/multicloud/azure/host.go index e4909ecb54..2c4a4e3cd5 100644 --- a/pkg/multicloud/azure/host.go +++ b/pkg/multicloud/azure/host.go @@ -49,7 +49,7 @@ func (self *SHost) GetName() string { } func (self *SHost) GetGlobalId() string { - return fmt.Sprintf("%s/%s", self.zone.region.GetGlobalId(), self.zone.region.SubscriptionID) + return fmt.Sprintf("%s/%s", self.zone.region.GetGlobalId(), self.zone.region.client.subscriptionId) } func (self *SHost) IsEmulated() bool { @@ -64,43 +64,25 @@ func (self *SHost) Refresh() error { return nil } -func (self *SHost) searchNetorkInterface(IPAddr string, networkId string, secgroupId string) (*SInstanceNic, error) { - interfaces, err := self.zone.region.GetNetworkInterfaces() +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + nic, err := self.zone.region.CreateNetworkInterface(desc.ProjectId, fmt.Sprintf("%s-ipconfig", desc.Name), desc.IpAddr, desc.ExternalNetworkId, desc.ExternalSecgroupId) if err != nil { + return nil, errors.Wrapf(err, "CreateNetworkInterface") + } + + instance, err := self.zone.region._createVM(desc, nic.ID) + if err != nil { + self.zone.region.DeleteNetworkInterface(nic.ID) return nil, err } - for i, nic := range interfaces { - for _, ipConf := range nic.Properties.IPConfigurations { - if ipConf.Properties.PrivateIPAddress == IPAddr && networkId == ipConf.Properties.Subnet.ID && ipConf.Properties.PrivateIPAllocationMethod == "Static" { - if nic.Properties.NetworkSecurityGroup == nil || nic.Properties.NetworkSecurityGroup.ID != secgroupId { - nic.Properties.NetworkSecurityGroup = &SSecurityGroup{ID: secgroupId} - if err := self.zone.region.client.Update(jsonutils.Marshal(nic), nil); err != nil { - log.Errorf("assign secgroup %s for nic %#v failed: %v", secgroupId, nic, err) - return nil, err - } - } - return &interfaces[i], nil - } - } - } - return nil, cloudprovider.ErrNotFound + instance.host = self + return instance, nil } -func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { - net := self.zone.getNetworkById(desc.ExternalNetworkId) - if net == nil { - return nil, fmt.Errorf("invalid network ID %s", desc.ExternalNetworkId) - } - nic, err := self.searchNetorkInterface(desc.IpAddr, net.GetId(), desc.ExternalSecgroupId) +func (self *SRegion) _createVM(desc *cloudprovider.SManagedVMCreateConfig, nicId string) (*SInstance, error) { + image, err := self.GetImageById(desc.ExternalImageId) if err != nil { - if errors.Cause(err) == cloudprovider.ErrNotFound { - nic, err = self.zone.region.CreateNetworkInterface(desc.ProjectId, fmt.Sprintf("%s-ipconfig", desc.Name), desc.IpAddr, net.GetId(), desc.ExternalSecgroupId) - if err != nil { - return nil, err - } - } else { - return nil, err - } + return nil, errors.Wrapf(err, "GetImageById(%s)", desc.ExternalImageId) } if len(desc.Password) == 0 { @@ -108,139 +90,117 @@ func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudpr desc.Password = seclib2.RandomPassword2(12) } - vmId, err := self._createVM(desc, nic.ID) - if err != nil { - self.zone.region.DeleteNetworkInterface(nic.ID) - return nil, err - } - if vm, err := self.zone.region.GetInstance(vmId); err != nil { - return nil, err - } else { - vm.host = self - return vm, err - } -} - -func (self *SHost) _createVM(desc *cloudprovider.SManagedVMCreateConfig, nicId string) (string, error) { - image, err := self.zone.region.GetImageById(desc.ExternalImageId) - if err != nil { - log.Errorf("Get Image %s fail %s", desc.ExternalImageId, err) - return "", err - } - if image.Properties.ProvisioningState != ImageStatusAvailable { - log.Errorf("image %s status %s", desc.ExternalImageId, image.Properties.ProvisioningState) - return "", fmt.Errorf("image not ready") - } - storage, err := self.zone.getStorageByType(desc.SysDisk.StorageType) - if err != nil { - return "", fmt.Errorf("Storage %s not avaiable: %s", desc.SysDisk.StorageType, err) + return nil, fmt.Errorf("image %s not ready status: %s", desc.ExternalImageId, image.Properties.ProvisioningState) } if !utils.IsInStringArray(desc.OsType, []string{osprofile.OS_TYPE_LINUX, osprofile.OS_TYPE_WINDOWS}) { desc.OsType = image.GetOsType() } - sysDiskSize := int32(desc.SysDisk.SizeGB) computeName := desc.Name - for _, k := range []string{"`", "~", "!", "@", "#", "$", `%`, "^", "&", "*", "(", ")", "=", "+", "_", "[", "]", "{", "}", "\\", "|", ";", ":", ".", "'", `"`, ",", "<", ">", "/", "?"} { - computeName = strings.Replace(computeName, k, "", -1) + for _, k := range "`~!@#$%^&*()=+_[]{}\\|;:.'\",<>/?" { + computeName = strings.Replace(computeName, string(k), "", -1) } if len(computeName) > 15 { computeName = computeName[:15] } - instance := SInstance{ - Name: desc.Name, - Location: self.zone.region.Name, - Properties: VirtualMachineProperties{ - HardwareProfile: HardwareProfile{ - VMSize: "", + osProfile := map[string]string{ + "ComputerName": computeName, + "AdminUsername": api.VM_AZURE_DEFAULT_LOGIN_USER, + "AdminPassword": desc.Password, + } + if len(desc.UserData) > 0 { + osProfile["CustomData"] = desc.UserData + } + params := jsonutils.Marshal(map[string]interface{}{ + "Name": desc.Name, + "Location": self.Name, + "Properties": map[string]interface{}{ + "HardwareProfile": map[string]string{ + "VMSize": "", }, - OsProfile: OsProfile{ - // Windows computer name cannot be more than 15 characters long, be entirely numeric, or contain the following characters: ` ~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \\ | ; : . ' \" , < > / ?." - ComputerName: computeName, - AdminUsername: api.VM_AZURE_DEFAULT_LOGIN_USER, - AdminPassword: desc.Password, - CustomData: desc.UserData, - }, - NetworkProfile: NetworkProfile{ - NetworkInterfaces: []NetworkInterfaceReference{ - { - ID: nicId, + "OsProfile": osProfile, + "NetworkProfile": map[string]interface{}{ + "NetworkInterfaces": []map[string]string{ + map[string]string{ + "Id": nicId, }, }, }, - StorageProfile: StorageProfile{ - ImageReference: image.getImageReference(), - OsDisk: OSDisk{ - Name: fmt.Sprintf("vdisk_%s_%d", desc.Name, time.Now().UnixNano()), - Caching: "ReadWrite", - ManagedDisk: &ManagedDiskParameters{ - StorageAccountType: storage.storageType, + "StorageProfile": map[string]interface{}{ + "ImageReference": image.getImageReference(), + "OsDisk": map[string]interface{}{ + "Name": fmt.Sprintf("vdisk_%s_%d", desc.Name, time.Now().UnixNano()), + "Caching": "ReadWrite", + "ManagedDisk": map[string]string{ + "StorageAccountType": desc.SysDisk.StorageType, }, - CreateOption: "FromImage", - DiskSizeGB: &sysDiskSize, - OsType: desc.OsType, + "CreateOption": "FromImage", + "DiskSizeGB": desc.SysDisk.SizeGB, + "OsType": desc.OsType, }, }, }, - Type: "Microsoft.Compute/virtualMachines", - } + "Type": "Microsoft.Compute/virtualMachines", + }).(*jsonutils.JSONDict) if len(desc.PublicKey) > 0 && desc.OsType == osprofile.OS_TYPE_LINUX { - instance.Properties.OsProfile.LinuxConfiguration = &LinuxConfiguration{ - DisablePasswordAuthentication: false, - SSH: &SSHConfiguration{ - PublicKeys: []SSHPublicKey{ - { - KeyData: desc.PublicKey, - Path: fmt.Sprintf("/home/%s/.ssh/authorized_keys", api.VM_AZURE_DEFAULT_LOGIN_USER), + linuxConfiguration := map[string]interface{}{ + "DisablePasswordAuthentication": false, + "SSH": map[string]interface{}{ + "PublicKeys": []map[string]string{ + map[string]string{ + "KeyData": desc.PublicKey, + "Path": fmt.Sprintf("/home/%s/.ssh/authorized_keys", api.VM_AZURE_DEFAULT_LOGIN_USER), }, }, }, } + params.Add(jsonutils.Marshal(linuxConfiguration), "Properties", "OsProfile", "LinuxConfiguration") } - _dataDisks := []DataDisk{} + dataDisks := jsonutils.NewArray() for i := 0; i < len(desc.DataDisks); i++ { - diskName := fmt.Sprintf("vdisk_%s_%d", desc.Name, time.Now().UnixNano()) - size := int32(desc.DataDisks[i].SizeGB) - lun := int32(i) - _dataDisks = append(_dataDisks, DataDisk{ - Name: diskName, - DiskSizeGB: &size, - CreateOption: "Empty", - Lun: lun, + dataDisk := jsonutils.Marshal(map[string]interface{}{ + "Name": fmt.Sprintf("vdisk_%s_%d", desc.Name, time.Now().UnixNano()), + "DiskSizeGB": desc.DataDisks[i].SizeGB, + "CreateOption": "Empty", + "Lun": i, }) + dataDisks.Add(dataDisk) } - if len(_dataDisks) > 0 { - instance.Properties.StorageProfile.DataDisks = _dataDisks + if dataDisks.Length() > 0 { + params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks") } + instance := &SInstance{} if len(desc.InstanceType) > 0 { - instance.Properties.HardwareProfile.VMSize = desc.InstanceType + params.Add(jsonutils.NewString(desc.InstanceType), "Properties", "HardwareProfile", "VMSize") log.Debugf("Try HardwareProfile : %s", desc.InstanceType) - err = self.zone.region.client.CreateWithResourceGroup(desc.ProjectId, jsonutils.Marshal(instance), &instance) + err = self.create(desc.ProjectId, params, instance) if err != nil { - log.Errorf("Failed for %s: %s", desc.InstanceType, err) - return "", fmt.Errorf("Failed to create specification %s.%s", desc.InstanceType, err.Error()) + return nil, errors.Wrapf(err, "create") } - return instance.ID, nil + return instance, nil } - for _, profile := range self.zone.region.getHardwareProfile(desc.Cpu, desc.MemoryMB) { - instance.Properties.HardwareProfile.VMSize = profile + for _, profile := range self.getHardwareProfile(desc.Cpu, desc.MemoryMB) { + params.Add(jsonutils.NewString(profile), "Properties", "HardwareProfile", "VMSize") log.Debugf("Try HardwareProfile : %s", profile) - err = self.zone.region.client.CreateWithResourceGroup(desc.ProjectId, jsonutils.Marshal(instance), &instance) + err = self.create(desc.ProjectId, params, &instance) if err != nil { for _, key := range []string{`"code":"InvalidParameter"`, `"code":"NicInUse"`} { if strings.Contains(err.Error(), key) { - return "", err + return nil, err } } log.Errorf("Failed for %s: %s", profile, err) continue } - return instance.ID, nil + return instance, nil } - return "", fmt.Errorf("instance type %dC%dMB not avaiable", desc.Cpu, desc.MemoryMB) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("instance type %dC%dMB not avaiable", desc.Cpu, desc.MemoryMB) } func (self *SHost) GetAccessIp() string { @@ -292,7 +252,7 @@ func (self *SHost) GetSysInfo() jsonutils.JSONObject { } func (self *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { - return self.zone.istorages, nil + return self.zone.getIStorages(), nil } func (self *SHost) GetIVMById(instanceId string) (cloudprovider.ICloudVM, error) { diff --git a/pkg/multicloud/azure/image.go b/pkg/multicloud/azure/image.go index 63223dd1ad..d62a942c70 100644 --- a/pkg/multicloud/azure/image.go +++ b/pkg/multicloud/azure/image.go @@ -17,6 +17,7 @@ package azure import ( "context" "fmt" + "net/url" "strings" "time" @@ -119,10 +120,6 @@ func (self *SImage) GetName() string { return self.Name } -func (self *SImage) IsEmulated() bool { - return false -} - func (self *SImage) GetGlobalId() string { return strings.ToLower(self.ID) } @@ -152,11 +149,11 @@ func (self *SImage) GetImageStatus() string { } func (self *SImage) Refresh() error { - new, err := self.storageCache.region.GetImageById(self.ID) + image, err := self.storageCache.region.GetImageById(self.ID) if err != nil { return err } - return jsonutils.Update(self, new) + return jsonutils.Update(self, image) } func (self *SImage) GetImageType() string { @@ -241,41 +238,13 @@ func (self *SRegion) GetImageById(imageId string) (SImage, error) { func (self *SRegion) getPrivateImage(imageId string) (SImage, error) { image := SImage{} - err := self.client.Get(imageId, []string{}, &image) + err := self.get(imageId, url.Values{}, &image) if err != nil { return image, err } return image, nil } -/* func (self *SRegion) GetImageByName(name string) (*SImage, error) { - images := []SImage{} - err := self.client.ListAll("Microsoft.Compute/images", &images) - if err != nil { - return nil, err - } - for i := 0; i < len(images); i++ { - if images[i].Name == name { - return &images[i], nil - } - } - return nil, cloudprovider.ErrNotFound -} - -func (self *SRegion) GetImageById(idstr string) (*SImage, error) { - images := []SImage{} - err := self.client.ListAll("Microsoft.Compute/images", &images) - if err != nil { - return nil, err - } - for i := 0; i < len(images); i++ { - if images[i].ID == idstr { - return &images[i], nil - } - } - return nil, cloudprovider.ErrNotFound -}*/ - func (self *SRegion) CreateImageByBlob(imageName, osType, blobURI string, diskSizeGB int32) (*SImage, error) { if diskSizeGB < 1 || diskSizeGB > 4095 { diskSizeGB = 30 @@ -295,7 +264,7 @@ func (self *SRegion) CreateImageByBlob(imageName, osType, blobURI string, diskSi }, Type: "Microsoft.Compute/images", } - return &image, self.client.Create(jsonutils.Marshal(image), &image) + return &image, self.create("", jsonutils.Marshal(image), &image) } func (self *SRegion) CreateImage(snapshotId, imageName, osType, imageDesc string) (*SImage, error) { @@ -315,7 +284,7 @@ func (self *SRegion) CreateImage(snapshotId, imageName, osType, imageDesc string }, Type: "Microsoft.Compute/images", } - return &image, self.client.Create(jsonutils.Marshal(image), &image) + return &image, self.create("", jsonutils.Marshal(image), &image) } func (self *SRegion) getOfferedImages(publishersFilter []string, offersFilter []string, skusFilter []string, verFilter []string, imageType string, latestVer bool) ([]SImage, error) { @@ -355,20 +324,20 @@ func (self *SRegion) GetOfferedImageIDs(publishersFilter []string, offersFilter for _, offer := range offers { skus, err := self.getImageSkus(publisher, offer, toLowerStringArray(skusFilter)) if err != nil { - log.Errorf("failed to found skus for publisher %s offer %s error: %v", publisher, offer, err) if errors.Cause(err) != cloudprovider.ErrNotFound { return nil, errors.Wrap(err, "getImageSkus") } + log.Errorf("failed to found skus for publisher %s offer %s error: %v", publisher, offer, err) continue } for _, sku := range skus { verFilter = toLowerStringArray(verFilter) vers, err := self.getImageVersions(publisher, offer, sku, verFilter, latestVer) if err != nil { - log.Errorf("failed to found publisher %s offer %s sku %s version error: %v", publisher, offer, sku, err) if errors.Cause(err) != cloudprovider.ErrNotFound { return nil, errors.Wrap(err, "getImageVersions") } + log.Errorf("failed to found publisher %s offer %s sku %s version error: %v", publisher, offer, sku, err) continue } for _, ver := range vers { @@ -388,7 +357,7 @@ func (self *SRegion) GetOfferedImageIDs(publishersFilter []string, offersFilter func (self *SRegion) getPrivateImages() ([]SImage, error) { result := []SImage{} images := []SImage{} - err := self.client.ListAll("Microsoft.Compute/images", &images) + err := self.client.list("Microsoft.Compute/images", url.Values{}, &images) if err != nil { return nil, err } @@ -433,7 +402,7 @@ func (self *SRegion) GetImages(imageType string) ([]SImage, error) { } func (self *SRegion) DeleteImage(imageId string) error { - return self.client.Delete(imageId) + return self.del(imageId) } func (self *SImage) GetBlobUri() string { @@ -464,7 +433,8 @@ type SAzureImageResource struct { func (region *SRegion) GetImagePublishers(filter []string) ([]string, error) { publishers := make([]SAzureImageResource, 0) - err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers", region.Name), &publishers, nil) + // TODO + err := region.client.list(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers", region.Name), url.Values{}, &publishers) if err != nil { return nil, err } @@ -493,7 +463,7 @@ func (region *SRegion) getImageOffers(publisher string, filter []string) ([]stri log.Warningf("failed to get publisher %s driver", publisher) } offers := make([]SAzureImageResource, 0) - err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers", region.Name, publisher), &offers, nil) + err := region.client.list(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers", region.Name, publisher), url.Values{}, &offers) if err != nil { return nil, err } @@ -519,7 +489,7 @@ func (region *SRegion) getImageSkus(publisher string, offer string, filter []str } } skus := make([]SAzureImageResource, 0) - err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus", region.Name, publisher, offer), &skus, nil) + err := region.client.list(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus", region.Name, publisher, offer), url.Values{}, &skus) if err != nil { return nil, err } @@ -534,10 +504,12 @@ func (region *SRegion) getImageSkus(publisher string, offer string, filter []str func (region *SRegion) getImageVersions(publisher string, offer string, sku string, filter []string, latestVer bool) ([]string, error) { vers := make([]SAzureImageResource, 0) resource := fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions", region.Name, publisher, offer, sku) + params := url.Values{} if latestVer { - resource = resource + "?$top=1&$orderby=name%20desc" + params.Set("$top", "1") + params.Set("orderby", "name desc") } - err := region.client.ListResources(resource, &vers, nil) + err := region.client.list(resource, params, &vers) if err != nil { return nil, err } @@ -558,7 +530,7 @@ func (region *SRegion) getImageDetail(publisher string, offer string, sku string "/artifacttypes/vmimage/offers/" + offer + "/skus/" + sku + "/versions/" + version - return image, region.client.Get(id, []string{}, &image) + return image, region.get(id, url.Values{}, &image) } func (region *SRegion) getOfferedImage(offerId string) (SImage, error) { diff --git a/pkg/multicloud/azure/instance.go b/pkg/multicloud/azure/instance.go index f5bfca00b4..5d373f4181 100644 --- a/pkg/multicloud/azure/instance.go +++ b/pkg/multicloud/azure/instance.go @@ -17,6 +17,7 @@ package azure import ( "context" "fmt" + "net/url" "strings" "time" @@ -24,7 +25,6 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/osprofile" - "yunion.io/x/pkg/utils" billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -53,39 +53,15 @@ type VirtualHardDisk struct { Uri string `json:"uri,omitempty"` } -type OSDisk struct { - OsType string `json:"osType,omitempty"` - Caching string `json:"caching,omitempty"` - Name string - DiskSizeGB *int32 `json:"diskSizeGB,omitempty"` - ManagedDisk *ManagedDiskParameters `json:"managedDisk,omitempty"` - CreateOption string `json:"createOption,omitempty"` - Vhd *VirtualHardDisk `json:"vhd,omitempty"` -} - type ManagedDiskParameters struct { StorageAccountType string `json:"storageAccountType,omitempty"` ID string } -type DataDisk struct { - Lun int32 - Name string `json:"name,omitempty"` - DiskName string `json:"diskName,omitempty"` - Vhd *VirtualHardDisk `json:"vhd,omitempty"` - Caching string `json:"caching,omitempty"` - DiskSizeGB *int32 `json:"diskSizeGB,omitempty"` - IoType string `json:"ioType,omitempty"` - CreateOption string `json:"createOption,omitempty"` - ManagedDisk *ManagedDiskParameters `json:"managedDisk,omitempty"` - VhdUri string `json:"vhdUri,omitempty"` - StorageAccount *SubResource `json:"storageAccount,omitempty"` -} - type StorageProfile struct { ImageReference ImageReference `json:"imageReference,omitempty"` - OsDisk OSDisk `json:"osDisk,omitempty"` - DataDisks []DataDisk `json:"dataDisks,allowempty"` + OsDisk SOsDisk `json:"osDisk,omitempty"` + DataDisks []SDataDisk `json:"dataDisks,allowempty"` } type SSHPublicKey struct { @@ -194,41 +170,36 @@ type SInstance struct { func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { instance := SInstance{} - return &instance, self.client.Get(instanceId, []string{"$expand=instanceView"}, &instance) + params := url.Values{} + params.Set("$expand", "instanceView") + return &instance, self.get(instanceId, params, &instance) } func (self *SRegion) GetInstanceScaleSets() ([]SInstance, error) { instance := []SInstance{} - return instance, self.client.ListAll("Microsoft.Compute/virtualMachineScaleSets", &instance) + return instance, self.client.list("Microsoft.Compute/virtualMachineScaleSets", url.Values{}, &instance) } func (self *SRegion) GetInstances() ([]SInstance, error) { result := []SInstance{} - instances := []SInstance{} - err := self.client.ListAll("Microsoft.Compute/virtualMachines", &instances) + resource := fmt.Sprintf("Microsoft.Compute/locations/%s/virtualMachines", self.Name) + err := self.client.list(resource, url.Values{}, &result) if err != nil { return nil, err } - for i := 0; i < len(instances); i++ { - if instances[i].Location == self.Name { - result = append(result, instances[i]) - } - } return result, nil } func (self *SRegion) doDeleteVM(instanceId string) error { - return self.client.Delete(instanceId) + return self.del(instanceId) } func (self *SInstance) GetSecurityGroupIds() ([]string, error) { secgroupIds := []string{} if nics, err := self.getNics(); err == nil { for _, nic := range nics { - if nic.Properties.NetworkSecurityGroup != nil { - if len(nic.Properties.NetworkSecurityGroup.ID) > 0 { - secgroupIds = append(secgroupIds, strings.ToLower(nic.Properties.NetworkSecurityGroup.ID)) - } + if len(nic.Properties.NetworkSecurityGroup.ID) > 0 { + secgroupIds = append(secgroupIds, strings.ToLower(nic.Properties.NetworkSecurityGroup.ID)) } } } @@ -262,10 +233,6 @@ func (self *SInstance) GetHypervisor() string { return api.HYPERVISOR_AZURE } -func (self *SInstance) IsEmulated() bool { - return false -} - func (self *SInstance) GetInstanceType() string { return self.Properties.HardwareProfile.VMSize } @@ -275,25 +242,22 @@ func (self *SInstance) WaitEnableVMAccessReady() error { return fmt.Errorf("instance may not install VMAgent or VMAgent not running") } if len(self.Properties.InstanceView.VMAgent.VmAgentVersion) > 0 { - startTime := time.Now() - timeout := time.Minute * 5 - for { - status := "" + status := "" + err := cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) { for _, vmAgent := range self.Properties.InstanceView.VMAgent.Statuses { status = vmAgent.DisplayStatus if status == "Ready" { break } log.Debugf("vmAgent %s status: %s waite for ready", self.Properties.InstanceView.VMAgent.VmAgentVersion, vmAgent.DisplayStatus) - time.Sleep(time.Second * 5) } if status == "Ready" { - break - } - self.Refresh() - if time.Now().Sub(startTime) > timeout { - return fmt.Errorf("timeout for waitting vmAgent ready, current status: %s", status) + return true, nil } + return false, self.Refresh() + }) + if err != nil { + return errors.Wrapf(err, "waitting vmAgent ready, current status: %s", status) } return nil } @@ -314,178 +278,12 @@ func (self *SInstance) WaitEnableVMAccessReady() error { return fmt.Errorf("instance may not install VMAgent or VMAgent not running") } -func (self *SInstance) getOsDisk() (*SDisk, error) { - diskId := self.Properties.StorageProfile.OsDisk.ManagedDisk.ID - if osDisk, err := self.getDiskWithStore(diskId); err != nil { - log.Errorf("Failed to find instance %s os disk: %s", self.Name, diskId) - return nil, err - } else { - return osDisk, nil - } -} - -func (self *SInstance) getStorageInfoByUri(uri string) (*SStorage, *SClassicStorage, error) { - _storageName := strings.Split(strings.Replace(uri, "https://", "", -1), ".") - storageName := "" - if len(_storageName) > 0 { - storageName = _storageName[0] - } - if len(storageName) == 0 { - return nil, nil, fmt.Errorf("bad uri %s for search storageaccount", uri) - } - storageaccounts, err := self.host.zone.region.GetClassicStorageAccounts() - if err != nil { - return nil, nil, err - } - for i := 0; i < len(storageaccounts); i++ { - if storageaccounts[i].Name == storageName { - storage := SClassicStorage{ - zone: self.host.zone, - Name: storageName, - ID: storageaccounts[i].ID, - Type: storageaccounts[i].Type, - Location: storageaccounts[i].Type, - } - return nil, &storage, nil - } - } - storageaccounts, err = self.host.zone.region.GetStorageAccounts() - if err != nil { - return nil, nil, err - } - for i := 0; i < len(storageaccounts); i++ { - if storageaccounts[i].Name == storageName { - storage := SStorage{ - zone: self.host.zone, - storageType: storageaccounts[i].Sku.Name, - } - if !utils.IsInStringArray(storage.storageType, STORAGETYPES) { - storage.storageType = STORAGE_STD_LRS - } - return &storage, nil, nil - } - } - return nil, nil, fmt.Errorf("failed to found classic storageaccount for %s", uri) -} - -type BasicDisk struct { - Name string - DiskSizeGB int32 - Caching string - CreateOption string - OsType string -} - -func (self *SInstance) getDisksByUri(uri string, disk *BasicDisk) ([]SDisk, []SClassicDisk, error) { - storage, classicStorage, err := self.getStorageInfoByUri(uri) - if err != nil { - return nil, nil, err - } - disks, classicDisks := []SDisk{}, []SClassicDisk{} - if classicStorage != nil { - classicDisks = append(classicDisks, SClassicDisk{ - storage: classicStorage, - DiskName: disk.Name, - DiskSizeGB: disk.DiskSizeGB, - Caching: disk.Caching, - VhdUri: uri, - }) - } - if storage != nil { - disks = append(disks, SDisk{ - storage: storage, - ID: uri, - Name: disk.Name, - Properties: DiskProperties{ - OsType: disk.OsType, - CreationData: CreationData{ - CreateOption: disk.CreateOption, - }, - DiskSizeGB: disk.DiskSizeGB, - }, - }) - } - return disks, classicDisks, nil -} - -func (self *SInstance) getDisks() ([]SDisk, []SClassicDisk, error) { - instance, err := self.host.zone.region.GetInstance(self.ID) - if err != nil { - return nil, nil, err - } - disks, classicDisks := []SDisk{}, []SClassicDisk{} - if instance.Properties.StorageProfile.OsDisk.Vhd != nil { - disk := self.Properties.StorageProfile.OsDisk - diskSizeGB := int32(0) - if disk.DiskSizeGB != nil { - diskSizeGB = *disk.DiskSizeGB - } - basicDisk := &BasicDisk{ - Name: disk.Name, - DiskSizeGB: diskSizeGB, - Caching: disk.Caching, - CreateOption: disk.CreateOption, - OsType: disk.OsType, - } - _disks, _classicDisks, err := self.getDisksByUri(disk.Vhd.Uri, basicDisk) - if err != nil { - return nil, nil, err - } - disks = append(disks, _disks...) - classicDisks = append(classicDisks, _classicDisks...) - } else if instance.Properties.StorageProfile.OsDisk.ManagedDisk != nil { - disk, err := self.getDiskWithStore(self.Properties.StorageProfile.OsDisk.ManagedDisk.ID) - if err != nil { - log.Errorf("Failed to find instance %s os disk: %s", self.Name, self.Properties.StorageProfile.OsDisk.ManagedDisk.ID) - return nil, nil, err - } - disks = append(disks, *disk) - } - for _, _disk := range instance.Properties.StorageProfile.DataDisks { - diskSizeGB := int32(0) - if _disk.DiskSizeGB != nil { - diskSizeGB = *_disk.DiskSizeGB - } else { - if _disk.ManagedDisk != nil { - disk, err := self.host.zone.region.GetDisk(_disk.ManagedDisk.ID) - if err != nil { - diskSizeGB = disk.Properties.DiskSizeGB - } - } - } - if _disk.Vhd != nil { - basicDisk := &BasicDisk{ - Name: _disk.Name, - DiskSizeGB: diskSizeGB, - Caching: _disk.Caching, - CreateOption: _disk.CreateOption, - } - _disks, _classicDisks, err := self.getDisksByUri(_disk.Vhd.Uri, basicDisk) - if err != nil { - return nil, nil, err - } - disks = append(disks, _disks...) - classicDisks = append(classicDisks, _classicDisks...) - } else if _disk.ManagedDisk != nil { - disk, err := self.getDiskWithStore(_disk.ManagedDisk.ID) - if err != nil { - log.Errorf("Failed to find instance %s os disk: %s", self.Name, _disk.ManagedDisk.ID) - return nil, nil, err - } - disks = append(disks, *disk) - } - } - - return disks, classicDisks, nil -} - func (self *SInstance) getNics() ([]SInstanceNic, error) { nics := []SInstanceNic{} for _, _nic := range self.Properties.NetworkProfile.NetworkInterfaces { - nic, err := self.host.zone.region.GetNetworkInterfaceDetail(_nic.ID) + nic, err := self.host.zone.region.GetNetworkInterface(_nic.ID) if err != nil { - log.Errorf("Failed to find instance %s nic: %s", self.Name, _nic.ID) - return nil, err + return nil, errors.Wrapf(err, "GetNetworkInterface(%s)", _nic.ID) } nic.instance = self nics = append(nics, *nic) @@ -541,118 +339,93 @@ func (self *SInstance) GetIHost() cloudprovider.ICloudHost { } func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { - status := self.GetStatus() - if err := self.host.zone.region.AttachDisk(self.ID, diskId); err != nil { - return err - } - return cloudprovider.WaitStatus(self, status, 10*time.Second, 300*time.Second) + return self.host.zone.region.AttachDisk(self.ID, diskId) } func (region *SRegion) AttachDisk(instanceId, diskId string) error { - disk, err := region.GetDisk(diskId) - if err != nil { - return err - } instance, err := region.GetInstance(instanceId) if err != nil { - return err + return errors.Wrapf(err, "GetInstance(%s)", instanceId) } - dataDisks := instance.Properties.StorageProfile.DataDisks - lun, find := -1, false - for i := 0; i < len(dataDisks); i++ { - if dataDisks[i].Lun != int32(i) { - lun, find = i, true - break + dataDisks := jsonutils.NewArray() + for _, disk := range instance.Properties.StorageProfile.DataDisks { + if disk.ManagedDisk != nil && strings.ToLower(disk.ManagedDisk.ID) == strings.ToLower(diskId) { + return nil } + dataDisks.Add(jsonutils.Marshal(disk)) } - - if !find || lun == -1 { - lun = len(dataDisks) - } - - dataDisks = append(dataDisks, DataDisk{ - Lun: int32(lun), - CreateOption: "Attach", - ManagedDisk: &ManagedDiskParameters{ - ID: disk.ID, + dataDisks.Add(jsonutils.Marshal(map[string]interface{}{ + "Lun": len(instance.Properties.StorageProfile.DataDisks), + "CreateOption": "Attach", + "ManagedDisk": map[string]string{ + "Id": diskId, }, - }) - instance.Properties.StorageProfile.DataDisks = dataDisks - instance.Properties.ProvisioningState = "" - instance.Properties.InstanceView = nil - return region.client.Update(jsonutils.Marshal(instance), nil) + })) + params := jsonutils.NewDict() + params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks") + params.Add(jsonutils.Marshal(instance.Properties.StorageProfile.OsDisk), "Properties", "StorageProfile", "OsDisk") + _, err = region.patch(instanceId, params) + return err } func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { - status := self.GetStatus() - if err := self.host.zone.region.DetachDisk(self.ID, diskId); err != nil { - return err - } - return cloudprovider.WaitStatus(self, status, 10*time.Second, 300*time.Second) + return self.host.zone.region.DetachDisk(self.ID, diskId) } func (region *SRegion) DetachDisk(instanceId, diskId string) error { instance, err := region.GetInstance(instanceId) if err != nil { - return err + return errors.Wrapf(err, "GetInstance(%s)", instanceId) } - disk, err := region.GetDisk(diskId) - if err != nil { - return err - } - dataDisks := []DataDisk{} - for _, origDisk := range instance.Properties.StorageProfile.DataDisks { - if strings.ToLower(origDisk.ManagedDisk.ID) != strings.ToLower(disk.ID) { - dataDisks = append(dataDisks, origDisk) + find := false + dataDisks := jsonutils.NewArray() + for _, disk := range instance.Properties.StorageProfile.DataDisks { + if disk.ManagedDisk != nil && strings.ToLower(disk.ManagedDisk.ID) == strings.ToLower(diskId) { + find = true + continue } + disk.Lun = int32(dataDisks.Length()) + dataDisks.Add(jsonutils.Marshal(disk)) } - instance.Properties.StorageProfile.DataDisks = dataDisks - instance.Properties.ProvisioningState = "" - instance.Properties.InstanceView = nil - return region.client.Update(jsonutils.Marshal(instance), nil) + if !find { + return nil + } + params := jsonutils.NewDict() + params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks") + params.Add(jsonutils.Marshal(instance.Properties.StorageProfile.OsDisk), "Properties", "StorageProfile", "OsDisk") + _, err = region.patch(instanceId, params) + return err } func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { if len(config.InstanceType) > 0 { - return self.ChangeConfig2(ctx, config.InstanceType) + return self.host.zone.region.ChangeConfig(self.ID, config.InstanceType) } var err error - status := self.GetStatus() for _, vmSize := range self.host.zone.region.getHardwareProfile(config.Cpu, config.MemoryMB) { - self.Properties.HardwareProfile.VMSize = vmSize - self.Properties.ProvisioningState = "" - self.Properties.InstanceView = nil log.Debugf("Try HardwareProfile : %s", vmSize) - err = self.host.zone.region.client.Update(jsonutils.Marshal(self), nil) + err = self.host.zone.region.ChangeConfig(self.ID, vmSize) if err == nil { - return cloudprovider.WaitStatus(self, status, 10*time.Second, 300*time.Second) + return nil } } if err != nil { - return errors.Wrap(err, "client.Update") + return errors.Wrap(err, "ChangeConfig") } return fmt.Errorf("Failed to change vm config, specification not supported") } -func (self *SInstance) ChangeConfig2(ctx context.Context, instanceType string) error { - status := self.GetStatus() - self.Properties.HardwareProfile.VMSize = instanceType - self.Properties.ProvisioningState = "" - self.Properties.InstanceView = nil +func (self *SRegion) ChangeConfig(instanceId, instanceType string) error { + params := map[string]interface{}{ + "Properties": map[string]interface{}{ + "HardwareProfile": map[string]string{ + "vmSize": instanceType, + }, + }, + } log.Debugf("Try HardwareProfile : %s", instanceType) - err := self.host.zone.region.client.Update(jsonutils.Marshal(self), nil) - if err != nil { - return errors.Wrap(err, "client.Update") - } - return cloudprovider.WaitStatus(self, status, 10*time.Second, 300*time.Second) -} - -func (region *SRegion) ChangeVMConfig2(ctx context.Context, instanceId string, instanceType string) error { - instance, err := region.GetInstance(instanceId) - if err != nil { - return err - } - return instance.ChangeConfig2(ctx, instanceType) + _, err := self.patch(instanceId, jsonutils.Marshal(params)) + return err } func (region *SRegion) ChangeVMConfig(ctx context.Context, instanceId string, ncpu int, vmem int) error { @@ -697,8 +470,8 @@ func (region *SRegion) execOnLinux(instanceId string, command string) error { Settings: map[string]string{"commandToExecute": command}, }, } - url := fmt.Sprintf("%s/extensions/CustomScript", instanceId) - _, err := region.client.jsonRequest("PUT", url, jsonutils.Marshal(extension).String()) + resource := fmt.Sprintf("%s/extensions/CustomScript", instanceId) + _, err := region.put(resource, jsonutils.Marshal(extension)) return err } @@ -715,7 +488,7 @@ func (region *SRegion) resetOvsEnv(instanceId string) error { } func (region *SRegion) deleteExtension(instanceId, extensionName string) error { - return region.client.Delete(fmt.Sprintf("%s/extensions/%s", instanceId, extensionName)) + return region.del(fmt.Sprintf("%s/extensions/%s", instanceId, extensionName)) } func (region *SRegion) resetLoginInfo(instanceId string, setting map[string]string) error { extension := SVirtualMachineExtension{ @@ -727,8 +500,8 @@ func (region *SRegion) resetLoginInfo(instanceId string, setting map[string]stri ProtectedSettings: setting, }, } - url := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId) - _, err := region.client.jsonRequest("PUT", url, jsonutils.Marshal(extension).String()) + resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId) + _, err := region.put(resource, jsonutils.Marshal(extension)) if err != nil { err = region.deleteExtension(instanceId, "enablevmaccess") if err != nil { @@ -738,8 +511,8 @@ func (region *SRegion) resetLoginInfo(instanceId string, setting map[string]stri if err != nil { return err } - url := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId) - _, err = region.client.jsonRequest("PUT", url, jsonutils.Marshal(extension).String()) + resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId) + _, err = region.put(resource, jsonutils.Marshal(extension)) return err } return nil @@ -781,7 +554,10 @@ func (region *SRegion) DeployVM(ctx context.Context, instanceId, name, password, func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { cpu := self.GetVcpuCount() memoryMb := self.GetVmemSizeMB() - self.StopVM(ctx, true) + opts := &cloudprovider.ServerStopOptions{ + IsForce: true, + } + self.StopVM(ctx, opts) return self.host.zone.region.ReplaceSystemDisk(self, cpu, memoryMb, desc.ImageId, desc.Password, desc.PublicKey, desc.SysSizeGB) } @@ -799,7 +575,7 @@ func (region *SRegion) ReplaceSystemDisk(instance *SInstance, cpu int, memoryMb return "", fmt.Errorf("failed to find network for instance: %s", instance.Name) } nicId := instance.Properties.NetworkProfile.NetworkInterfaces[0].ID - nic, err := region.GetNetworkInterfaceDetail(nicId) + nic, err := region.GetNetworkInterface(nicId) if err != nil { log.Errorf("failed to find nic %s error: %v", nicId, err) return "", err @@ -807,8 +583,8 @@ func (region *SRegion) ReplaceSystemDisk(instance *SInstance, cpu int, memoryMb if len(nic.Properties.IPConfigurations) == 0 { return "", fmt.Errorf("failed to find networkId for nic %s", nicId) } - if instance.Properties.StorageProfile.OsDisk.DiskSizeGB != nil && *instance.Properties.StorageProfile.OsDisk.DiskSizeGB > int32(sysSizeGB) { - sysSizeGB = int(*instance.Properties.StorageProfile.OsDisk.DiskSizeGB) + if instance.Properties.StorageProfile.OsDisk.DiskSizeGB.Int32() > int32(sysSizeGB) { + sysSizeGB = int(instance.Properties.StorageProfile.OsDisk.DiskSizeGB.Int32()) } image, err := region.GetImageById(imageId) if err != nil { @@ -827,12 +603,15 @@ func (region *SRegion) ReplaceSystemDisk(instance *SInstance, cpu int, memoryMb sysSizeGB = 128 } - newInstance, err := region.CreateInstanceSimple(instance.Name+"-1", imageId, osType, cpu, memoryMb, sysSizeGB, storageType, []int{}, networkId, passwd, publicKey) + newInstance, err := region.CreateInstanceSimple(instance.Name, imageId, osType, cpu, memoryMb, sysSizeGB, storageType, []int{}, networkId, passwd, publicKey) if err != nil { return "", err } - newInstance.StopVM(context.Background(), true) + opts := &cloudprovider.ServerStopOptions{ + IsForce: true, + } + newInstance.StopVM(context.Background(), opts) cloudprovider.WaitStatus(newInstance, api.VM_READY, time.Second*5, time.Minute*5) newInstance.deleteVM(context.Background(), true) @@ -844,7 +623,7 @@ func (region *SRegion) ReplaceSystemDisk(instance *SInstance, cpu int, memoryMb instance.Properties.ProvisioningState = "" instance.Properties.InstanceView = nil instance.Properties.VmId = "" - err = region.client.Update(jsonutils.Marshal(instance), nil) + err = region.update(jsonutils.Marshal(instance), nil) if err != nil { // 更新失败,需要删除之前交换过的系统盘 region.DeleteDisk(instance.Properties.StorageProfile.OsDisk.ManagedDisk.ID) @@ -885,7 +664,7 @@ func (self *SInstance) deleteVM(ctx context.Context, keepSysDisk bool) error { return err } if len(sysDiskId) > 0 && !keepSysDisk { - err := self.host.zone.region.deleteDisk(sysDiskId) + err := self.host.zone.region.DeleteDisk(sysDiskId) if err != nil { return err } @@ -909,31 +688,15 @@ func (self *SInstance) DeleteVM(ctx context.Context) error { return self.deleteVM(ctx, false) } -func (self *SInstance) getDiskWithStore(diskId string) (*SDisk, error) { - if disk, err := self.host.zone.region.GetDisk(diskId); err != nil { - return nil, err - } else if store, err := self.host.zone.getStorageByType(string(disk.Sku.Name)); err != nil { - log.Errorf("fail to find storage for disk(%s) : %v", disk.Name, err) - return nil, err - } else { - disk.storage = store - return disk, nil - } -} - func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { - disks, classicDisks, err := self.getDisks() - if err != nil { - return nil, err + disks := []cloudprovider.ICloudDisk{} + self.Properties.StorageProfile.OsDisk.region = self.host.zone.region + disks = append(disks, &self.Properties.StorageProfile.OsDisk) + for i := range self.Properties.StorageProfile.DataDisks { + self.Properties.StorageProfile.DataDisks[i].region = self.host.zone.region + disks = append(disks, &self.Properties.StorageProfile.DataDisks[i]) } - idisks := make([]cloudprovider.ICloudDisk, len(disks)+len(classicDisks)) - for i := 0; i < len(disks); i++ { - idisks[i] = &disks[i] - } - for i := 0; i < len(classicDisks); i++ { - idisks[len(disks)+i] = &classicDisks[i] - } - return idisks, nil + return disks, nil } func (self *SInstance) GetOSType() string { @@ -1020,11 +783,13 @@ func (self *SInstance) GetVdi() string { } func (self *SInstance) fetchVMSize() error { - vmSize, err := self.host.zone.region.getVMSize(self.Properties.HardwareProfile.VMSize) - if err != nil { - return err + if self.vmSize == nil { + vmSize, err := self.host.zone.region.getVMSize(self.Properties.HardwareProfile.VMSize) + if err != nil { + return err + } + self.vmSize = vmSize } - self.vmSize = vmSize return nil } @@ -1052,7 +817,7 @@ func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { } func (self *SRegion) StartVM(instanceId string) error { - _, err := self.client.PerformAction(instanceId, "start", "") + _, err := self.perform(instanceId, "start", nil) return err } @@ -1060,21 +825,21 @@ func (self *SInstance) StartVM(ctx context.Context) error { if err := self.host.zone.region.StartVM(self.ID); err != nil { return err } - self.host.zone.region.client.jsonRequest("PATCH", self.ID, jsonutils.Marshal(self).String()) + self.host.zone.region.patch(self.ID, jsonutils.Marshal(self)) return cloudprovider.WaitStatus(self, api.VM_RUNNING, 10*time.Second, 300*time.Second) } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { - err := self.host.zone.region.StopVM(self.ID, isForce) +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := self.host.zone.region.StopVM(self.ID, opts.IsForce) if err != nil { return err } - self.host.zone.region.client.jsonRequest("PATCH", self.ID, jsonutils.Marshal(self).String()) + self.host.zone.region.patch(self.ID, jsonutils.Marshal(self)) return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) } func (self *SRegion) StopVM(instanceId string, isForce bool) error { - _, err := self.client.PerformAction(instanceId, "deallocate", "") + _, err := self.perform(instanceId, "deallocate", nil) return err } @@ -1085,13 +850,13 @@ func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { } for _, nic := range nics { for _, ip := range nic.Properties.IPConfigurations { - if ip.Properties.PublicIPAddress != nil { - if len(ip.Properties.PublicIPAddress.ID) > 0 { - eip, err := self.host.zone.region.GetEip(ip.Properties.PublicIPAddress.ID) - if err == nil { - return eip, nil - } - log.Errorf("find eip for instance %s failed: %v", self.Name, err) + if ip.Properties.PublicIPAddress != nil && len(ip.Properties.PublicIPAddress.ID) > 0 { + eip, err := self.host.zone.region.GetEip(ip.Properties.PublicIPAddress.ID) + if err != nil { + return nil, errors.Wrapf(err, "GetEip(%s)", ip.Properties.PublicIPAddress.ID) + } + if len(eip.Properties.IPAddress) > 0 { + return eip, nil } } } diff --git a/pkg/multicloud/azure/instancenic.go b/pkg/multicloud/azure/instancenic.go index 123a3e516e..a1d9abc450 100644 --- a/pkg/multicloud/azure/instancenic.go +++ b/pkg/multicloud/azure/instancenic.go @@ -15,13 +15,16 @@ package azure import ( + "net/url" "strings" "yunion.io/x/jsonutils" - "yunion.io/x/log" + "yunion.io/x/pkg/errors" "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" ) type PublicIPAddress struct { @@ -35,8 +38,8 @@ type InterfaceIPConfigurationPropertiesFormat struct { PrivateIPAddress string `json:"privateIPAddress,omitempty"` PrivateIPAddressVersion string `json:"privateIPAddressVersion,omitempty"` PrivateIPAllocationMethod string `json:"privateIPAllocationMethod,omitempty"` - Subnet Subnet `json:"subnet,omitempty"` - Primary *bool `json:"primary,omitempty"` + Subnet SNetwork `json:"subnet,omitempty"` + Primary bool `json:"primary,omitempty"` PublicIPAddress *PublicIPAddress `json:"publicIPAddress,omitempty"` } @@ -47,14 +50,16 @@ type InterfaceIPConfiguration struct { } type InterfacePropertiesFormat struct { - NetworkSecurityGroup *SSecurityGroup `json:"networkSecurityGroup,omitempty"` + NetworkSecurityGroup SSecurityGroup `json:"networkSecurityGroup,omitempty"` IPConfigurations []InterfaceIPConfiguration `json:"ipConfigurations,omitempty"` MacAddress string `json:"macAddress,omitempty"` - Primary *bool `json:"primary,omitempty"` - VirtualMachine *SubResource `json:"virtualMachine,omitempty"` + Primary bool `json:"primary,omitempty"` + VirtualMachine SubResource `json:"virtualMachine,omitempty"` } type SInstanceNic struct { + multicloud.SResourceBase + instance *SInstance ID string Name string @@ -71,7 +76,7 @@ func (self *SInstanceNic) GetIP() string { } func (region *SRegion) DeleteNetworkInterface(interfaceId string) error { - return region.client.Delete(interfaceId) + return region.del(interfaceId) } func (self *SInstanceNic) Delete() error { @@ -97,11 +102,10 @@ func (self *SInstanceNic) InClassicNetwork() bool { func (self *SInstanceNic) updateSecurityGroup(secgroupId string) error { region := self.instance.host.zone.region - self.Properties.NetworkSecurityGroup = nil if len(secgroupId) > 0 { - self.Properties.NetworkSecurityGroup = &SSecurityGroup{ID: secgroupId} + self.Properties.NetworkSecurityGroup = SSecurityGroup{ID: secgroupId} } - return region.client.Update(jsonutils.Marshal(self), nil) + return region.update(jsonutils.Marshal(self), nil) } func (self *SInstanceNic) revokeSecurityGroup() error { @@ -113,69 +117,125 @@ func (self *SInstanceNic) assignSecurityGroup(secgroupId string) error { } func (self *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork { - wires, err := self.instance.host.GetIWires() - if err != nil { - log.Errorf("GetINetwork error: %v", err) - return nil - } - for i := 0; i < len(wires); i++ { - wire := wires[i].(*SWire) - if len(self.Properties.IPConfigurations) > 0 { - network := wire.getNetworkById(self.Properties.IPConfigurations[0].Properties.Subnet.ID) - if network != nil { - return network - } + if len(self.Properties.IPConfigurations) > 0 { + network, err := self.instance.host.zone.region.GetNetwork(self.Properties.IPConfigurations[0].Properties.Subnet.ID) + if err != nil { + return nil } + return network } return nil } -func (self *SRegion) GetNetworkInterfaceDetail(interfaceId string) (*SInstanceNic, error) { +func (self *SRegion) GetNetworkInterface(interfaceId string) (*SInstanceNic, error) { instancenic := SInstanceNic{} - return &instancenic, self.client.Get(interfaceId, []string{}, &instancenic) + return &instancenic, self.get(interfaceId, url.Values{}, &instancenic) } func (self *SRegion) GetNetworkInterfaces() ([]SInstanceNic, error) { interfaces := []SInstanceNic{} - err := self.client.ListAll("Microsoft.Network/networkInterfaces", &interfaces) + err := self.list("Microsoft.Network/networkInterfaces", url.Values{}, &interfaces) if err != nil { return nil, err } - result := []SInstanceNic{} - for i := 0; i < len(interfaces); i++ { - if interfaces[i].Location == self.Name { - result = append(result, interfaces[i]) - } - } - return result, nil + return interfaces, nil } func (self *SRegion) CreateNetworkInterface(resourceGroup string, nicName string, ipAddr string, subnetId string, secgrpId string) (*SInstanceNic, error) { - instancenic := SInstanceNic{ - Name: nicName, - Location: self.Name, - Properties: InterfacePropertiesFormat{ - IPConfigurations: []InterfaceIPConfiguration{ - { - Name: nicName, - Properties: InterfaceIPConfigurationPropertiesFormat{ - PrivateIPAddress: ipAddr, - PrivateIPAddressVersion: "IPv4", - PrivateIPAllocationMethod: "Static", - Subnet: Subnet{ - ID: subnetId, + allocMethod := "Static" + if len(ipAddr) == 0 { + allocMethod = "Dynamic" + } + params := jsonutils.Marshal(map[string]interface{}{ + "Name": nicName, + "Location": self.Name, + "Properties": map[string]interface{}{ + "IPConfigurations": []map[string]interface{}{ + map[string]interface{}{ + "Name": nicName, + "Properties": map[string]interface{}{ + "PrivateIPAddress": ipAddr, + "PrivateIPAddressVersion": "IPv4", + "PrivateIPAllocationMethod": allocMethod, + "Subnet": map[string]string{ + "Id": subnetId, }, }, }, }, - NetworkSecurityGroup: &SSecurityGroup{ID: secgrpId}, }, - Type: "Microsoft.Network/networkInterfaces", + "Type": "Microsoft.Network/networkInterfaces", + }).(*jsonutils.JSONDict) + if len(secgrpId) > 0 { + params.Add(jsonutils.Marshal(map[string]string{"id": secgrpId}), "Properties", "NetworkSecurityGroup") } + nic := SInstanceNic{} + return &nic, self.create(resourceGroup, params, &nic) +} - if len(ipAddr) == 0 { - instancenic.Properties.IPConfigurations[0].Properties.PrivateIPAllocationMethod = "Dynamic" +func (self *SRegion) GetINetworkInterfaces() ([]cloudprovider.ICloudNetworkInterface, error) { + nics, err := self.GetNetworkInterfaces() + if err != nil { + return nil, errors.Wrapf(err, "GetNetworkInterfaces") } + ret := []cloudprovider.ICloudNetworkInterface{} + for i := range nics { + if len(nics[i].Properties.VirtualMachine.ID) > 0 { + continue + } + ret = append(ret, &nics[i]) + } + return ret, nil +} + +func (self *SInstanceNic) GetAssociateId() string { + return "" +} + +func (self *SInstanceNic) GetAssociateType() string { + return "" +} + +func (self *SInstanceNic) GetId() string { + return self.ID +} + +func (self *SInstanceNic) GetName() string { + return self.Name +} + +func (self *SInstanceNic) GetStatus() string { + return api.NETWORK_INTERFACE_STATUS_AVAILABLE +} + +func (self *SInstanceNic) GetGlobalId() string { + return strings.ToLower(self.ID) +} + +func (self *SInstanceNic) GetMacAddress() string { + return self.Properties.MacAddress +} + +func (self *SInstanceNic) GetICloudInterfaceAddresses() ([]cloudprovider.ICloudInterfaceAddress, error) { + addrs := []cloudprovider.ICloudInterfaceAddress{} + for i := range self.Properties.IPConfigurations { + addrs = append(addrs, &self.Properties.IPConfigurations[i]) + } + return addrs, nil +} + +func (self *InterfaceIPConfiguration) GetGlobalId() string { + return strings.ToLower(self.ID) +} + +func (self *InterfaceIPConfiguration) GetINetworkId() string { + return strings.ToLower(self.Properties.Subnet.ID) +} + +func (self *InterfaceIPConfiguration) GetIP() string { + return self.Properties.PrivateIPAddress +} - return &instancenic, self.client.CreateWithResourceGroup(resourceGroup, jsonutils.Marshal(&instancenic), &instancenic) +func (self *InterfaceIPConfiguration) IsPrimary() bool { + return self.Properties.Primary } diff --git a/pkg/multicloud/azure/monitor.go b/pkg/multicloud/azure/monitor.go index bfb533a47f..b9f385ed9e 100644 --- a/pkg/multicloud/azure/monitor.go +++ b/pkg/multicloud/azure/monitor.go @@ -15,6 +15,8 @@ package azure import ( + "fmt" + "net/url" "time" ) @@ -85,22 +87,18 @@ type MetricValue struct { func (self *SRegion) GetMonitorData(name string, ns string, external_id string, since time.Time, until time.Time) (*ResponseMetirc, error) { - params := map[string]string{ - "metricnamespace": ns, - "metricnames": name, - "interval": "PT1M", - "aggregation": "Average", - "api-version": "2018-01-01", - } + params := url.Values{} + params.Set("metricnamespace", ns) + params.Set("metricnames", name) + params.Set("interval", "PT1M") + params.Set("aggregation", "Average") + params.Set("api-version", "2018-01-01") if !since.IsZero() && !until.IsZero() { - params["timespan"] = since.UTC().Format(time.RFC3339) + "/" + until.UTC().Format(time.RFC3339) - } - rtn, err := self.client.ListResourcesOfMetirc("microsoft.insights/metrics", external_id, params) - if err != nil { - return nil, err + params.Set("timespan", since.UTC().Format(time.RFC3339)+"/"+until.UTC().Format(time.RFC3339)) } + resource := fmt.Sprintf("%s/provider/microsoft.insights/metrics") elements := ResponseMetirc{} - err = rtn.Unmarshal(&elements) + err := self.get(resource, params, &elements) if err != nil { return nil, err } diff --git a/pkg/multicloud/azure/network.go b/pkg/multicloud/azure/network.go index 2486d6c932..ff55d988c9 100644 --- a/pkg/multicloud/azure/network.go +++ b/pkg/multicloud/azure/network.go @@ -22,21 +22,19 @@ import ( 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 wire *SWire - AvailableIpAddressCount *int `json:"availableIpAddressCount,omitempty"` - ID string - Name string - Properties SubnetPropertiesFormat - AddressPrefix string `json:"addressPrefix,omitempty"` -} - -func (self *SNetwork) GetMetadata() *jsonutils.JSONDict { - return nil + //AvailableIpAddressCount int `json:"availableIpAddressCount,omitempty"` + ID string + Name string + Properties SubnetPropertiesFormat + AddressPrefix string `json:"addressPrefix,omitempty"` } func (self *SNetwork) GetId() string { @@ -51,29 +49,12 @@ func (self *SNetwork) GetGlobalId() string { return strings.ToLower(self.ID) } -func (self *SNetwork) IsEmulated() bool { - return false -} - func (self *SNetwork) GetStatus() string { - return "available" + return api.NETWORK_STATUS_AVAILABLE } func (self *SNetwork) Delete() error { - vpc := self.wire.vpc - subnets := []SNetwork{} - if vpc.Properties.Subnets != nil { - for i := 0; i < len(*vpc.Properties.Subnets); i++ { - if (*vpc.Properties.Subnets)[i].Name == self.Name { - continue - } - subnets = append(subnets, (*vpc.Properties.Subnets)[i]) - } - vpc.Properties.Subnets = &subnets - vpc.Properties.ProvisioningState = "" - return self.wire.vpc.region.client.Update(jsonutils.Marshal(vpc), nil) - } - return nil + return self.wire.vpc.region.del(self.ID) } func (self *SNetwork) GetGateway() string { @@ -123,11 +104,11 @@ func (self *SNetwork) GetServerType() string { } func (self *SNetwork) Refresh() error { - if new, err := self.wire.zone.region.GetNetworkDetail(self.ID); err != nil { + network, err := self.wire.zone.region.GetNetwork(self.ID) + if err != nil { return err - } else { - return jsonutils.Update(self, new) } + return jsonutils.Update(self, network) } func (self *SNetwork) GetAllocTimeoutSeconds() int { diff --git a/pkg/multicloud/azure/os_disk.go b/pkg/multicloud/azure/os_disk.go new file mode 100644 index 0000000000..1eaa2ac73b --- /dev/null +++ b/pkg/multicloud/azure/os_disk.go @@ -0,0 +1,161 @@ +// 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 azure + +import ( + "context" + "strings" + + "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 SOsDisk struct { + multicloud.SDisk + region *SRegion + + OsType string `json:"osType,omitempty"` + Caching string `json:"caching,omitempty"` + Name string + DiskSizeGB TAzureInt32 `json:"diskSizeGB,omitempty"` + ManagedDisk *ManagedDiskParameters `json:"managedDisk,omitempty"` + CreateOption string `json:"createOption,omitempty"` + Vhd *VirtualHardDisk `json:"vhd,omitempty"` +} + +func (self *SOsDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { + if self.ManagedDisk != nil { + snapshot, err := self.region.CreateSnapshot(self.ManagedDisk.ID, name, desc) + if err != nil { + return nil, errors.Wrapf(err, "CreateSnapshot") + } + return snapshot, nil + } + return nil, cloudprovider.ErrNotSupported +} + +func (self *SOsDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + return []cloudprovider.ICloudSnapshot{}, nil +} + +func (self *SOsDisk) Delete(ctx context.Context) error { + if self.ManagedDisk != nil { + return self.region.del(self.ManagedDisk.ID) + } + return cloudprovider.ErrNotSupported +} + +func (self *SOsDisk) GetStatus() string { + return api.DISK_READY +} + +func (self *SOsDisk) GetId() string { + if self.ManagedDisk != nil { + return strings.ToLower(self.ManagedDisk.ID) + } + return self.Vhd.Uri +} + +func (self *SOsDisk) GetGlobalId() string { + return self.GetId() +} + +func (self *SOsDisk) GetName() string { + return self.Name +} + +func (self *SOsDisk) Resize(ctx context.Context, sizeMb int64) error { + if self.ManagedDisk != nil { + return self.region.ResizeDisk(self.ManagedDisk.ID, int32(sizeMb/1024)) + } + return cloudprovider.ErrNotSupported +} + +func (self *SOsDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { + storageType := "Standard_LRS" + if self.ManagedDisk != nil && len(self.ManagedDisk.StorageAccountType) > 0 { + storageType = self.ManagedDisk.StorageAccountType + } + return &SStorage{storageType: storageType, zone: self.region.getZone()}, nil +} + +func (self *SOsDisk) GetFsFormat() string { + return "" +} + +func (self *SOsDisk) GetIsNonPersistent() bool { + return false +} + +func (self *SOsDisk) GetDriver() string { + return "scsi" +} + +func (self *SOsDisk) GetCacheMode() string { + return "none" +} + +func (self *SOsDisk) GetMountpoint() string { + return "" +} + +func (self *SOsDisk) GetDiskFormat() string { + return "vhd" +} + +func (self *SOsDisk) GetDiskSizeMB() int { + return int(self.DiskSizeGB.Int32()) * 1024 +} + +func (self *SOsDisk) GetIsAutoDelete() bool { + return true +} + +func (self *SOsDisk) GetTemplateId() string { + if self.ManagedDisk != nil { + disk, err := self.region.GetDisk(self.ManagedDisk.ID) + if err == nil { + return disk.GetTemplateId() + } + } + return "" +} + +func (self *SOsDisk) Reset(ctx context.Context, snapshotId string) (string, error) { + return "", cloudprovider.ErrNotSupported +} + +func (self *SOsDisk) GetDiskType() string { + return api.DISK_TYPE_SYS +} + +func (disk *SOsDisk) GetAccessPath() string { + return "" +} + +func (self *SOsDisk) Rebuild(ctx context.Context) error { + // TODO + return cloudprovider.ErrNotSupported +} + +func (self *SOsDisk) GetProjectId() string { + if self.ManagedDisk != nil { + return getResourceGroup(self.ManagedDisk.ID) + } + return "" +} diff --git a/pkg/multicloud/azure/policy.go b/pkg/multicloud/azure/policy.go index 495072243c..135911a06f 100644 --- a/pkg/multicloud/azure/policy.go +++ b/pkg/multicloud/azure/policy.go @@ -72,7 +72,7 @@ type SPolicyDefinition struct { func (client *SAzureClient) GetPolicyDefinitions() ([]SPolicyDefinition, error) { definitions := []SPolicyDefinition{} - err := client.ListAll("Microsoft.Authorization/policyDefinitions", &definitions) + err := client.list("Microsoft.Authorization/policyDefinitions", url.Values{}, &definitions) if err != nil { return nil, errors.Wrap(err, "Microsoft.Authorization/policyDefinitions.List") } @@ -81,7 +81,7 @@ func (client *SAzureClient) GetPolicyDefinitions() ([]SPolicyDefinition, error) func (client *SAzureClient) GetPolicyDefinition(id string) (*SPolicyDefinition, error) { definition := &SPolicyDefinition{} - err := client.Get(id, []string{}, definition) + err := client.get(id, url.Values{}, definition) if err != nil { return nil, errors.Wrapf(err, "get %s", id) } @@ -129,10 +129,11 @@ func (assignment *SPolicyAssignment) GetParameters() *jsonutils.JSONDict { func (client *SAzureClient) GetPolicyAssignments(defineId string) ([]SPolicyAssignment, error) { assignments := []SPolicyAssignment{} resource := "Microsoft.Authorization/policyAssignments" + params := url.Values{} if len(defineId) > 0 { - resource += ("?$filter=" + url.PathEscape("policyDefinitionId eq ") + fmt.Sprintf("'%s'", defineId)) + params.Set("$filter", fmt.Sprintf(`policyDefinitionId eq '%s'`, defineId)) } - err := client.ListAll(resource, &assignments) + err := client.list(resource, params, &assignments) if err != nil { return nil, errors.Wrap(err, "Microsoft.Authorization/policyAssignments.List") } diff --git a/pkg/multicloud/azure/region.go b/pkg/multicloud/azure/region.go index 6b07e03626..9bdbc6c495 100644 --- a/pkg/multicloud/azure/region.go +++ b/pkg/multicloud/azure/region.go @@ -16,6 +16,7 @@ package azure import ( "fmt" + "net/url" "strconv" "strings" @@ -26,7 +27,6 @@ import ( 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/seclib2" ) type SVMSize struct { @@ -42,18 +42,13 @@ type SRegion struct { multicloud.SRegion client *SAzureClient - izones []cloudprovider.ICloudZone - ivpcs []cloudprovider.ICloudVpc - iclassicVpcs []cloudprovider.ICloudVpc - storageCache *SStoragecache - ID string - SubscriptionID string - Name string - DisplayName string - Latitude string - Longitude string + ID string + Name string + DisplayName string + Latitude string + Longitude string } func (self *SRegion) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { @@ -70,50 +65,37 @@ func (self *SRegion) GetClient() *SAzureClient { return self.client } -func (self *SRegion) GetVMSize(location string) (map[string]SVMSize, error) { - if len(location) == 0 { - location = self.Name - } - body, err := self.client.ListVmSizes(location) - if err != nil { - return nil, err - } - vmSizes := []SVMSize{} - err = body.Unmarshal(&vmSizes, "value") - if err != nil { - return nil, err - } - result := map[string]SVMSize{} - for i := 0; i < len(vmSizes); i++ { - result[vmSizes[i].Name] = vmSizes[i] - } - return result, nil +func (self *SRegion) ListVmSizes() ([]SVMSize, error) { + result := []SVMSize{} + resource := fmt.Sprintf("Microsoft.Compute/locations/%s/vmSizes", self.Name) + return result, self.client.list(resource, url.Values{}, &result) } func (self *SRegion) getHardwareProfile(cpu, memMB int) []string { - if vmSizes, err := self.GetVMSize(""); err != nil { + vmSizes, err := self.ListVmSizes() + if err != nil { return []string{} - } else { - profiles := make([]string, 0) - for vmSize, info := range vmSizes { - if info.MemoryInMB == int32(memMB) && info.NumberOfCores == cpu { - profiles = append(profiles, vmSize) - } - } - return profiles } + result := []string{} + for i := range vmSizes { + if vmSizes[i].MemoryInMB == int32(memMB) && vmSizes[i].NumberOfCores == cpu { + result = append(result, vmSizes[i].Name) + } + } + return result } -func (self *SRegion) getVMSize(size string) (*SVMSize, error) { - vmSizes, err := self.GetVMSize("") +func (self *SRegion) getVMSize(name string) (*SVMSize, error) { + vmSizes, err := self.ListVmSizes() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "ListVmSizes") } - vmSize, ok := vmSizes[size] - if !ok { - return nil, cloudprovider.ErrNotFound + for i := range vmSizes { + if vmSizes[i].Name == name { + return &vmSizes[i], nil + } } - return &vmSize, nil + return nil, errors.Wrapf(cloudprovider.ErrNotFound, name) } func (self *SRegion) GetMetadata() *jsonutils.JSONDict { @@ -191,7 +173,7 @@ func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudpro }, Type: "Microsoft.Network/virtualNetworks", } - return &vpc, self.client.Create(jsonutils.Marshal(vpc), &vpc) + return &vpc, self.create("", jsonutils.Marshal(vpc), &vpc) } func (self *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) { @@ -302,36 +284,12 @@ func (self *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) { return nil, cloudprovider.ErrNotFound } -func (self *SRegion) getZoneById(id string) (*SZone, error) { - if izones, err := self.GetIZones(); err != nil { - return nil, err - } else { - for i := 0; i < len(izones); i += 1 { - zone := izones[i].(*SZone) - if zone.GetId() == id { - return zone, nil - } - } - } - return nil, fmt.Errorf("no such zone %s", id) -} - -func (self *SRegion) fetchZones() error { - if self.izones == nil || len(self.izones) == 0 { - self.izones = make([]cloudprovider.ICloudZone, 1) - zone := SZone{region: self, Name: self.Name} - self.izones[0] = &zone - } - return nil +func (self *SRegion) getZone() *SZone { + return &SZone{region: self, Name: self.Name} } func (self *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) { - if self.izones == nil { - if err := self.fetchInfrastructure(); err != nil { - return nil, err - } - } - return self.izones, nil + return []cloudprovider.ICloudZone{self.getZone()}, nil } func (self *SRegion) getStoragecache() *SStoragecache { @@ -341,164 +299,74 @@ func (self *SRegion) getStoragecache() *SStoragecache { return self.storageCache } -func (self *SRegion) getVpcs() ([]SVpc, error) { +func (self *SRegion) ListVpcs() ([]SVpc, error) { result := []SVpc{} - vpcs := []SVpc{} - err := self.client.ListAll("Microsoft.Network/virtualNetworks", &vpcs) + err := self.list("Microsoft.Network/virtualNetworks", url.Values{}, &result) if err != nil { - return nil, err - } - for i := 0; i < len(vpcs); i++ { - if vpcs[i].Location == self.Name { - result = append(result, vpcs[i]) - } + return nil, errors.Wrapf(err, "list") } return result, nil } -func (self *SRegion) getClassicVpcs() ([]SClassicVpc, error) { +func (self *SRegion) ListClassicVpcs() ([]SClassicVpc, error) { result := []SClassicVpc{} - for _, resourceType := range []string{"Microsoft.ClassicNetwork/virtualNetworks"} { - vpcs := []SClassicVpc{} - err := self.client.ListAll(resourceType, &vpcs) - if err != nil { - return nil, err - } - for i := 0; i < len(vpcs); i++ { - if vpcs[i].Location == self.Name { - result = append(result, vpcs[i]) - } - } + err := self.list("Microsoft.ClassicNetwork/virtualNetworks", url.Values{}, &result) + if err != nil { + return nil, errors.Wrapf(err, "ListClassicVpcs") } return result, nil } -func (self *SRegion) fetchIClassicVpc() error { - classicVpcs, err := self.getClassicVpcs() - if err != nil { - return err - } - self.iclassicVpcs = make([]cloudprovider.ICloudVpc, 0) - for i := 0; i < len(classicVpcs); i++ { - classicVpcs[i].region = self - self.iclassicVpcs = append(self.iclassicVpcs, &classicVpcs[i]) - } - return nil -} - -func (self *SRegion) fetchIVpc() error { - vpcs, err := self.getVpcs() - if err != nil { - return err - } - self.ivpcs = make([]cloudprovider.ICloudVpc, 0) - for i := 0; i < len(vpcs); i++ { - if vpcs[i].Location == self.Name { - vpcs[i].region = self - self.ivpcs = append(self.ivpcs, &vpcs[i]) - } - } - return nil -} - func (self *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) { - if self.ivpcs == nil || self.iclassicVpcs == nil { - if err := self.fetchInfrastructure(); err != nil { - return nil, err - } - } - for _, vpc := range self.ivpcs { - log.Debugf("find vpc %s for region %s", vpc.GetName(), self.GetName()) - } - for _, vpc := range self.iclassicVpcs { - log.Debugf("find classic vpc %s for region %s", vpc.GetName(), self.GetName()) - } - ivpcs := self.ivpcs - if len(self.iclassicVpcs) > 0 { - ivpcs = append(ivpcs, self.iclassicVpcs...) - } - return ivpcs, nil -} - -func (self *SRegion) fetchInfrastructure() error { - err := self.fetchZones() + vpcs, err := self.ListVpcs() if err != nil { - return err + return nil, errors.Wrapf(err, "ListVpcs") } - err = self.fetchIVpc() + classicVpcs, err := self.ListClassicVpcs() if err != nil { - return err + return nil, errors.Wrapf(err, "ListClassicVpcs") } - for i := 0; i < len(self.ivpcs); i++ { - for j := 0; j < len(self.izones); j++ { - zone := self.izones[j].(*SZone) - vpc := self.ivpcs[i].(*SVpc) - wire := SWire{zone: zone, vpc: vpc} - zone.addWire(&wire) - vpc.addWire(&wire) - } + ret := []cloudprovider.ICloudVpc{} + for i := range vpcs { + vpcs[i].region = self + ret = append(ret, &vpcs[i]) } - - err = self.fetchIClassicVpc() - if err != nil { - return err + for i := range classicVpcs { + classicVpcs[i].region = self + ret = append(ret, &classicVpcs[i]) } - for i := 0; i < len(self.iclassicVpcs); i++ { - for j := 0; j < len(self.izones); j++ { - zone := self.izones[j].(*SZone) - vpc := self.iclassicVpcs[i].(*SClassicVpc) - wire := SClassicWire{zone: zone, vpc: vpc} - zone.addClassicWire(&wire) - vpc.addWire(&wire) - } - } - return nil + return ret, nil } func (self *SRegion) CreateInstanceSimple(name string, imgId, osType string, cpu int, memMb int, sysDiskSizeGB int, storageType string, dataDiskSizesGB []int, networkId string, passwd string, publicKey string) (*SInstance, error) { - izones, err := self.GetIZones() + network, err := self.GetNetwork(networkId) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "GetNetwork(%s)", networkId) } - for i := 0; i < len(izones); i += 1 { - z := izones[i].(*SZone) - log.Debugf("Search in zone %s", z.Name) - net := z.getNetworkById(networkId) - if net != nil { - desc := &cloudprovider.SManagedVMCreateConfig{ - Name: name, - ExternalImageId: imgId, - SysDisk: cloudprovider.SDiskInfo{SizeGB: sysDiskSizeGB, StorageType: storageType}, - Cpu: cpu, - MemoryMB: memMb, - ExternalNetworkId: networkId, - Password: seclib2.RandomPassword2(12), - DataDisks: []cloudprovider.SDiskInfo{}, - PublicKey: publicKey, - OsType: osType, - } - if len(passwd) > 0 { - desc.Password = passwd - } - for _, sizeGB := range dataDiskSizesGB { - desc.DataDisks = append(desc.DataDisks, cloudprovider.SDiskInfo{SizeGB: sizeGB, StorageType: storageType}) - } - host := z.getHost() - inst, err := host.CreateVM(desc) - if err != nil { - return nil, err - } - instance := inst.(*SInstance) - instance.host = host - return instance, nil - } + desc := &cloudprovider.SManagedVMCreateConfig{ + Name: name, + ExternalImageId: imgId, + SysDisk: cloudprovider.SDiskInfo{SizeGB: sysDiskSizeGB, StorageType: storageType}, + Cpu: cpu, + MemoryMB: memMb, + ExternalNetworkId: networkId, + Password: passwd, + DataDisks: []cloudprovider.SDiskInfo{}, + PublicKey: publicKey, + OsType: osType, } - return nil, fmt.Errorf("cannot find network %s", networkId) + if len(passwd) > 0 { + desc.Password = passwd + } + for _, sizeGB := range dataDiskSizesGB { + desc.DataDisks = append(desc.DataDisks, cloudprovider.SDiskInfo{SizeGB: sizeGB, StorageType: storageType}) + } + return self._createVM(desc, network.ID) } func (region *SRegion) GetEips() ([]SEipAddress, error) { eips := []SEipAddress{} - err := region.client.ListAll("Microsoft.Network/publicIPAddresses", &eips) + err := region.client.list("Microsoft.Network/publicIPAddresses", url.Values{}, &eips) if err != nil { return nil, err } @@ -542,29 +410,15 @@ func (region *SRegion) GetISecurityGroupById(secgroupId string) (cloudprovider.I func (region *SRegion) GetISecurityGroupByName(opts *cloudprovider.SecurityGroupFilterOptions) (cloudprovider.ICloudSecurityGroup, error) { if strings.Contains(strings.ToLower(opts.VpcId), "microsoft.classicnetwork") { - secgroups, err := region.GetClassicSecurityGroups(opts.Name) - if err != nil { - return nil, err - } - if len(secgroups) == 0 { - return nil, cloudprovider.ErrNotFound - } - if len(secgroups) > 1 { - return nil, cloudprovider.ErrDuplicateId - } - return &secgroups[0], nil + return nil, errors.Wrapf(cloudprovider.ErrNotSupported, "not support classic secgroup") } - secgroups, err := region.GetSecurityGroups(opts.Name) + resource := fmt.Sprintf("subscriptions/%s/resourcegroups/%s/providers/microsoft.network/networksecuritygroups/%s", region.client.subscriptionId, opts.ProjectId, opts.Name) + secgroup := &SSecurityGroup{region: region} + err := region.get(resource, url.Values{}, secgroup) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "get(%s)", resource) } - if len(secgroups) == 0 { - return nil, cloudprovider.ErrNotFound - } - if len(secgroups) > 1 { - return nil, cloudprovider.ErrDuplicateId - } - return &secgroups[0], nil + return secgroup, nil } func (region *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) { @@ -611,16 +465,13 @@ func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAc } func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) { - iBuckets, err := region.client.getIBuckets() + accounts, err := region.ListStorageAccounts() if err != nil { - return nil, errors.Wrap(err, "getIBuckets") + return nil, errors.Wrapf(err, "ListStorageAccounts") } ret := make([]cloudprovider.ICloudBucket, 0) - for i := range iBuckets { - if iBuckets[i].GetLocation() != region.GetId() { - continue - } - ret = append(ret, iBuckets[i]) + for i := range accounts { + ret = append(ret, &accounts[i]) } return ret, nil } @@ -634,20 +485,19 @@ func (region *SRegion) CreateIBucket(name string, storageClassStr string, acl st } func (region *SRegion) DeleteIBucket(name string) error { - accounts, err := region.GetStorageAccounts() + accounts, err := region.listStorageAccounts() if err != nil { - return errors.Wrap(err, "GetStorageAccounts") + return errors.Wrap(err, "ListStorageAccounts") } for i := range accounts { if accounts[i].Name == name { - err = region.client.Delete(accounts[i].ID) + err = region.del(accounts[i].ID) if err != nil { - return errors.Wrap(err, "region.client.Delete") + return errors.Wrapf(err, "region.del") } return nil } } - region.client.invalidateIBuckets() return nil } @@ -666,3 +516,109 @@ func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket func (region *SRegion) GetCapabilities() []string { return region.client.GetCapabilities() } + +func (self *SRegion) get(resource string, params url.Values, retVal interface{}) error { + return self.client.get(resource, params, retVal) +} + +func (self *SRegion) Show(resource string) (jsonutils.JSONObject, error) { + ret := jsonutils.NewDict() + err := self.get(resource, url.Values{}, ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (self *SRegion) del(resource string) error { + return self.client.del(resource) +} + +func (self *SRegion) Delete(resource string) error { + return self.del(resource) +} + +func (self *SRegion) checkResourceGroup(resourceGroup string) (string, error) { + if len(resourceGroup) == 0 { + resourceGroup = "Default" + } + for i := range self.client.ressourceGroups { + if strings.ToLower(self.client.ressourceGroups[i].Name) == strings.ToLower(resourceGroup) { + return resourceGroup, nil + } + } + _, err := self.CreateResourceGroup(resourceGroup) + return resourceGroup, err +} + +type sInfo struct { + Location string `json:"Location"` + Name string `json:"Name"` + Type string `json:"Type"` +} + +func (self *SRegion) createInfo(body jsonutils.JSONObject) (sInfo, error) { + info := sInfo{} + err := body.Unmarshal(&info) + if err != nil { + return info, errors.Wrapf(err, "body.Unmarshal") + } + if len(info.Name) == 0 { + return info, fmt.Errorf("Missing name params") + } + if len(info.Type) == 0 { + return info, fmt.Errorf("Missing type params") + } + return info, nil +} + +func (self *SRegion) create(resourceGroup string, _body jsonutils.JSONObject, retVal interface{}) error { + body := _body.(*jsonutils.JSONDict) + info, err := self.createInfo(_body) + if err != nil { + return errors.Wrapf(err, "createInfo") + } + resourceGroup, err = self.checkResourceGroup(resourceGroup) + if err != nil { + return errors.Wrapf(err, "checkResourceGroup") + } + info.Name, err = self.client.getUniqName(resourceGroup, info.Type, info.Name) + if err != nil { + return errors.Wrapf(err, "getUniqName") + } + info.Location = self.Name + body.Update(jsonutils.Marshal(info)) + return self.client.create(resourceGroup, info.Type, info.Name, body, retVal) +} + +func (self *SRegion) update(body jsonutils.JSONObject, retVal interface{}) error { + return self.client.update(body, retVal) +} + +func (self *SRegion) perform(id, action string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.client.perform(id, action, body) +} + +func (self *SRegion) put(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.client.put(resource, body) +} + +func (self *SRegion) patch(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.client.patch(resource, body) +} + +func (self *SRegion) list(resource string, params url.Values, retVal interface{}) error { + result := []jsonutils.JSONObject{} + err := self.client.list(resource, params, &result) + if err != nil { + return errors.Wrapf(err, "client.list") + } + ret := []jsonutils.JSONObject{} + for i := range result { + location, _ := result[i].GetString("location") + if len(location) == 0 || strings.ToLower(self.Name) == strings.ToLower(location) { + ret = append(ret, result[i]) + } + } + return jsonutils.Update(retVal, ret) +} diff --git a/pkg/multicloud/azure/resourcegroup.go b/pkg/multicloud/azure/resourcegroup.go index a8abcd1de6..e83eef6da2 100644 --- a/pkg/multicloud/azure/resourcegroup.go +++ b/pkg/multicloud/azure/resourcegroup.go @@ -16,11 +16,13 @@ package azure import ( "fmt" + "net/url" "strings" "yunion.io/x/jsonutils" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" ) type GroupProperties struct { @@ -28,6 +30,9 @@ type GroupProperties struct { } type SResourceGroup struct { + multicloud.SResourceBase + client *SAzureClient + ID string Name string Location string @@ -35,33 +40,29 @@ type SResourceGroup struct { ManagedBy string } -func (self *SRegion) GetResourceGroups() ([]SResourceGroup, error) { - resourceGroups := []SResourceGroup{} - return resourceGroups, self.client.List("resourcegroups", &resourceGroups) -} - func (self *SRegion) GetResourceGroupDetail(groupName string) (*SResourceGroup, error) { resourceGroup := SResourceGroup{} - idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.SubscriptionID, groupName) - return &resourceGroup, self.client.Get(idStr, []string{}, &resourceGroup) + idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.client.subscriptionId, groupName) + return &resourceGroup, self.get(idStr, url.Values{}, &resourceGroup) } // not support update, resource group name is immutable??? func (self *SRegion) UpdateResourceGroup(groupName string, newName string) error { resourceGroup := SResourceGroup{Name: newName} - idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.SubscriptionID, groupName) - return self.client.Patch(idStr, jsonutils.Marshal(&resourceGroup)) + resource := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.client.subscriptionId, groupName) + _, err := self.client.patch(resource, jsonutils.Marshal(&resourceGroup)) + return err } -func (self *SRegion) CreateResourceGroup(groupName string) error { +func (self *SRegion) CreateResourceGroup(groupName string) (jsonutils.JSONObject, error) { resourceGroup := SResourceGroup{Location: self.Name} - idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.SubscriptionID, groupName) - return self.client.Put(idStr, jsonutils.Marshal(resourceGroup)) + idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.client.subscriptionId, groupName) + return self.client.put(idStr, jsonutils.Marshal(resourceGroup)) } func (self *SRegion) DeleteResourceGroup(groupName string) error { - idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.SubscriptionID, groupName) - return self.client.Delete(idStr) + idStr := fmt.Sprintf("subscriptions/%s/resourcegroups/%s", self.client.subscriptionId, groupName) + return self.del(idStr) } func (r *SResourceGroup) GetName() string { @@ -79,15 +80,3 @@ func (r *SResourceGroup) GetGlobalId() string { func (r *SResourceGroup) GetStatus() string { return api.EXTERNAL_PROJECT_STATUS_AVAILABLE } - -func (r *SResourceGroup) GetMetadata() *jsonutils.JSONDict { - return nil -} - -func (r *SResourceGroup) IsEmulated() bool { - return false -} - -func (r *SResourceGroup) Refresh() error { - return nil -} diff --git a/pkg/multicloud/azure/resourcesku.go b/pkg/multicloud/azure/resourcesku.go index 18438e4252..b143337e51 100644 --- a/pkg/multicloud/azure/resourcesku.go +++ b/pkg/multicloud/azure/resourcesku.go @@ -14,12 +14,6 @@ package azure -import ( - "fmt" - - "yunion.io/x/pkg/utils" -) - /* { "capabilities":[ @@ -113,48 +107,7 @@ type SResourceSkusResult struct { } func (self *SAzureClient) ListResourceSkus() ([]SResourceSku, error) { - cli, err := self.getDefaultClient() - if err != nil { - return nil, err - } - if len(self.subscriptionId) == 0 { - return nil, fmt.Errorf("need subscription id") - } - url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/skus?api-version=2017-09-01", self.subscriptionId) - skus := make([]SResourceSku, 0) - for { - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) - if err != nil { - return nil, err - } - result := SResourceSkusResult{} - err = body.Unmarshal(&result) - if err != nil { - return nil, err - } - skus = append(skus, result.Value...) - if len(result.NextLink) > 0 { - url = result.NextLink - } else { - break - } - } - return skus, nil -} - -func (self *SRegion) GetResourceSkus(location string) ([]SResourceSku, error) { - skus, err := self.client.ListResourceSkus() - if err != nil { - return nil, err - } - if len(location) == 0 { - return skus, nil - } - ret := make([]SResourceSku, 0) - for i := 0; i < len(skus); i += 1 { - if utils.IsInStringArray(location, skus[i].Locations) { - ret = append(ret, skus[i]) - } - } - return ret, nil + skus := []SResourceSku{} + resource := "Microsoft.Compute/skus" + return skus, self.list(resource, nil, &skus) } diff --git a/pkg/multicloud/azure/securitygroup.go b/pkg/multicloud/azure/securitygroup.go index ee7ab931ad..beecd49b0e 100644 --- a/pkg/multicloud/azure/securitygroup.go +++ b/pkg/multicloud/azure/securitygroup.go @@ -17,6 +17,7 @@ package azure import ( "fmt" "net" + "net/url" "strconv" "strings" "unicode" @@ -62,12 +63,12 @@ type SecurityGroupPropertiesFormat struct { SecurityRules []SecurityRules `json:"securityRules,omitempty"` DefaultSecurityRules []SecurityRules `json:"defaultSecurityRules,omitempty"` NetworkInterfaces *[]Interface `json:"networkInterfaces,omitempty"` - Subnets *[]Subnet `json:"subnets,omitempty"` + Subnets *[]SNetwork `json:"subnets,omitempty"` ProvisioningState string //Possible values are: 'Updating', 'Deleting', and 'Failed' } type SSecurityGroup struct { multicloud.SSecurityGroup - vpc *SVpc + region *SRegion Properties *SecurityGroupPropertiesFormat `json:"properties,omitempty"` ID string @@ -311,28 +312,21 @@ func (region *SRegion) CreateSecurityGroup(secName string) (*SSecurityGroup, err Type: "Microsoft.Network/networkSecurityGroups", Location: region.Name, } - return &secgroup, region.client.Create(jsonutils.Marshal(secgroup), &secgroup) + return &secgroup, region.create("", jsonutils.Marshal(secgroup), &secgroup) } -func (region *SRegion) GetSecurityGroups(name string) ([]SSecurityGroup, error) { +func (region *SRegion) ListSecgroups() ([]SSecurityGroup, error) { secgroups := []SSecurityGroup{} - err := region.client.ListAll("Microsoft.Network/networkSecurityGroups", &secgroups) + err := region.list("Microsoft.Network/networkSecurityGroups", url.Values{}, &secgroups) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "list") } - result := []SSecurityGroup{} - for i := 0; i < len(secgroups); i++ { - if secgroups[i].Location == region.Name && (len(name) == 0 || strings.ToLower(secgroups[i].Name) == strings.ToLower(name)) { - secgroups[i].region = region - result = append(result, secgroups[i]) - } - } - return result, err + return secgroups, nil } func (region *SRegion) GetSecurityGroupDetails(secgroupId string) (*SSecurityGroup, error) { secgroup := SSecurityGroup{region: region} - return &secgroup, region.client.Get(secgroupId, []string{}, &secgroup) + return &secgroup, region.get(secgroupId, url.Values{}, &secgroup) } func (self *SSecurityGroup) Refresh() error { @@ -417,12 +411,12 @@ func convertSecurityGroupRule(rule cloudprovider.SecurityRule) *SecurityRules { func (region *SRegion) AttachSecurityToInterfaces(secgroupId string, nicIds []string) error { for _, nicId := range nicIds { - nic, err := region.GetNetworkInterfaceDetail(nicId) + nic, err := region.GetNetworkInterface(nicId) if err != nil { return err } - nic.Properties.NetworkSecurityGroup = &SSecurityGroup{ID: secgroupId} - if err := region.client.Update(jsonutils.Marshal(nic), nil); err != nil { + nic.Properties.NetworkSecurityGroup = SSecurityGroup{ID: secgroupId} + if err := region.update(jsonutils.Marshal(nic), nil); err != nil { return err } } @@ -448,17 +442,16 @@ func (self *SSecurityGroup) GetProjectId() string { func (self *SSecurityGroup) Delete() error { if self.Properties.NetworkInterfaces != nil { for _, nic := range *self.Properties.NetworkInterfaces { - nic, err := self.region.GetNetworkInterfaceDetail(nic.ID) + nic, err := self.region.GetNetworkInterface(nic.ID) if err != nil { return err } - nic.Properties.NetworkSecurityGroup = nil - if err := self.region.client.Update(jsonutils.Marshal(nic), nil); err != nil { + if err := self.region.update(jsonutils.Marshal(nic), nil); err != nil { return err } } } - return self.region.client.Delete(self.ID) + return self.region.del(self.ID) } func (self *SSecurityGroup) SetRules(rules []cloudprovider.SecurityRule) error { @@ -479,7 +472,7 @@ func (self *SSecurityGroup) SetRules(rules []cloudprovider.SecurityRule) error { } self.Properties.SecurityRules = securityRules self.Properties.ProvisioningState = "" - return self.region.client.Update(jsonutils.Marshal(self), nil) + return self.region.update(jsonutils.Marshal(self), nil) } func (self *SSecurityGroup) SyncRules(common, inAdds, outAdds, inDels, outDels []cloudprovider.SecurityRule) error { diff --git a/pkg/multicloud/azure/service.go b/pkg/multicloud/azure/service.go index 79385b1c0b..b7ab368d74 100644 --- a/pkg/multicloud/azure/service.go +++ b/pkg/multicloud/azure/service.go @@ -16,6 +16,14 @@ package azure import ( "fmt" + "net/url" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" ) type SServices struct { @@ -36,34 +44,52 @@ type ResourceType struct { ResourceType string `json:"resourceType,omitempty"` } -func (self *SRegion) ListServices() ([]SService, error) { +func (self *SAzureClient) ListServices() ([]SService, error) { services := []SService{} - return services, self.client.List("providers", &services) + return services, self.list("providers", url.Values{}, &services) } -func (self *SRegion) SerciceShow(serviceType string) (*SService, error) { +func (self *SAzureClient) GetSercice(serviceType string) (*SService, error) { service := SService{} - return &service, self.client.Get("providers/"+serviceType, []string{}, &service) + return &service, self.get("providers/"+serviceType, url.Values{}, &service) } -func (self *SRegion) serviceOperation(resourceType, operation string) error { - services, err := self.ListServices() - if err != nil { - return err - } - for _, service := range services { - if service.Namespace == resourceType { - _, err := self.client.jsonRequest("POST", fmt.Sprintf("%s/%s", service.ID, operation), "") - return err +func (self *SAzureClient) serviceOperation(serviceType, operation string) error { + resource := fmt.Sprintf("subscriptions/%s/providers/%s", self.subscriptionId, serviceType) + _, err := self.perform(resource, operation, nil) + return err +} + +func (self *SAzureClient) waitServiceStatus(serviceType, status string) error { + return cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) { + services, err := self.ListServices() + if err != nil { + return false, errors.Wrapf(err, "ListServices") } + for _, service := range services { + if strings.ToLower(service.Namespace) == strings.ToLower(serviceType) { + if service.RegistrationState == status { + return true, nil + } + log.Debugf("service %s status: %s expect %s", serviceType, service.RegistrationState, status) + } + } + return false, nil + }) +} + +func (self *SAzureClient) ServiceRegister(serviceType string) error { + err := self.serviceOperation(serviceType, "register") + if err != nil { + return errors.Wrapf(err, "serviceOperation(%s)", "register") } - return fmt.Errorf("failed to find namespace: %s", resourceType) + return self.waitServiceStatus(serviceType, "Registered") } -func (self *SRegion) ServiceRegister(resourceType string) error { - return self.serviceOperation(resourceType, "register") -} - -func (self *SRegion) ServiceUnRegister(resourceType string) error { - return self.serviceOperation(resourceType, "unregister") +func (self *SAzureClient) ServiceUnRegister(serviceType string) error { + err := self.serviceOperation(serviceType, "unregister") + if err != nil { + return errors.Wrapf(err, "serviceOperation(%s)", "unregister") + } + return self.waitServiceStatus(serviceType, "NotRegistered") } diff --git a/pkg/multicloud/azure/shell/cloudpolicy.go b/pkg/multicloud/azure/shell/cloudpolicy.go index 54b26084af..c33c706a44 100644 --- a/pkg/multicloud/azure/shell/cloudpolicy.go +++ b/pkg/multicloud/azure/shell/cloudpolicy.go @@ -61,7 +61,7 @@ func init() { } shellutils.R(&AssignmentIdOption{}, "assignment-delete", "Delete role assignment", func(cli *azure.SRegion, args *AssignmentIdOption) error { - return cli.GetClient().Delete(args.ID) + return cli.GetClient().GDelete(args.ID) }) type ObjectPolicyListOptions struct { diff --git a/pkg/multicloud/azure/shell/disk.go b/pkg/multicloud/azure/shell/disk.go index cd73bf9fb9..28e9c8f466 100644 --- a/pkg/multicloud/azure/shell/disk.go +++ b/pkg/multicloud/azure/shell/disk.go @@ -21,24 +21,13 @@ import ( func init() { type DiskListOptions struct { - Classic bool `help:"List classic disks"` - Offset int `help:"List offset"` - Limit int `help:"List limit"` } shellutils.R(&DiskListOptions{}, "disk-list", "List disks", func(cli *azure.SRegion, args *DiskListOptions) error { - if args.Classic { - disks, err := cli.GetClassicDisks() - if err != nil { - return err - } - printList(disks, len(disks), args.Offset, args.Limit, []string{}) - return nil - } disks, err := cli.GetDisks() if err != nil { return err } - printList(disks, len(disks), args.Offset, args.Limit, []string{}) + printList(disks, len(disks), 0, 0, []string{}) return nil }) @@ -53,13 +42,7 @@ func init() { } shellutils.R(&DiskCreateOptions{}, "disk-create", "Create disk", func(cli *azure.SRegion, args *DiskCreateOptions) error { - var disk *azure.SDisk - var err error - if len(args.SnapshotId) > 0 { - disk, err = cli.CreateDiskBySnapshot(args.NAME, args.SnapshotId) - } else { - disk, err = cli.CreateDisk(args.STORAGETYPE, args.NAME, args.SizeGb, args.Desc, args.Image, args.ResourceGroup) - } + disk, err := cli.CreateDisk(args.STORAGETYPE, args.NAME, args.SizeGb, args.Desc, args.Image, args.SnapshotId, args.ResourceGroup) if err != nil { return err } @@ -72,12 +55,21 @@ func init() { } shellutils.R(&DiskOptions{}, "disk-show", "Show disk", func(cli *azure.SRegion, args *DiskOptions) error { - if disk, err := cli.GetDisk(args.ID); err != nil { + disk, err := cli.GetDisk(args.ID) + if err != nil { return err - } else { - printObject(disk) - return nil } + printObject(disk) + return nil + }) + + shellutils.R(&DiskOptions{}, "classic-disk-show", "Show classic disk", func(cli *azure.SRegion, args *DiskOptions) error { + disk, err := cli.GetClassicDisk(args.ID) + if err != nil { + return err + } + printObject(disk) + return nil }) shellutils.R(&DiskOptions{}, "disk-delete", "Delete disks", func(cli *azure.SRegion, args *DiskOptions) error { diff --git a/pkg/multicloud/azure/shell/image.go b/pkg/multicloud/azure/shell/image.go index 80c8e3898c..73257c7ac9 100644 --- a/pkg/multicloud/azure/shell/image.go +++ b/pkg/multicloud/azure/shell/image.go @@ -27,12 +27,12 @@ func init() { ImageType string `help:"image type" choices:"customized|system|shared|market"` } shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *azure.SRegion, args *ImageListOptions) error { - if images, err := cli.GetImages(args.ImageType); err != nil { + images, err := cli.GetImages(args.ImageType) + if err != nil { return err - } else { - printList(images, len(images), 0, 0, []string{}) - return nil } + printList(images, len(images), 0, 0, []string{}) + return nil }) type ImagePublishersOptions struct { @@ -93,12 +93,21 @@ func init() { } }) - type ImageDeleteOptions struct { + type ImageIdOptions struct { ID string `helo:"Image ID"` } - shellutils.R(&ImageDeleteOptions{}, "image-delete", "Delete image", func(cli *azure.SRegion, args *ImageDeleteOptions) error { + shellutils.R(&ImageIdOptions{}, "image-delete", "Delete image", func(cli *azure.SRegion, args *ImageIdOptions) error { return cli.DeleteImage(args.ID) }) + shellutils.R(&ImageIdOptions{}, "image-show", "Delete image", func(cli *azure.SRegion, args *ImageIdOptions) error { + image, err := cli.GetImageById(args.ID) + if err != nil { + return err + } + printObject(image) + return nil + }) + } diff --git a/pkg/multicloud/azure/shell/instance.go b/pkg/multicloud/azure/shell/instance.go index 327520c4dc..1c3debab85 100644 --- a/pkg/multicloud/azure/shell/instance.go +++ b/pkg/multicloud/azure/shell/instance.go @@ -26,47 +26,60 @@ func init() { type InstanceListOptions struct { Classic bool `help:"List classic instance"` ScaleSets bool `help:"List Scale Sets instance"` - Limit int `help:"page size"` - Offset int `help:"page offset"` } shellutils.R(&InstanceListOptions{}, "instance-list", "List intances", func(cli *azure.SRegion, args *InstanceListOptions) error { - if args.Classic { - instances, err := cli.GetClassicInstances() - if err != nil { - return err - } - printList(instances, len(instances), args.Offset, args.Limit, []string{}) - return nil - } else if args.ScaleSets { - instances, err := cli.GetInstanceScaleSets() - if err != nil { - return err - } - printList(instances, len(instances), args.Offset, args.Limit, []string{}) - return nil - } instances, err := cli.GetInstances() if err != nil { return err } - printList(instances, len(instances), args.Offset, args.Limit, []string{}) + printList(instances, len(instances), 0, 0, []string{}) + return nil + }) + + shellutils.R(&InstanceListOptions{}, "classic-instance-list", "List classic instance", func(cli *azure.SRegion, args *InstanceListOptions) error { + instances, err := cli.GetClassicInstances() + if err != nil { + return err + } + printList(instances, len(instances), 0, 0, []string{}) + return nil + }) + + type SClassicInstacneIdOptions struct { + ID string + } + + shellutils.R(&SClassicInstacneIdOptions{}, "classic-instance-disk-list", "List classic instance disks", func(cli *azure.SRegion, args *SClassicInstacneIdOptions) error { + disks, err := cli.GetClassicInstanceDisks(args.ID) + if err != nil { + return err + } + printList(disks, len(disks), 0, 0, []string{}) + return nil + }) + + shellutils.R(&InstanceListOptions{}, "instance-scaleset-list", "List classic instance", func(cli *azure.SRegion, args *InstanceListOptions) error { + instances, err := cli.GetInstanceScaleSets() + if err != nil { + return err + } + printList(instances, len(instances), 0, 0, []string{}) return nil }) type InstanceSizeListOptions struct { - Location string } shellutils.R(&InstanceSizeListOptions{}, "instance-size-list", "List intances", func(cli *azure.SRegion, args *InstanceSizeListOptions) error { - if vmSize, err := cli.GetVMSize(args.Location); err != nil { + vmSizes, err := cli.ListVmSizes() + if err != nil { return err - } else { - printObject(vmSize) - return nil } + printList(vmSizes, 0, 0, 0, nil) + return nil }) shellutils.R(&InstanceSizeListOptions{}, "resource-sku-list", "List resource sku", func(cli *azure.SRegion, args *InstanceSizeListOptions) error { - skus, err := cli.GetResourceSkus(args.Location) + skus, err := cli.GetClient().ListResourceSkus() if err != nil { return err } @@ -78,7 +91,8 @@ func init() { NAME string `help:"Name of instance"` IMAGE string `help:"image ID"` CPU int `help:"CPU count"` - MEMORYGB int `help:"MemoryGB"` + MEMORYMB int `help:"MemoryMb"` + InstanceType string `help:"Instance Type"` SYSDISKSIZEGB int `help:"System Disk Size"` Disk []int `help:"Data disk sizes int GB"` STORAGE string `help:"Storage type"` @@ -88,7 +102,7 @@ func init() { OsType string `help:"Operation system type" choices:"Linux|Windows"` } shellutils.R(&InstanceCreateOptions{}, "instance-create", "Create a instance", func(cli *azure.SRegion, args *InstanceCreateOptions) error { - instance, e := cli.CreateInstanceSimple(args.NAME, args.IMAGE, args.OsType, args.CPU, args.MEMORYGB, args.SYSDISKSIZEGB, args.STORAGE, args.Disk, args.NETWORK, args.PASSWD, args.PublicKey) + instance, e := cli.CreateInstanceSimple(args.NAME, args.IMAGE, args.OsType, args.CPU, args.MEMORYMB, args.SYSDISKSIZEGB, args.STORAGE, args.Disk, args.NETWORK, args.PASSWD, args.PublicKey) if e != nil { return e } @@ -155,13 +169,12 @@ func init() { }) type InstanceConfigOptions struct { - ID string `help:"Instance ID"` - NCPU int `help:"Number of cpu core"` - MEMERY int `helo:"Instance memery in mb"` + ID string `help:"Instance ID"` + INSTANCE_TYPE string `help:"Instance Vm Size"` } - shellutils.R(&InstanceConfigOptions{}, "instance-change-conf", "Attach a disk to intance", func(cli *azure.SRegion, args *InstanceConfigOptions) error { - return cli.ChangeVMConfig(context.Background(), args.ID, args.NCPU, args.MEMERY) + shellutils.R(&InstanceConfigOptions{}, "instance-change-config", "Attach a disk to intance", func(cli *azure.SRegion, args *InstanceConfigOptions) error { + return cli.ChangeConfig(args.ID, args.INSTANCE_TYPE) }) type InstanceDeployOptions struct { diff --git a/pkg/multicloud/azure/shell/network.go b/pkg/multicloud/azure/shell/network.go index 4cab3854ca..5cbc2d3857 100644 --- a/pkg/multicloud/azure/shell/network.go +++ b/pkg/multicloud/azure/shell/network.go @@ -21,37 +21,44 @@ import ( func init() { type NetworkListOptions struct { - Limit int `help:"page size"` - Offset int `help:"page offset"` + VPC string `help:"Vpc Id"` + Limit int `help:"page size"` + Offset int `help:"page offset"` } shellutils.R(&NetworkListOptions{}, "network-list", "List networks", func(cli *azure.SRegion, args *NetworkListOptions) error { - if vpcs, err := cli.GetIVpcs(); err != nil { - return nil - } else { - networks := make([]azure.SNetwork, 0) - for _, _vpc := range vpcs { - vpc := _vpc.(*azure.SVpc) - if _networks := vpc.GetNetworks(); len(_networks) > 0 { - networks = append(networks, _networks...) - } - - } - printList(networks, len(networks), args.Offset, args.Limit, []string{}) + vpc, err := cli.GetVpc(args.VPC) + if err != nil { + return err } + networks := vpc.GetNetworks() + printList(networks, len(networks), args.Offset, args.Limit, []string{}) + return nil + }) + + type NetworkCreateOptions struct { + VPC string + NAME string + CIDR string + } + + shellutils.R(&NetworkCreateOptions{}, "network-create", "Create network", func(cli *azure.SRegion, args *NetworkCreateOptions) error { + network, err := cli.CreateNetwork(args.VPC, args.NAME, args.CIDR, "") + if err != nil { + return err + } + printObject(network) return nil }) type NetworkInterfaceListOptions struct { - Limit int `help:"page size"` - Offset int `help:"page offset"` } shellutils.R(&NetworkInterfaceListOptions{}, "network-interface-list", "List network interface", func(cli *azure.SRegion, args *NetworkInterfaceListOptions) error { - if interfaces, err := cli.GetNetworkInterfaces(); err != nil { + nics, err := cli.GetNetworkInterfaces() + if err != nil { return err - } else { - printList(interfaces, len(interfaces), args.Offset, args.Limit, []string{}) } + printList(nics, len(nics), 0, 0, []string{}) return nil }) @@ -60,12 +67,12 @@ func init() { } shellutils.R(&NetworkInterfaceOptions{}, "network-interface-show", "Show network interface", func(cli *azure.SRegion, args *NetworkInterfaceOptions) error { - if networkInterface, err := cli.GetNetworkInterfaceDetail(args.ID); err != nil { + nic, err := cli.GetNetworkInterface(args.ID) + if err != nil { return err - } else { - printObject(networkInterface) - return nil } + printObject(nic) + return nil }) type NetworkInterfaceCreateOptions struct { diff --git a/pkg/multicloud/azure/shell/region.go b/pkg/multicloud/azure/shell/region.go index 3682dc2bd0..deec4b9013 100644 --- a/pkg/multicloud/azure/shell/region.go +++ b/pkg/multicloud/azure/shell/region.go @@ -27,4 +27,21 @@ func init() { printList(regions, 0, 0, 0, nil) return nil }) + + type ResourceIdOptions struct { + ID string `help:"resource id"` + } + shellutils.R(&ResourceIdOptions{}, "delete", "delete resource", func(cli *azure.SRegion, args *ResourceIdOptions) error { + return cli.Delete(args.ID) + }) + + shellutils.R(&ResourceIdOptions{}, "show", "Show resource", func(cli *azure.SRegion, args *ResourceIdOptions) error { + ret, err := cli.Show(args.ID) + if err != nil { + return err + } + printObject(ret) + return nil + }) + } diff --git a/pkg/multicloud/azure/shell/resourcegroup.go b/pkg/multicloud/azure/shell/resourcegroup.go index 4efdac3cc8..b1721c9190 100644 --- a/pkg/multicloud/azure/shell/resourcegroup.go +++ b/pkg/multicloud/azure/shell/resourcegroup.go @@ -25,12 +25,12 @@ func init() { Offset int `help:"page offset"` } shellutils.R(&ResourceGroupListOptions{}, "resource-group-list", "List group", func(cli *azure.SRegion, args *ResourceGroupListOptions) error { - if groups, err := cli.GetResourceGroups(); err != nil { + groups, err := cli.GetClient().ListResourceGroups() + if err != nil { return err - } else { - printList(groups, len(groups), args.Offset, args.Limit, []string{}) - return nil } + printList(groups, len(groups), 0, 0, []string{}) + return nil }) type ResourceGroupOptions struct { @@ -47,10 +47,11 @@ func init() { }) shellutils.R(&ResourceGroupOptions{}, "resource-group-create", "Create resource group", func(cli *azure.SRegion, args *ResourceGroupOptions) error { - err := cli.CreateResourceGroup(args.GROUP) + resp, err := cli.CreateResourceGroup(args.GROUP) if err != nil { return err } + printObject(resp) return nil }) diff --git a/pkg/multicloud/azure/shell/secgroup.go b/pkg/multicloud/azure/shell/secgroup.go index c5a63cbf6b..a870117494 100644 --- a/pkg/multicloud/azure/shell/secgroup.go +++ b/pkg/multicloud/azure/shell/secgroup.go @@ -21,25 +21,13 @@ import ( func init() { type SecurityGroupListOptions struct { - Classic bool `help:"List classic secgroups"` - Name string `help:"Secgroup name"` - Limit int `help:"page size"` - Offset int `help:"page offset"` } shellutils.R(&SecurityGroupListOptions{}, "security-group-list", "List security group", func(cli *azure.SRegion, args *SecurityGroupListOptions) error { - if args.Classic { - secgrps, err := cli.GetClassicSecurityGroups(args.Name) - if err != nil { - return err - } - printList(secgrps, len(secgrps), args.Offset, args.Limit, []string{}) - return nil - } - secgrps, err := cli.GetSecurityGroups(args.Name) + secgrps, err := cli.ListSecgroups() if err != nil { return err } - printList(secgrps, len(secgrps), args.Offset, args.Limit, []string{}) + printList(secgrps, len(secgrps), 0, 0, []string{}) return nil }) diff --git a/pkg/multicloud/azure/shell/service.go b/pkg/multicloud/azure/shell/service.go index c7dc64de6d..c9a10429ba 100644 --- a/pkg/multicloud/azure/shell/service.go +++ b/pkg/multicloud/azure/shell/service.go @@ -23,7 +23,7 @@ func init() { type ServiceListOptions struct { } shellutils.R(&ServiceListOptions{}, "service-list", "List providers", func(cli *azure.SRegion, args *ServiceListOptions) error { - services, err := cli.ListServices() + services, err := cli.GetClient().ListServices() if err != nil { return err } @@ -36,15 +36,15 @@ func init() { } shellutils.R(&ServiceOptions{}, "service-register", "Register service", func(cli *azure.SRegion, args *ServiceOptions) error { - return cli.ServiceRegister(args.NAME) + return cli.GetClient().ServiceRegister(args.NAME) }) shellutils.R(&ServiceOptions{}, "service-unregister", "Unregister service", func(cli *azure.SRegion, args *ServiceOptions) error { - return cli.ServiceUnRegister(args.NAME) + return cli.GetClient().ServiceUnRegister(args.NAME) }) shellutils.R(&ServiceOptions{}, "service-show", "Show service detail", func(cli *azure.SRegion, args *ServiceOptions) error { - service, err := cli.SerciceShow(args.NAME) + service, err := cli.GetClient().GetSercice(args.NAME) if err != nil { return err } diff --git a/pkg/multicloud/azure/shell/snapshot.go b/pkg/multicloud/azure/shell/snapshot.go index 8f3e79ca38..0532e5bea4 100644 --- a/pkg/multicloud/azure/shell/snapshot.go +++ b/pkg/multicloud/azure/shell/snapshot.go @@ -23,17 +23,14 @@ import ( func init() { type SnapshotListOptions struct { - Disk string `help:"List snapshot for disk"` - Limit int `help:"page size"` - Offset int `help:"page offset"` } shellutils.R(&SnapshotListOptions{}, "snapshot-list", "List snapshot", func(cli *azure.SRegion, args *SnapshotListOptions) error { - if snapshots, err := cli.GetSnapShots(args.Disk); err != nil { + snapshots, err := cli.ListSnapshots() + if err != nil { return err - } else { - printList(snapshots, len(snapshots), args.Offset, args.Limit, []string{}) - return nil } + printList(snapshots, len(snapshots), 0, 0, []string{}) + return nil }) type SnapshotCreateOptions struct { @@ -43,12 +40,12 @@ func init() { } shellutils.R(&SnapshotCreateOptions{}, "snapshot-create", "Create snapshot", func(cli *azure.SRegion, args *SnapshotCreateOptions) error { - if snapshot, err := cli.CreateSnapshot(args.DISK, args.NAME, args.Desc); err != nil { + snapshot, err := cli.CreateSnapshot(args.DISK, args.NAME, args.Desc) + if err != nil { return err - } else { - printObject(snapshot) - return nil } + printObject(snapshot) + return nil }) type SnapshotOptions struct { @@ -60,12 +57,12 @@ func init() { }) shellutils.R(&SnapshotOptions{}, "snapshot-show", "List snapshot", func(cli *azure.SRegion, args *SnapshotOptions) error { - if snapshot, err := cli.GetSnapshotDetail(args.ID); err != nil { + snapshot, err := cli.GetSnapshot(args.ID) + if err != nil { return err - } else { - printObject(snapshot) - return nil } + printObject(snapshot) + return nil }) shellutils.R(&SnapshotOptions{}, "snapshot-grant-access", "Grant access for snapshot", func(cli *azure.SRegion, args *SnapshotOptions) error { diff --git a/pkg/multicloud/azure/shell/storageaccount.go b/pkg/multicloud/azure/shell/storageaccount.go index 46871b4436..8ee966efb9 100644 --- a/pkg/multicloud/azure/shell/storageaccount.go +++ b/pkg/multicloud/azure/shell/storageaccount.go @@ -25,18 +25,36 @@ func init() { type StorageAccountListOptions struct { } shellutils.R(&StorageAccountListOptions{}, "storage-account-list", "List storage account", func(cli *azure.SRegion, args *StorageAccountListOptions) error { - if accounts, err := cli.GetStorageAccounts(); err != nil { + accounts, err := cli.ListStorageAccounts() + if err != nil { return err - } else { - printList(accounts, len(accounts), 0, 0, []string{}) - return nil } + printList(accounts, len(accounts), 0, 0, []string{}) + return nil + }) + + shellutils.R(&StorageAccountListOptions{}, "classic-storage-account-list", "List classic storage account", func(cli *azure.SRegion, args *StorageAccountListOptions) error { + accounts, err := cli.ListClassicStorageAccounts() + if err != nil { + return err + } + printList(accounts, len(accounts), 0, 0, []string{}) + return nil }) type StorageAccountOptions struct { ID string `help:"StorageAccount ID"` } + shellutils.R(&StorageAccountOptions{}, "classic-storage-account-show", "Show storage account detail", func(cli *azure.SRegion, args *StorageAccountOptions) error { + account, err := cli.GetClassicStorageAccount(args.ID) + if err != nil { + return err + } + printObject(account) + return nil + }) + shellutils.R(&StorageAccountOptions{}, "storage-account-delete", "Delete storage account", func(cli *azure.SRegion, args *StorageAccountOptions) error { return cli.DeleteStorageAccount(args.ID) }) @@ -145,7 +163,10 @@ func init() { } shellutils.R(&StorageAccountCheckeOptions{}, "storage-uniq-name", "Get a uniqel storage account name", func(cli *azure.SRegion, args *StorageAccountCheckeOptions) error { - uniqName := cli.GetUniqStorageAccountName() + uniqName, err := cli.GetUniqStorageAccountName() + if err != nil { + return err + } fmt.Println(uniqName) return nil }) diff --git a/pkg/multicloud/azure/shell/subscription.go b/pkg/multicloud/azure/shell/subscription.go index 393ebd5753..16d4b6d394 100644 --- a/pkg/multicloud/azure/shell/subscription.go +++ b/pkg/multicloud/azure/shell/subscription.go @@ -23,7 +23,7 @@ func init() { type SubscriptionListOptions struct { } shellutils.R(&SubscriptionListOptions{}, "subscription-list", "List subscriptions", func(cli *azure.SRegion, args *SubscriptionListOptions) error { - subscriptions, err := cli.GetClient().GetSubscriptions() + subscriptions, err := cli.GetClient().ListSubscriptions() if err != nil { return err } diff --git a/pkg/multicloud/azure/shell/vpc.go b/pkg/multicloud/azure/shell/vpc.go index 039cc04316..8a6798b696 100644 --- a/pkg/multicloud/azure/shell/vpc.go +++ b/pkg/multicloud/azure/shell/vpc.go @@ -21,12 +21,11 @@ import ( func init() { type VpcListOptions struct { - Classic bool `help:"List classic vpcs"` - Limit int `help:"page size"` - Offset int `help:"page offset"` + Limit int `help:"page size"` + Offset int `help:"page offset"` } shellutils.R(&VpcListOptions{}, "vpc-list", "List vpcs", func(cli *azure.SRegion, args *VpcListOptions) error { - vpcs, err := cli.GetIVpcs() + vpcs, err := cli.ListVpcs() if err != nil { return err } diff --git a/pkg/multicloud/azure/snapshot.go b/pkg/multicloud/azure/snapshot.go index dcdd4ecd20..a526245241 100644 --- a/pkg/multicloud/azure/snapshot.go +++ b/pkg/multicloud/azure/snapshot.go @@ -15,7 +15,7 @@ package azure import ( - "fmt" + "net/url" "strings" "yunion.io/x/jsonutils" @@ -23,6 +23,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SnapshotSku struct { @@ -31,6 +32,7 @@ type SnapshotSku struct { } type SSnapshot struct { + multicloud.SResourceBase region *SRegion ID string @@ -50,10 +52,6 @@ func (self *SSnapshot) GetGlobalId() string { return strings.ToLower(self.ID) } -func (self *SSnapshot) GetMetadata() *jsonutils.JSONDict { - return nil -} - func (self *SSnapshot) GetName() string { return self.Name } @@ -68,29 +66,20 @@ func (self *SSnapshot) GetStatus() string { } } -func (self *SSnapshot) IsEmulated() bool { - return false -} - -func (self *SRegion) CreateSnapshot(diskId, snapName, desc string) (*SSnapshot, error) { - disk, err := self.GetDisk(diskId) - if err != nil { - return nil, err - } - snapshot := SSnapshot{ - region: self, - Name: snapName, - Location: self.Name, - Properties: DiskProperties{ - CreationData: CreationData{ - CreateOption: "Copy", - SourceResourceID: diskId, +func (self *SRegion) CreateSnapshot(diskId, name, desc string) (*SSnapshot, error) { + params := map[string]interface{}{ + "Name": name, + "Location": self.Name, + "Properties": map[string]interface{}{ + "CreationData": map[string]string{ + "CreateOption": "Copy", + "SourceResourceID": diskId, }, - DiskSizeGB: disk.Properties.DiskSizeGB, }, - Type: "Microsoft.Compute/snapshots", + "Type": "Microsoft.Compute/snapshots", } - return &snapshot, self.client.Create(jsonutils.Marshal(snapshot), &snapshot) + snapshot := &SSnapshot{region: self} + return snapshot, self.create("", jsonutils.Marshal(params), snapshot) } func (self *SSnapshot) Delete() error { @@ -98,11 +87,11 @@ func (self *SSnapshot) Delete() error { } func (self *SSnapshot) GetSizeMb() int32 { - return self.Properties.DiskSizeGB * 1024 + return self.Properties.DiskSizeGB.Int32() * 1024 } func (self *SRegion) DeleteSnapshot(snapshotId string) error { - return self.client.Delete(snapshotId) + return self.del(snapshotId) } type AccessURIOutput struct { @@ -119,7 +108,11 @@ type AccessURI struct { } func (self *SRegion) GrantAccessSnapshot(snapshotId string) (string, error) { - body, err := self.client.PerformAction(snapshotId, "beginGetAccess", fmt.Sprintf(`{"access": "Read", "durationInSeconds": %d}`, 3600*24)) + params := map[string]interface{}{ + "access": "Read", + "durationInSeconds": 3600 * 24, + } + body, err := self.perform(snapshotId, "beginGetAccess", jsonutils.Marshal(params)) if err != nil { return "", err } @@ -128,7 +121,7 @@ func (self *SRegion) GrantAccessSnapshot(snapshotId string) (string, error) { } func (self *SSnapshot) Refresh() error { - snapshot, err := self.region.GetSnapshotDetail(self.ID) + snapshot, err := self.region.GetSnapshot(self.ID) if err != nil { return err } @@ -136,47 +129,20 @@ func (self *SSnapshot) Refresh() error { } func (self *SRegion) GetISnapshotById(snapshotId string) (cloudprovider.ICloudSnapshot, error) { - if strings.HasPrefix(snapshotId, "https://") { - //TODO - return nil, cloudprovider.ErrNotImplemented - } - return self.GetSnapshotDetail(snapshotId) + return self.GetSnapshot(snapshotId) } func (self *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { - snapshots, err := self.GetSnapShots("") + snapshots, err := self.ListSnapshots() if err != nil { return nil, err } - classicSnapshots := []SClassicSnapshot{} - storages, err := self.GetStorageAccounts() - if err != nil { - return nil, err - } - _, _classicSnapshots, err := self.GetStorageAccountsDisksWithSnapshots(storages...) - if err != nil { - return nil, err - } - classicSnapshots = append(classicSnapshots, _classicSnapshots...) - classicStorages, err := self.GetClassicStorageAccounts() - if err != nil { - return nil, err - } - _, _classicSnapshots, err = self.GetStorageAccountsDisksWithSnapshots(classicStorages...) - if err != nil { - return nil, err - } - classicSnapshots = append(classicSnapshots, _classicSnapshots...) - isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)+len(classicSnapshots)) - for i := 0; i < len(snapshots); i++ { + ret := []cloudprovider.ICloudSnapshot{} + for i := range snapshots { snapshots[i].region = self - isnapshots[i] = &snapshots[i] + ret = append(ret, &snapshots[i]) } - for i := 0; i < len(classicSnapshots); i++ { - classicSnapshots[i].region = self - isnapshots[len(snapshots)+i] = &classicSnapshots[i] - } - return isnapshots, nil + return ret, nil } func (self *SSnapshot) GetDiskId() string { @@ -190,3 +156,17 @@ func (self *SSnapshot) GetDiskType() string { func (self *SSnapshot) GetProjectId() string { return getResourceGroup(self.ID) } + +func (region *SRegion) GetSnapshot(snapshotId string) (*SSnapshot, error) { + snapshot := SSnapshot{region: region} + return &snapshot, region.get(snapshotId, url.Values{}, &snapshot) +} + +func (region *SRegion) ListSnapshots() ([]SSnapshot, error) { + result := []SSnapshot{} + err := region.list("Microsoft.Compute/snapshots", url.Values{}, &result) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/pkg/multicloud/azure/storage.go b/pkg/multicloud/azure/storage.go index ed24419e62..7384118578 100644 --- a/pkg/multicloud/azure/storage.go +++ b/pkg/multicloud/azure/storage.go @@ -19,10 +19,10 @@ import ( "strings" "yunion.io/x/jsonutils" - "yunion.io/x/log" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type Capabilitie struct { @@ -39,6 +39,7 @@ const ( var STORAGETYPES = []string{STORAGE_STD_LRS, STORAGE_PRE_LRS, STORAGE_STD_SSD} type SStorage struct { + multicloud.SResourceBase zone *SZone storageType string @@ -49,10 +50,6 @@ type SStorage struct { Capabilities []Capabilitie } -func (self *SStorage) GetMetadata() *jsonutils.JSONDict { - return nil -} - func (self *SStorage) GetId() string { return fmt.Sprintf("%s/%s", self.zone.GetGlobalId(), strings.ToLower(self.storageType)) } @@ -86,7 +83,7 @@ func (self *SStorage) GetCapacityUsedMB() int64 { } func (self *SStorage) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudprovider.ICloudDisk, error) { - disk, err := self.zone.region.CreateDisk(self.storageType, conf.Name, int32(conf.SizeGb), conf.Desc, "", conf.ProjectId) + disk, err := self.zone.region.CreateDisk(self.storageType, conf.Name, int32(conf.SizeGb), conf.Desc, "", "", conf.ProjectId) if err != nil { return nil, err } @@ -114,38 +111,6 @@ func (self *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { if storageType == strings.ToLower(self.storageType) { disks[i].storage = self idisks = append(idisks, &disks[i]) - log.Debugf("find disk %s for storage %s", disks[i].GetName(), self.GetName()) - } - } - storageaccounts, err := self.zone.region.GetStorageAccounts() - if err != nil { - log.Errorf("List storage account for get idisks error: %v", err) - return nil, err - } - for i := 0; i < len(storageaccounts); i++ { - storageType := strings.ToLower(storageaccounts[i].Sku.Name) - if strings.ToLower(self.storageType) != storageType { - continue - } - disks, _, err := self.zone.region.GetStorageAccountDisksWithSnapshots(storageaccounts[i]) - if err != nil { - return nil, err - } - for j := 0; j < len(disks); j++ { - disk := SDisk{ - storage: self, - Sku: DiskSku{ - Name: storageaccounts[i].Sku.Name, - Tier: storageaccounts[i].Sku.Tier, - }, - Properties: DiskProperties{ - DiskSizeGB: disks[j].DiskSizeGB, - OsType: disks[j].diskType, - }, - ID: disks[j].VhdUri, - Name: disks[j].DiskName, - } - idisks = append(idisks, &disk) } } return idisks, nil diff --git a/pkg/multicloud/azure/storageaccount.go b/pkg/multicloud/azure/storageaccount.go index 891f9a76d3..d1c1367217 100644 --- a/pkg/multicloud/azure/storageaccount.go +++ b/pkg/multicloud/azure/storageaccount.go @@ -21,6 +21,7 @@ import ( "io" "math/rand" "net/http" + "net/url" "path" "strconv" "strings" @@ -66,9 +67,6 @@ type SStorageEndpoints struct { } type AccountProperties struct { - //classic - ClassicStorageProperties - //normal PrimaryEndpoints SStorageEndpoints `json:"primaryEndpoints,omitempty"` ProvisioningState string @@ -101,19 +99,22 @@ type SStorageAccount struct { Properties AccountProperties `json:"properties"` } -func (self *SRegion) GetStorageAccounts() ([]*SStorageAccount, error) { - iBuckets, err := self.client.getIBuckets() +func (self *SRegion) listStorageAccounts() ([]SStorageAccount, error) { + accounts := []SStorageAccount{} + err := self.list("Microsoft.Storage/storageAccounts", url.Values{}, &accounts) if err != nil { - return nil, errors.Wrap(err, "getIBuckets") + return nil, errors.Wrapf(err, "list") } - ret := make([]*SStorageAccount, 0) - for i := range iBuckets { - if iBuckets[i].GetLocation() != self.GetId() { - continue - } - ret = append(ret, iBuckets[i].(*SStorageAccount)) + result := []SStorageAccount{} + for i := range accounts { + accounts[i].region = self + result = append(result, accounts[i]) } - return ret, nil + return result, nil +} + +func (self *SRegion) ListStorageAccounts() ([]SStorageAccount, error) { + return self.listStorageAccounts() } func randomString(prefix string, length int) string { @@ -126,55 +127,29 @@ func randomString(prefix string, length int) string { return prefix + string(result) } -func (self *SRegion) GetUniqStorageAccountName() string { - for { - uniqString := randomString("storage", 8) - requestBody := fmt.Sprintf(`{"name": "%s", "type": "Microsoft.Storage/storageAccounts"}`, uniqString) - body, err := self.client.CheckNameAvailability("Microsoft.Storage", requestBody) - if err != nil { - continue - } - if avaliable, _ := body.Bool("nameAvailable"); avaliable { - return uniqString +func (self *SRegion) GetUniqStorageAccountName() (string, error) { + for i := 0; i < 20; i++ { + name := randomString("storage", 8) + exist, err := self.checkStorageAccountNameExist(name) + if err == nil && !exist { + return name, nil } } + return "", fmt.Errorf("failed to found uniq storage name") } -type sStorageAccountCheckNameAvailabilityInput struct { - Name string - Type string -} - -type sStorageAccountCheckNameAvailabilityOutput struct { +type sNameAvailableOutput struct { NameAvailable bool `json:"nameAvailable"` Reason string `json:"reason"` Message string `json:"message"` } func (self *SRegion) checkStorageAccountNameExist(name string) (bool, error) { - url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Storage/checkNameAvailability?api-version=2019-04-01", self.client.subscriptionId) - body := jsonutils.Marshal(sStorageAccountCheckNameAvailabilityInput{ - Name: name, - Type: "Microsoft.Storage/storageAccounts", - }) - resp, err := self.client.jsonRequest("POST", url, body.String()) + ok, err := self.client.CheckNameAvailability("Microsoft.Storage/storageAccounts", name) if err != nil { - return false, errors.Wrap(err, "jsonRequest") - } - output := sStorageAccountCheckNameAvailabilityOutput{} - err = resp.Unmarshal(&output) - if err != nil { - return false, errors.Wrap(err, "Unmarshal") - } - if output.NameAvailable { - return false, nil - } else { - if output.Reason == "AlreadyExists" { - return true, nil - } else { - return false, errors.Error(output.Reason) - } + return false, errors.Wrapf(err, "CheckNameAvailability(%s)", name) } + return !ok, nil } type SStorageAccountSku struct { @@ -196,7 +171,7 @@ type SStorageAccountSku struct { func (self *SRegion) GetStorageAccountSkus() ([]SStorageAccountSku, error) { skus := make([]SStorageAccountSku, 0) - err := self.client.List("providers/Microsoft.Storage/skus?api-version=2019-04-01", &skus) + err := self.client.list("Microsoft.Storage/skus", url.Values{}, &skus) if err != nil { return nil, errors.Wrap(err, "List") } @@ -254,11 +229,10 @@ func (self *SRegion) createStorageAccount(name string, skuName string) (*SStorag Type: "Microsoft.Storage/storageAccounts", } - err := self.client.Create(jsonutils.Marshal(storageaccount), &storageaccount) + err := self.create("", jsonutils.Marshal(storageaccount), &storageaccount) if err != nil { return nil, errors.Wrap(err, "Create") } - self.client.invalidateIBuckets() return &storageaccount, nil } @@ -268,7 +242,10 @@ func (self *SRegion) CreateStorageAccount(storageAccount string) (*SStorageAccou return account, nil } if errors.Cause(err) == cloudprovider.ErrNotFound { - uniqName := self.GetUniqStorageAccountName() + uniqName, err := self.GetUniqStorageAccountName() + if err != nil { + return nil, errors.Wrapf(err, "GetUniqStorageAccountName") + } stoargeaccount := SStorageAccount{ region: self, Sku: SSku{ @@ -284,21 +261,21 @@ func (self *SRegion) CreateStorageAccount(storageAccount string) (*SStorageAccou Type: "Microsoft.Storage/storageAccounts", Tags: map[string]string{"id": storageAccount}, } - return &stoargeaccount, self.client.Create(jsonutils.Marshal(stoargeaccount), &stoargeaccount) + return &stoargeaccount, self.create("", jsonutils.Marshal(stoargeaccount), &stoargeaccount) } return nil, err } func (self *SRegion) getStorageAccountID(storageAccount string) (*SStorageAccount, error) { - accounts, err := self.GetStorageAccounts() + accounts, err := self.ListStorageAccounts() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "ListStorageAccounts") } for i := 0; i < len(accounts); i++ { for k, v := range accounts[i].Tags { if k == "id" && v == storageAccount { accounts[i].region = self - return accounts[i], nil + return &accounts[i], nil } } } @@ -307,7 +284,7 @@ func (self *SRegion) getStorageAccountID(storageAccount string) (*SStorageAccoun func (self *SRegion) GetStorageAccountDetail(accountId string) (*SStorageAccount, error) { account := SStorageAccount{region: self} - err := self.client.Get(accountId, []string{}, &account) + err := self.get(accountId, url.Values{}, &account) if err != nil { return nil, err } @@ -321,7 +298,7 @@ type AccountKeys struct { } func (self *SRegion) GetStorageAccountKey(accountId string) (string, error) { - body, err := self.client.PerformAction(accountId, "listKeys", "") + body, err := self.perform(accountId, "listKeys", nil) if err != nil { return "", err } @@ -342,23 +319,25 @@ func (self *SRegion) GetStorageAccountKey(accountId string) (string, error) { } func (self *SRegion) DeleteStorageAccount(accountId string) error { - return self.client.Delete(accountId) + return self.del(accountId) } -func (self *SRegion) GetClassicStorageAccounts() ([]*SStorageAccount, error) { - result := make([]*SStorageAccount, 0) +func (self *SRegion) ListClassicStorageAccounts() ([]SStorageAccount, error) { accounts := make([]SStorageAccount, 0) - err := self.client.ListAll("Microsoft.ClassicStorage/storageAccounts", &accounts) + err := self.list("Microsoft.ClassicStorage/storageAccounts", url.Values{}, &accounts) if err != nil { return nil, err } - for i := 0; i < len(accounts); i++ { - if accounts[i].Location == self.Name { - accounts[i].region = self - result = append(result, &accounts[i]) - } + return accounts, nil +} + +func (self *SRegion) GetClassicStorageAccount(id string) (*SStorageAccount, error) { + account := &SStorageAccount{} + err := self.get(id, url.Values{}, account) + if err != nil { + return nil, errors.Wrapf(err, "get(%s)", id) } - return result, nil + return account, nil } func (self *SStorageAccount) GetAccountKey() (accountKey string, err error) { @@ -369,18 +348,6 @@ func (self *SStorageAccount) GetAccountKey() (accountKey string, err error) { return self.accountKey, err } -func (self *SStorageAccount) GetBlobBaseUrl() string { - if self.Type == "Microsoft.Storage/storageAccounts" { - return self.Properties.PrimaryEndpoints.Blob - } - for _, url := range self.Properties.Endpoints { - if strings.Contains(url, ".blob.") { - return url - } - } - return "" -} - func (self *SStorageAccount) getBlobServiceClient() (*storage.BlobStorageClient, error) { accessKey, err := self.GetAccountKey() if err != nil { diff --git a/pkg/multicloud/azure/storagecache.go b/pkg/multicloud/azure/storagecache.go index 83a67b1411..055e9a7409 100644 --- a/pkg/multicloud/azure/storagecache.go +++ b/pkg/multicloud/azure/storagecache.go @@ -133,9 +133,9 @@ func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.To } func (self *SStoragecache) checkStorageAccount() (*SStorageAccount, error) { - storageaccounts, err := self.region.GetStorageAccounts() + storageaccounts, err := self.region.ListStorageAccounts() if err != nil { - return nil, errors.Wrap(err, "GetStorageAccounts") + return nil, errors.Wrap(err, "ListStorageAccounts") } if len(storageaccounts) == 0 { storageaccount, err := self.region.CreateStorageAccount(self.region.Name) @@ -146,16 +146,16 @@ func (self *SStoragecache) checkStorageAccount() (*SStorageAccount, error) { } for i := 0; i < len(storageaccounts); i++ { if id, ok := storageaccounts[i].Tags["id"]; ok && id == self.region.Name { - return storageaccounts[i], nil + return &storageaccounts[i], nil } } - storageaccount := storageaccounts[0] + storageaccount := &storageaccounts[0] if storageaccount.Tags == nil { storageaccount.Tags = map[string]string{} } storageaccount.Tags["id"] = self.region.Name - err = self.region.client.Update(jsonutils.Marshal(storageaccount), nil) + err = self.region.update(jsonutils.Marshal(storageaccount), nil) if err != nil { return nil, errors.Wrapf(err, "Update(%s)", jsonutils.Marshal(storageaccount).String()) } diff --git a/pkg/multicloud/azure/subscription.go b/pkg/multicloud/azure/subscription.go index 3e8bf49a35..bb92999bbf 100644 --- a/pkg/multicloud/azure/subscription.go +++ b/pkg/multicloud/azure/subscription.go @@ -1,6 +1,20 @@ +// 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 azure -import "yunion.io/x/pkg/errors" +import "net/url" type SSubscription struct { SubscriptionId string `json:"subscriptionId"` @@ -8,15 +22,8 @@ type SSubscription struct { DisplayName string `json:"displayName"` } -func (self *SAzureClient) GetSubscriptions() ([]SSubscription, error) { - resp, err := self.ListSubscriptions() - if err != nil { - return nil, err - } - subscriptions := []SSubscription{} - err = resp.Unmarshal(&subscriptions, "value") - if err != nil { - return nil, errors.Wrap(err, "resp.Unmarshal") - } - return subscriptions, nil +func (self *SAzureClient) ListSubscriptions() ([]SSubscription, error) { + result := []SSubscription{} + err := self.list("subscriptions", url.Values{}, &result) + return result, err } diff --git a/pkg/multicloud/azure/usage.go b/pkg/multicloud/azure/usage.go index e3f88efb50..47de5d8d25 100644 --- a/pkg/multicloud/azure/usage.go +++ b/pkg/multicloud/azure/usage.go @@ -16,6 +16,7 @@ package azure import ( "fmt" + "net/url" "strings" "yunion.io/x/pkg/errors" @@ -59,7 +60,7 @@ func (u *SUsage) GetCurrentQuotaUsedCount() int { func (region *SRegion) GetUsage(resourceType string) ([]SUsage, error) { usage := []SUsage{} resource := fmt.Sprintf("%s/locations/%s/usages", resourceType, region.Name) - err := region.client.ListAll(resource, &usage) + err := region.client.list(resource, url.Values{}, &usage) if err != nil { return nil, errors.Wrapf(err, "ListAll(%s)", resource) } diff --git a/pkg/multicloud/azure/vpc.go b/pkg/multicloud/azure/vpc.go index 63ba80ab14..a04287f899 100644 --- a/pkg/multicloud/azure/vpc.go +++ b/pkg/multicloud/azure/vpc.go @@ -15,9 +15,11 @@ package azure import ( + "net/url" "strings" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud" @@ -32,30 +34,18 @@ type SubnetPropertiesFormat struct { //ProvisioningState string } -type Subnet struct { - Properties SubnetPropertiesFormat - Name string - ID string -} - type VirtualNetworkPropertiesFormat struct { ProvisioningState string Status string VirtualNetworkSiteName string AddressSpace AddressSpace `json:"addressSpace,omitempty"` - Subnets *[]SNetwork `json:"subnets,omitempty"` + Subnets []SNetwork `json:"subnets,omitempty"` } type SVpc struct { multicloud.SVpc - region *SRegion - iwires []cloudprovider.ICloudWire - secgroups []cloudprovider.ICloudSecurityGroup - - isDefault bool - ID string Name string Etag string @@ -86,11 +76,14 @@ func (self *SVpc) IsEmulated() bool { } func (self *SVpc) GetIsDefault() bool { - return self.isDefault + return true } func (self *SVpc) GetCidrBlock() string { - return self.Properties.AddressSpace.AddressPrefixes[0] + if len(self.Properties.AddressSpace.AddressPrefixes) > 0 { + return self.Properties.AddressSpace.AddressPrefixes[0] + } + return "" } func (self *SVpc) Delete() error { @@ -98,63 +91,20 @@ func (self *SVpc) Delete() error { } func (self *SRegion) DeleteVpc(vpcId string) error { - return self.client.Delete(vpcId) -} - -func (self *SVpc) getSecurityGroups() ([]SSecurityGroup, error) { - securityGroups, err := self.region.GetSecurityGroups("") - if err != nil { - return nil, err - } - for i := 0; i < len(securityGroups); i++ { - securityGroups[i].vpc = self - } - return securityGroups, nil -} - -func (self *SVpc) fetchSecurityGroups() error { - self.secgroups = make([]cloudprovider.ICloudSecurityGroup, 0) - if secgrps, err := self.getSecurityGroups(); err != nil { - return err - } else { - for i := 0; i < len(secgrps); i++ { - self.secgroups = append(self.secgroups, &secgrps[i]) - } - return nil - } -} - -func (self *SVpc) getWire() *SWire { - if self.iwires == nil { - self.fetchWires() - } - return self.iwires[0].(*SWire) -} - -func (self *SVpc) fetchNetworks() error { - vpc, err := self.region.GetVpc(self.ID) - if err != nil { - return err - } - if vpc.Properties.Subnets != nil { - networks := *vpc.Properties.Subnets - wire := self.getWire() - for i := 0; i < len(networks); i++ { - networks[i].wire = wire - wire.addNetwork(&networks[i]) - } - } - return nil + return self.del(vpcId) } func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { - if self.secgroups == nil { - err := self.fetchSecurityGroups() - if err != nil { - return nil, err - } + secgroups, err := self.region.ListSecgroups() + if err != nil { + return nil, errors.Wrapf(err, "ListSecgroups") } - return self.secgroups, nil + ret := []cloudprovider.ICloudSecurityGroup{} + for i := range secgroups { + secgroups[i].region = self.region + ret = append(ret, &secgroups[i]) + } + return ret, nil } func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { @@ -166,44 +116,21 @@ func (self *SVpc) GetIRouteTableById(routeTableId string) (cloudprovider.ICloudR return nil, cloudprovider.ErrNotSupported } -func (self *SVpc) fetchWires() error { - networks := make([]cloudprovider.ICloudNetwork, len(*self.Properties.Subnets)) - if len(self.region.izones) == 0 { - self.region.fetchZones() +func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { + wire := self.getWire() + if wire.GetGlobalId() != wireId { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, wireId) } - wire := SWire{zone: self.region.izones[0].(*SZone), vpc: self, inetworks: networks} - for i, _network := range *self.Properties.Subnets { - network := SNetwork{wire: &wire} - if err := jsonutils.Update(&network, _network); err != nil { - return err - } - networks[i] = &network - } - self.iwires = []cloudprovider.ICloudWire{&wire} - return nil + return wire, nil } -func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { - if self.iwires == nil { - if err := self.fetchNetworks(); err != nil { - return nil, err - } - } - for i := 0; i < len(self.iwires); i++ { - if self.iwires[i].GetGlobalId() == wireId { - return self.iwires[i], nil - } - } - return nil, cloudprovider.ErrNotFound +func (self *SVpc) getWire() *SWire { + zone := self.region.getZone() + return &SWire{zone: zone, vpc: self} } func (self *SVpc) GetIWires() ([]cloudprovider.ICloudWire, error) { - if self.iwires == nil { - if err := self.fetchWires(); err != nil { - return nil, err - } - } - return self.iwires, nil + return []cloudprovider.ICloudWire{self.getWire()}, nil } func (self *SVpc) GetRegion() cloudprovider.ICloudRegion { @@ -219,30 +146,22 @@ func (self *SVpc) GetStatus() string { func (region *SRegion) GetVpc(vpcId string) (*SVpc, error) { vpc := SVpc{region: region} - return &vpc, region.client.Get(vpcId, []string{}, &vpc) + return &vpc, region.get(vpcId, url.Values{}, &vpc) } func (self *SVpc) Refresh() error { - if vpc, err := self.region.GetVpc(self.ID); err != nil { - return err - } else if err := jsonutils.Update(self, vpc); err != nil { + vpc, err := self.region.GetVpc(self.ID) + if err != nil { return err } - return nil -} - -func (self *SVpc) addWire(wire *SWire) { - if self.iwires == nil { - self.iwires = make([]cloudprovider.ICloudWire, 0) - } - self.iwires = append(self.iwires, wire) + return jsonutils.Update(self, vpc) } func (self *SVpc) GetNetworks() []SNetwork { - return *self.Properties.Subnets + return self.Properties.Subnets } -func (self *SRegion) GetNetworkDetail(networkId string) (*Subnet, error) { - subnet := Subnet{} - return &subnet, self.client.Get(networkId, []string{}, &subnet) +func (self *SRegion) GetNetwork(networkId string) (*SNetwork, error) { + network := SNetwork{} + return &network, self.get(networkId, url.Values{}, &network) } diff --git a/pkg/multicloud/azure/wire.go b/pkg/multicloud/azure/wire.go index 5394310776..2adf44e0b2 100644 --- a/pkg/multicloud/azure/wire.go +++ b/pkg/multicloud/azure/wire.go @@ -19,21 +19,20 @@ import ( "strings" "yunion.io/x/jsonutils" - "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SWire struct { + multicloud.SResourceBase + zone *SZone vpc *SVpc inetworks []cloudprovider.ICloudNetwork } -func (self *SWire) GetMetadata() *jsonutils.JSONDict { - return nil -} - func (self *SWire) GetId() string { return fmt.Sprintf("%s/%s/%s", self.zone.region.GetGlobalId(), self.zone.region.client.subscriptionId, self.vpc.GetName()) } @@ -54,54 +53,28 @@ func (self *SWire) GetStatus() string { return "available" } -func (self *SWire) Refresh() error { - return nil -} - -func (self *SWire) addNetwork(network *SNetwork) { - if self.inetworks == nil { - self.inetworks = make([]cloudprovider.ICloudNetwork, 0) - } - find := false - for i := 0; i < len(self.inetworks); i += 1 { - if self.inetworks[i].GetGlobalId() == strings.ToLower(network.ID) { - find = true - break - } - } - if !find { - self.inetworks = append(self.inetworks, network) - } -} - -func (self *SRegion) createNetwork(vpc *SVpc, subnetName string, cidr string, desc string) (*SNetwork, error) { - subnet := SNetwork{ - Name: subnetName, - Properties: SubnetPropertiesFormat{ - AddressPrefix: cidr, +func (self *SRegion) CreateNetwork(vpcId, name string, cidr string, desc string) (*SNetwork, error) { + params := map[string]interface{}{ + "Name": name, + "Properties": map[string]interface{}{ + "AddressPrefix": cidr, }, } - if vpc.Properties.Subnets == nil { - subnets := []SNetwork{subnet} - vpc.Properties.Subnets = &subnets - } else { - *vpc.Properties.Subnets = append(*vpc.Properties.Subnets, subnet) - } - vpc.Properties.ProvisioningState = "" - err := self.client.Update(jsonutils.Marshal(vpc), vpc) + resource := fmt.Sprintf("%s/subnets/%s", vpcId, name) + network := &SNetwork{} + resp, err := self.put(resource, jsonutils.Marshal(params)) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "put(%s)", resource) } - for i := 0; i < len(*vpc.Properties.Subnets); i++ { - if (*vpc.Properties.Subnets)[i].Name == subnetName { - subnet.ID = (*vpc.Properties.Subnets)[i].ID - } + err = resp.Unmarshal(network) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") } - return &subnet, nil + return network, nil } func (self *SWire) CreateINetwork(opts *cloudprovider.SNetworkCreateOptions) (cloudprovider.ICloudNetwork, error) { - network, err := self.zone.region.createNetwork(self.vpc, opts.Name, opts.Cidr, opts.Desc) + network, err := self.zone.region.CreateNetwork(self.vpc.ID, opts.Name, opts.Cidr, opts.Desc) if err != nil { return nil, err } @@ -123,14 +96,17 @@ func (self *SWire) GetINetworkById(netid string) (cloudprovider.ICloudNetwork, e return networks[i], nil } } - return nil, cloudprovider.ErrNotFound + return nil, errors.Wrapf(cloudprovider.ErrNotFound, netid) } func (self *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) { - if err := self.vpc.fetchNetworks(); err != nil { - return nil, err + ret := []cloudprovider.ICloudNetwork{} + networks := self.vpc.GetNetworks() + for i := range networks { + networks[i].wire = self + ret = append(ret, &networks[i]) } - return self.inetworks, nil + return ret, nil } func (self *SWire) GetIVpc() cloudprovider.ICloudVpc { @@ -140,19 +116,3 @@ func (self *SWire) GetIVpc() cloudprovider.ICloudVpc { func (self *SWire) GetIZone() cloudprovider.ICloudZone { return self.zone } - -func (self *SWire) getNetworkById(networkId string) *SNetwork { - if networks, err := self.GetINetworks(); err != nil { - log.Errorf("getNetworkById error: %v", err) - return nil - } else { - log.Debugf("search for networks %d", len(networks)) - for i := 0; i < len(networks); i++ { - network := networks[i].(*SNetwork) - if strings.ToLower(networkId) == strings.ToLower(network.ID) { - return network - } - } - } - return nil -} diff --git a/pkg/multicloud/azure/zone.go b/pkg/multicloud/azure/zone.go index b38e04311a..f64e481ba9 100644 --- a/pkg/multicloud/azure/zone.go +++ b/pkg/multicloud/azure/zone.go @@ -15,32 +15,18 @@ package azure import ( - "strings" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SZone struct { + multicloud.SResourceBase region *SRegion - iwires []cloudprovider.ICloudWire - iclassicWires []cloudprovider.ICloudWire - istorages []cloudprovider.ICloudStorage - iclassicStorages []cloudprovider.ICloudStorage - - storageTypes []string - classicStorageTypes []string - Name string - host *SHost - classicHost *SClassicHost -} - -func (self *SZone) GetMetadata() *jsonutils.JSONDict { - return nil + Name string } func (self *SZone) GetId() string { @@ -60,7 +46,7 @@ func (self *SZone) IsEmulated() bool { } func (self *SZone) GetStatus() string { - return "enable" + return api.ZONE_ENABLE } func (self *SZone) Refresh() error { @@ -69,168 +55,119 @@ func (self *SZone) Refresh() error { } func (self *SZone) getHost() *SHost { - if self.host == nil { - self.host = &SHost{zone: self} - } - return self.host + return &SHost{zone: self} } func (self *SZone) getClassicHost() *SClassicHost { - if self.classicHost == nil { - self.classicHost = &SClassicHost{zone: self} - } - return self.classicHost + return &SClassicHost{zone: self} } func (self *SZone) GetIRegion() cloudprovider.ICloudRegion { return self.region } -func (self *SZone) fetchClassicStorages() error { - storageaccounts, err := self.region.GetClassicStorageAccounts() - if err != nil { - return err +func (self *SZone) ListStorageTypes() []SStorage { + storages := []SStorage{} + for _, storageType := range STORAGETYPES { + storage := SStorage{zone: self, storageType: storageType} + storages = append(storages, storage) } - self.iclassicStorages = make([]cloudprovider.ICloudStorage, len(storageaccounts)) - for i := 0; i < len(storageaccounts); i++ { - storage := SClassicStorage{ - zone: self, - ID: storageaccounts[i].ID, - Name: storageaccounts[i].Name, - Type: storageaccounts[i].Type, - Location: storageaccounts[i].Location, - Properties: storageaccounts[i].Properties.ClassicStorageProperties, - } - self.iclassicStorages[i] = &storage - } - return nil + return storages } -func (self *SZone) fetchStorages() error { - self.istorages = make([]cloudprovider.ICloudStorage, len(STORAGETYPES)) - for i, storageType := range STORAGETYPES { - storage := SStorage{zone: self, storageType: storageType} - self.istorages[i] = &storage +func (self *SRegion) ListClassicStorageTypes() []SClassicStorage { + storages := []SClassicStorage{} + for _, storageType := range []string{STORAGE_LRS, STORAGE_GRS} { + storage := SClassicStorage{AccountType: storageType, region: self} + storages = append(storages, storage) } - return nil + return storages +} + +func (self *SZone) GetIClassicStorages() []cloudprovider.ICloudStorage { + ret := []cloudprovider.ICloudStorage{} + storages := self.region.ListClassicStorageTypes() + for i := range storages { + ret = append(ret, &storages[i]) + } + return ret +} + +func (self *SZone) getIStorages() []cloudprovider.ICloudStorage { + ret := []cloudprovider.ICloudStorage{} + storages := self.ListStorageTypes() + for i := range storages { + storages[i].zone = self + ret = append(ret, &storages[i]) + } + return ret } func (self *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) { - err := self.fetchStorages() - if err != nil { - return nil, errors.Wrapf(err, "fetchStorages") - } - err = self.fetchClassicStorages() - if err != nil { - return nil, errors.Wrapf(err, "fetchClassicStorages") - } - istorages := append(self.istorages, self.iclassicStorages...) - return istorages, nil + ret := self.getIStorages() + ret = append(ret, self.GetIClassicStorages()...) + return ret, nil } func (self *SZone) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { - if self.istorages == nil { - err := self.fetchStorages() - if err != nil { - return nil, errors.Wrapf(err, "fetchStorages") - } - } - for i := 0; i < len(self.istorages); i += 1 { - if self.istorages[i].GetGlobalId() == id { - return self.istorages[i], nil - } - } - return nil, cloudprovider.ErrNotFound -} - -func (self *SZone) getStorageByType(storageType string) (*SStorage, error) { - _, err := self.GetIStorages() + storages, err := self.GetIStorages() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "GetIStorages") } - for i := 0; i < len(self.istorages); i += 1 { - storage := self.istorages[i].(*SStorage) - if strings.ToLower(storage.storageType) == strings.ToLower(storageType) { - return storage, nil + for i := range storages { + if storages[i].GetGlobalId() == id { + return storages[i], nil } } - return nil, cloudprovider.ErrNotFound -} - -func (self *SZone) getClassicStorageByType(storageType string) (*SClassicStorage, error) { - _, err := self.GetIStorages() - if err != nil { - return nil, err - } - for i := 0; i < len(self.iclassicStorages); i += 1 { - storage := self.iclassicStorages[i].(*SClassicStorage) - if strings.ToLower(storage.Properties.AccountType) == strings.ToLower(storageType) { - return storage, nil - } - } - return nil, cloudprovider.ErrNotFound + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) } func (self *SZone) GetIHostById(id string) (cloudprovider.ICloudHost, error) { - host := self.getHost() - if host.GetGlobalId() == id { - return host, nil + hosts, err := self.GetIHosts() + if err != nil { + return nil, errors.Wrapf(err, "GetIHosts") } - classicHost := self.getClassicHost() - if classicHost.GetGlobalId() == id { - return classicHost, nil + for i := range hosts { + if hosts[i].GetGlobalId() == id { + return hosts[i], nil + } } - return nil, cloudprovider.ErrNotFound + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) } func (self *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { return []cloudprovider.ICloudHost{self.getHost(), self.getClassicHost()}, nil } -func (self *SZone) addWire(wire *SWire) { - if self.iwires == nil { - self.iwires = make([]cloudprovider.ICloudWire, 0) +func (self *SZone) GetIClassicWires() ([]cloudprovider.ICloudWire, error) { + wires := []cloudprovider.ICloudWire{} + classicVpcs, err := self.region.ListClassicVpcs() + if err != nil { + return nil, errors.Wrapf(err, "ListClassicVpcs") } - self.iwires = append(self.iwires, wire) -} - -func (self *SZone) addClassicWire(wire *SClassicWire) { - if self.iclassicWires == nil { - self.iclassicWires = make([]cloudprovider.ICloudWire, 0) + for i := range classicVpcs { + classicVpcs[i].region = self.region + wire := &SClassicWire{vpc: &classicVpcs[i], zone: self} + wires = append(wires, wire) } - self.iclassicWires = append(self.iclassicWires, wire) + return wires, nil } func (self *SZone) GetIWires() ([]cloudprovider.ICloudWire, error) { - return self.iwires, nil -} - -func (self *SZone) GetIClassicWires() ([]cloudprovider.ICloudWire, error) { - return self.iclassicWires, nil -} - -func (self *SZone) getNetworkById(networId string) *SNetwork { - log.Debugf("Search in wires %d", len(self.iwires)) - for i := 0; i < len(self.iwires); i += 1 { - log.Debugf("Search in wire %s", self.iwires[i].GetName()) - wire := self.iwires[i].(*SWire) - net := wire.getNetworkById(networId) - if net != nil { - return net - } + vpcs, err := self.region.ListVpcs() + if err != nil { + return nil, errors.Wrapf(err, "ListVpcs") } - return nil -} - -func (self *SZone) getClassicNetworkById(networId string) *SClassicNetwork { - log.Debugf("Search in wires %d", len(self.iclassicWires)) - for i := 0; i < len(self.iclassicWires); i += 1 { - log.Debugf("Search in wire %s", self.iclassicWires[i].GetName()) - wire := self.iclassicWires[i].(*SClassicWire) - net := wire.getNetworkById(networId) - if net != nil { - return net - } + wires := []cloudprovider.ICloudWire{} + for i := range vpcs { + vpcs[i].region = self.region + wire := &SWire{vpc: &vpcs[i], zone: self} + wires = append(wires, wire) } - return nil + classWires, err := self.GetIClassicWires() + if err != nil { + return nil, errors.Wrapf(err, "GetIClassicWires") + } + wires = append(wires, classWires...) + return wires, nil } diff --git a/pkg/multicloud/bucket_base.go b/pkg/multicloud/bucket_base.go index 96f206b74c..4660b8deb4 100644 --- a/pkg/multicloud/bucket_base.go +++ b/pkg/multicloud/bucket_base.go @@ -94,7 +94,7 @@ func (b *SBaseBucket) GetCORSRules() ([]cloudprovider.SBucketCORSRule, error) { return nil, cloudprovider.ErrNotImplemented } -func (b *SBaseBucket) DeleteCORS() error { +func (b *SBaseBucket) DeleteCORS(id []string) error { return cloudprovider.ErrNotImplemented } diff --git a/pkg/multicloud/ctyun/instance.go b/pkg/multicloud/ctyun/instance.go index 829fab68c9..7edf59d6f3 100644 --- a/pkg/multicloud/ctyun/instance.go +++ b/pkg/multicloud/ctyun/instance.go @@ -476,7 +476,7 @@ func (self *SInstance) StartVM(ctx context.Context) error { return nil } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { err := self.host.zone.region.StopVM(self.GetId()) if err != nil { return errors.Wrap(err, "Instance.StopVM") diff --git a/pkg/multicloud/dbinstance_backup_base.go b/pkg/multicloud/dbinstance_backup_base.go index 94a0bc057f..93b942048c 100644 --- a/pkg/multicloud/dbinstance_backup_base.go +++ b/pkg/multicloud/dbinstance_backup_base.go @@ -36,3 +36,11 @@ func (backup *SDBInstanceBackupBase) Delete() error { func (backup *SDBInstanceBackupBase) GetProjectId() string { return "" } + +func (backup *SDBInstanceBackupBase) CreateICloudDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + return nil, errors.Wrap(cloudprovider.ErrNotImplemented, "CreateICloudDBInstance") +} + +func (backup *SDBInstanceBackupBase) GetBackupMethod() cloudprovider.TBackupMethod { + return cloudprovider.BackupMethodUnknown +} diff --git a/pkg/multicloud/disk_base.go b/pkg/multicloud/disk_base.go index 4ae664a363..81bc672d2f 100644 --- a/pkg/multicloud/disk_base.go +++ b/pkg/multicloud/disk_base.go @@ -15,6 +15,7 @@ package multicloud type SDisk struct { + SVirtualResourceBase SBillingBase } diff --git a/pkg/multicloud/esxi/storage.go b/pkg/multicloud/esxi/storage.go index a7d7c22528..7e7cc9358a 100644 --- a/pkg/multicloud/esxi/storage.go +++ b/pkg/multicloud/esxi/storage.go @@ -389,15 +389,20 @@ func (self *SDatastore) isLocalVMFS() bool { func (self *SDatastore) GetStorageType() string { moStore := self.getDatastore() - switch strings.ToLower(moStore.Summary.Type) { + t := strings.ToLower(moStore.Summary.Type) + switch t { case "vmfs": if self.isLocalVMFS() { return api.STORAGE_LOCAL } else { return api.STORAGE_NAS } - case "nfs", "nfs41", "cifs", "vsan": - return api.STORAGE_NAS + case "nfs", "nfs41": + return api.STORAGE_NFS + case "vsan": + return api.STORAGE_VSAN + case "cifs": + return api.STORAGE_CIFS default: log.Fatalf("unsupported datastore type %s", moStore.Summary.Type) return "" diff --git a/pkg/multicloud/esxi/virtualmachine.go b/pkg/multicloud/esxi/virtualmachine.go index a1639abf62..32e32f25ea 100644 --- a/pkg/multicloud/esxi/virtualmachine.go +++ b/pkg/multicloud/esxi/virtualmachine.go @@ -456,11 +456,11 @@ func makeNicStartConnected(nic *SVirtualNIC) *types.VirtualDeviceConfigSpec { return &editSpec } -func (self *SVirtualMachine) StopVM(ctx context.Context, isForce bool) error { +func (self *SVirtualMachine) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { if self.GetStatus() == api.VM_READY { return nil } - if !isForce && self.isToolsOk() { + if !opts.IsForce && self.isToolsOk() { return self.shutdownVM(ctx) } else { return self.poweroffVM(ctx) diff --git a/pkg/multicloud/google/dbinstance_backup.go b/pkg/multicloud/google/dbinstance_backup.go index e24b68fbb9..9af5c191dc 100644 --- a/pkg/multicloud/google/dbinstance_backup.go +++ b/pkg/multicloud/google/dbinstance_backup.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" ) type OperationError struct { @@ -32,6 +33,7 @@ type OperationError struct { } type SDBInstanceBackup struct { + multicloud.SDBInstanceBackupBase rds *SDBInstance Kind string diff --git a/pkg/multicloud/google/instance.go b/pkg/multicloud/google/instance.go index 388189f48c..520f6a1dbe 100644 --- a/pkg/multicloud/google/instance.go +++ b/pkg/multicloud/google/instance.go @@ -430,7 +430,7 @@ func (instance *SInstance) StartVM(ctx context.Context) error { return instance.host.zone.region.StartInstance(instance.SelfLink) } -func (instance *SInstance) StopVM(ctx context.Context, isForce bool) error { +func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { return instance.host.zone.region.StopInstance(instance.SelfLink) } diff --git a/pkg/multicloud/huawei/client/modules/mod_disks.go b/pkg/multicloud/huawei/client/modules/mod_disks.go index 8488f315d8..be68003211 100644 --- a/pkg/multicloud/huawei/client/modules/mod_disks.go +++ b/pkg/multicloud/huawei/client/modules/mod_disks.go @@ -66,3 +66,11 @@ func (self *SDiskManager) AsyncCreate(params jsonutils.JSONObject) (string, erro // 包年包月机器 return ret.GetString("order_id") } + +// https://support.huaweicloud.com/api-evs/evs_04_2003.html +func (self *SDiskManager) GetDiskTypes() (*responses.ListResult, error) { + originKeyword := self.ResourceKeyword + self.ResourceKeyword = "" + defer func() { self.ResourceKeyword = originKeyword }() + return self.ListInContextWithSpec(self.ctx, "types", nil, "volume_types") +} diff --git a/pkg/multicloud/huawei/disktype.go b/pkg/multicloud/huawei/disktype.go new file mode 100644 index 0000000000..534d90987e --- /dev/null +++ b/pkg/multicloud/huawei/disktype.go @@ -0,0 +1,26 @@ +package huawei + +import "strings" + +type SDiskType struct { + ExtraSpecs ExtraSpecs `json:"extra_specs"` + Name string `json:"name"` + QosSpecsID string `json:"qos_specs_id"` + ID string `json:"id"` + IsPublic bool `json:"is_public"` +} + +type ExtraSpecs struct { + VolumeBackendName string `json:"volume_backend_name"` + AvailabilityZone string `json:"availability-zone"` + RESKEYAvailabilityZones string `json:"RESKEY:availability_zones"` + OSVendorExtendedSoldOutAvailabilityZones string `json:"os-vendor-extended:sold_out_availability_zones"` +} + +func (self *SDiskType) IsAvaliableInZone(zoneId string) bool { + if len(self.QosSpecsID) > 0 && strings.Contains(zoneId, self.ExtraSpecs.RESKEYAvailabilityZones) && !strings.Contains(zoneId, self.ExtraSpecs.OSVendorExtendedSoldOutAvailabilityZones) { + return true + } + + return false +} diff --git a/pkg/multicloud/huawei/huawei.go b/pkg/multicloud/huawei/huawei.go index 825c379319..9fd30e657e 100644 --- a/pkg/multicloud/huawei/huawei.go +++ b/pkg/multicloud/huawei/huawei.go @@ -496,6 +496,7 @@ func (self *SHuaweiClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_CACHE, cloudprovider.CLOUD_CAPABILITY_EVENT, cloudprovider.CLOUD_CAPABILITY_CLOUDID, + cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, } // huawei objectstore is shared across projects(subscriptions) // to avoid multiple project access the same bucket diff --git a/pkg/multicloud/huawei/instance.go b/pkg/multicloud/huawei/instance.go index 8183f0fe6c..0a00736aae 100644 --- a/pkg/multicloud/huawei/instance.go +++ b/pkg/multicloud/huawei/instance.go @@ -470,7 +470,7 @@ func (self *SInstance) StartVM(ctx context.Context) error { return cloudprovider.ErrTimeout } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { if self.Status == InstanceStatusStopped { return nil } @@ -480,7 +480,7 @@ func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { return nil } - err := self.host.zone.region.StopVM(self.GetId(), isForce) + err := self.host.zone.region.StopVM(self.GetId(), opts.IsForce) if err != nil { return err } diff --git a/pkg/multicloud/huawei/region.go b/pkg/multicloud/huawei/region.go index 713bcce40f..a32f203991 100644 --- a/pkg/multicloud/huawei/region.go +++ b/pkg/multicloud/huawei/region.go @@ -1007,3 +1007,35 @@ func (self *SRegion) GetIElasticcaches() ([]cloudprovider.ICloudElasticcache, er func (region *SRegion) GetCapabilities() []string { return region.client.GetCapabilities() } + +func (self *SRegion) GetDiskTypes() ([]SDiskType, error) { + ret, err := self.ecsClient.Disks.GetDiskTypes() + if err != nil { + return nil, errors.Wrap(err, "GetDiskTypes") + } + + dts := []SDiskType{} + _ret := jsonutils.NewArray(ret.Data...) + err = _ret.Unmarshal(&dts) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return dts, nil +} + +func (self *SRegion) GetZoneSupportedDiskTypes(zoneId string) ([]string, error) { + dts, err := self.GetDiskTypes() + if err != nil { + return nil, errors.Wrap(err, "GetDiskTypes") + } + + ret := []string{} + for i := range dts { + if dts[i].IsAvaliableInZone(zoneId) { + ret = append(ret, dts[i].Name) + } + } + + return ret, nil +} diff --git a/pkg/multicloud/huawei/routetable.go b/pkg/multicloud/huawei/routetable.go index ae65161242..5e160f2c1a 100644 --- a/pkg/multicloud/huawei/routetable.go +++ b/pkg/multicloud/huawei/routetable.go @@ -61,7 +61,7 @@ func (route *SRouteEntry) GetGlobalId() string { } func (route *SRouteEntry) GetStatus() string { - return "" + return api.ROUTE_ENTRY_STATUS_AVAILIABLE } func (route *SRouteEntry) Refresh() error { @@ -136,7 +136,7 @@ func (self *SRouteTable) GetGlobalId() string { } func (self *SRouteTable) GetStatus() string { - return "" + return api.ROUTE_TABLE_AVAILABLE } func (self *SRouteTable) Refresh() error { diff --git a/pkg/multicloud/huawei/shell/disk.go b/pkg/multicloud/huawei/shell/disk.go index fa10a563e8..f6ead5656d 100644 --- a/pkg/multicloud/huawei/shell/disk.go +++ b/pkg/multicloud/huawei/shell/disk.go @@ -42,4 +42,13 @@ func init() { } return nil }) + + shellutils.R(&DiskListOptions{}, "disk-types", "List disk types", func(cli *huawei.SRegion, args *DiskListOptions) error { + ret, e := cli.GetDiskTypes() + if e != nil { + return e + } + printList(ret, 0, 0, 0, nil) + return nil + }) } diff --git a/pkg/multicloud/huawei/zone.go b/pkg/multicloud/huawei/zone.go index bb4e739393..50f0d0c412 100644 --- a/pkg/multicloud/huawei/zone.go +++ b/pkg/multicloud/huawei/zone.go @@ -18,6 +18,7 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -58,7 +59,12 @@ func (self *SZone) addWire(wire *SWire) { func (self *SZone) getStorageType() { if len(self.storageTypes) == 0 { - self.storageTypes = StorageTypes + if sts, err := self.region.GetZoneSupportedDiskTypes(self.GetId()); err == nil { + self.storageTypes = sts + } else { + log.Errorf("GetZoneSupportedDiskTypes %s %s", self.GetId(), err) + self.storageTypes = StorageTypes + } } } diff --git a/pkg/multicloud/loader/loader.go b/pkg/multicloud/loader/loader.go index 600ccafc56..9f8146a1c9 100644 --- a/pkg/multicloud/loader/loader.go +++ b/pkg/multicloud/loader/loader.go @@ -18,6 +18,7 @@ import ( "yunion.io/x/log" // on-premise virtualization technologies _ "yunion.io/x/onecloud/pkg/multicloud/aliyun/provider" + _ "yunion.io/x/onecloud/pkg/multicloud/apsara/provider" // aliyun apsara stack _ "yunion.io/x/onecloud/pkg/multicloud/aws/provider" _ "yunion.io/x/onecloud/pkg/multicloud/azure/provider" _ "yunion.io/x/onecloud/pkg/multicloud/ctyun/provider" diff --git a/pkg/multicloud/objectstore/shell.go b/pkg/multicloud/objectstore/shell.go index f77e7bcc0f..fd1c77313a 100644 --- a/pkg/multicloud/objectstore/shell.go +++ b/pkg/multicloud/objectstore/shell.go @@ -451,6 +451,7 @@ func S3Shell() { AllowedHeaders []string MaxAgeSeconds int ExposeHeaders []string + Id string } shellutils.R(&BucketSetCorsOption{}, "bucket-set-cors", "Set bucket cors", func(cli cloudprovider.ICloudRegion, args *BucketSetCorsOption) error { bucket, err := cli.GetIBucketById(args.BUCKET) @@ -463,6 +464,7 @@ func S3Shell() { AllowedHeaders: args.AllowedHeaders, MaxAgeSeconds: args.MaxAgeSeconds, ExposeHeaders: args.ExposeHeaders, + Id: args.Id, } err = bucket.SetCORS([]cloudprovider.SBucketCORSRule{rule}) if err != nil { @@ -489,14 +491,15 @@ func S3Shell() { }) type BucketDeleteCorsOption struct { - BUCKET string `help:"name of bucket to put object"` + BUCKET string `help:"name of bucket to put object"` + Ids []string `help:"rule ids to delete"` } - shellutils.R(&BucketGetWebsiteConfOption{}, "bucket-delete-cors", "Delete bucket cors", func(cli cloudprovider.ICloudRegion, args *BucketGetWebsiteConfOption) error { + shellutils.R(&BucketDeleteCorsOption{}, "bucket-delete-cors", "Delete bucket cors", func(cli cloudprovider.ICloudRegion, args *BucketDeleteCorsOption) error { bucket, err := cli.GetIBucketById(args.BUCKET) if err != nil { return err } - err = bucket.DeleteCORS() + err = bucket.DeleteCORS(args.Ids) if err != nil { return err } diff --git a/pkg/multicloud/openstack/instance.go b/pkg/multicloud/openstack/instance.go index e33a784dae..db207288b5 100644 --- a/pkg/multicloud/openstack/instance.go +++ b/pkg/multicloud/openstack/instance.go @@ -406,8 +406,8 @@ func (instance *SInstance) StartVM(ctx context.Context) error { return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 10*time.Second, 8*time.Minute) } -func (instance *SInstance) StopVM(ctx context.Context, isForce bool) error { - err := instance.host.zone.region.StopVM(instance.Id, isForce) +func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := instance.host.zone.region.StopVM(instance.Id, opts.IsForce) if err != nil { return errors.Wrapf(err, "StopVM(%s)", instance.Id) } diff --git a/pkg/multicloud/qcloud/bucket.go b/pkg/multicloud/qcloud/bucket.go index 5c55c98207..a996af869e 100644 --- a/pkg/multicloud/qcloud/bucket.go +++ b/pkg/multicloud/qcloud/bucket.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "net/http" + "strconv" "strings" "time" @@ -642,9 +643,40 @@ func (b *SBucket) SetCORS(rules []cloudprovider.SBucketCORSRule) error { AllowedHeaders: rules[i].AllowedHeaders, MaxAgeSeconds: rules[i].MaxAgeSeconds, ExposeHeaders: rules[i].ExposeHeaders, + ID: rules[i].Id, }) } - _, err = coscli.Bucket.PutCORS(context.Background(), &opts) + + newSet := []cos.BucketCORSRule{} + updateSet := map[int]cos.BucketCORSRule{} + + oldConf, _, err := coscli.Bucket.GetCORS(context.Background()) + if err != nil { + if !strings.Contains(err.Error(), "NoSuchCORSConfiguration") { + return errors.Wrap(err, "b.region.GetCORS") + } + } + + for i := range opts.Rules { + index, err := strconv.Atoi(opts.Rules[i].ID) + if err == nil && index < len(oldConf.Rules) { + updateSet[index] = opts.Rules[i] + } else { + newSet = append(newSet, opts.Rules[i]) + } + } + updatedOpts := cos.BucketPutCORSOptions{} + for i := range oldConf.Rules { + if _, ok := updateSet[i]; !ok { + updatedOpts.Rules = append(updatedOpts.Rules, oldConf.Rules[i]) + } else { + updatedOpts.Rules = append(updatedOpts.Rules, updateSet[i]) + } + } + + updatedOpts.Rules = append(updatedOpts.Rules, newSet...) + + _, err = coscli.Bucket.PutCORS(context.Background(), &updatedOpts) if err != nil { return errors.Wrap(err, "coscli.Bucket.PutCORS") } @@ -657,6 +689,12 @@ func (b *SBucket) GetCORSRules() ([]cloudprovider.SBucketCORSRule, error) { return nil, errors.Wrap(err, "b.region.GetCosClient") } conf, _, err := coscli.Bucket.GetCORS(context.Background()) + if err != nil { + if strings.Contains(err.Error(), "NoSuchCORSConfiguration") { + return nil, nil + } + return nil, errors.Wrap(err, "b.region.GetCORS") + } result := []cloudprovider.SBucketCORSRule{} for i := range conf.Rules { result = append(result, cloudprovider.SBucketCORSRule{ @@ -665,19 +703,55 @@ func (b *SBucket) GetCORSRules() ([]cloudprovider.SBucketCORSRule, error) { AllowedHeaders: conf.Rules[i].AllowedHeaders, MaxAgeSeconds: conf.Rules[i].MaxAgeSeconds, ExposeHeaders: conf.Rules[i].ExposeHeaders, + Id: strconv.Itoa(i), }) } return result, nil } -func (b *SBucket) DeleteCORS() error { +func (b *SBucket) DeleteCORS(id []string) error { coscli, err := b.region.GetCosClient(b) if err != nil { return errors.Wrap(err, "b.region.GetCosClient") } - _, err = coscli.Bucket.DeleteCORS(context.Background()) - if err != nil { - return errors.Wrap(err, "coscli.Bucket.DeleteCORS") + + existedRules := []cos.BucketCORSRule{} + if len(id) > 0 { + conf, _, err := coscli.Bucket.GetCORS(context.Background()) + if err != nil { + if strings.Contains(err.Error(), "NoSuchCORSConfiguration") { + return nil + } + return errors.Wrap(err, "b.region.GetCORS") + } + existedRules = conf.Rules + } + + excludeMap := map[int]bool{} + for i := range id { + index, err := strconv.Atoi(id[i]) + if err == nil { + excludeMap[index] = true + } + } + newRules := []cos.BucketCORSRule{} + for i := range existedRules { + if _, ok := excludeMap[i]; !ok { + newRules = append(newRules, existedRules[i]) + } + } + if len(newRules) < len(existedRules) { + if len(newRules) == 0 { + _, err = coscli.Bucket.DeleteCORS(context.Background()) + if err != nil { + return errors.Wrap(err, "coscli.Bucket.DeleteCORS") + } + return nil + } + _, err = coscli.Bucket.PutCORS(context.Background(), &cos.BucketPutCORSOptions{Rules: newRules}) + if err != nil { + return errors.Wrap(err, "coscli.Bucket.PutCORS") + } } return nil } @@ -715,6 +789,9 @@ func (b *SBucket) GetReferer() (cloudprovider.SBucketRefererConf, error) { return result, errors.Wrap(err, "b.region.GetCosClient") } referResult, _, err := coscli.Bucket.GetReferer(context.Background()) + if err != nil { + return result, errors.Wrap(err, " coscli.Bucket.GetReferer") + } result.Enabled = true result.Type = "White-List" @@ -736,11 +813,25 @@ func (b *SBucket) GetReferer() (cloudprovider.SBucketRefererConf, error) { func toAPICdnArea(area string) string { switch area { case "mainland": - return api.CDN_AREA_MAINLAND + return api.CDN_DOMAIN_AREA_MAINLAND case "overseas": - return api.CDN_AREA_OVERSEAS + return api.CDN_DOMAIN_AREA_OVERSEAS case "global": - return api.CDN_AREA_GLOBAL + return api.CDN_DOMAIN_AREA_GLOBAL + default: + return "" + } +} +func toAPICdnStatus(status string) string { + switch status { + case "online": + return api.CDN_DOMAIN_STATUS_ONLINE + case "offline": + return api.CDN_DOMAIN_STATUS_OFFLINE + case "processing": + return api.CDN_DOMAIN_STATUS_PROCESSING + case "rejected": + return api.CDN_DOMAIN_STATUS_REJECTED default: return "" } @@ -755,16 +846,15 @@ func (b *SBucket) GetCdnDomains() ([]cloudprovider.SCdnDomain, error) { if err != nil { return nil, errors.Wrapf(err, `b.region.client.DescribeAllCdnDomains(nil, []string{%s}, "cos")`, bucketHost) } - if len(bucketCdnDomains) > 1 { - return nil, cloudprovider.ErrDuplicateId - } - if len(bucketCdnDomains) == 1 { + + for i := range bucketCdnDomains { result = append(result, cloudprovider.SCdnDomain{ - Domain: bucketCdnDomains[0].Domain, - Cname: bucketCdnDomains[0].Cname, - Area: toAPICdnArea(bucketCdnDomains[0].Area), + Domain: bucketCdnDomains[i].Domain, + Status: toAPICdnStatus(bucketCdnDomains[i].Status), + Cname: bucketCdnDomains[i].Cname, + Area: toAPICdnArea(bucketCdnDomains[i].Area), Origin: bucketHost, - OriginType: api.CDN_ORIGIN_TYPE_BUCKET, + OriginType: api.CDN_DOMAIN_ORIGIN_TYPE_BUCKET, }) } @@ -772,17 +862,15 @@ func (b *SBucket) GetCdnDomains() ([]cloudprovider.SCdnDomain, error) { if err != nil { return nil, errors.Wrapf(err, `b.region.client.DescribeAllCdnDomains(nil, []string{%s}, "cos")`, bucketWebsiteHost) } - if len(bucketWebsiteCdnDomains) > 1 { - return nil, cloudprovider.ErrDuplicateId - } - if len(bucketWebsiteCdnDomains) == 1 { + for i := range bucketWebsiteCdnDomains { result = append(result, cloudprovider.SCdnDomain{ - Domain: bucketWebsiteCdnDomains[0].Domain, - Cname: bucketWebsiteCdnDomains[0].Cname, - Area: toAPICdnArea(bucketWebsiteCdnDomains[0].Area), + Domain: bucketWebsiteCdnDomains[i].Domain, + Status: toAPICdnStatus(bucketWebsiteCdnDomains[i].Status), + Cname: bucketWebsiteCdnDomains[i].Cname, + Area: toAPICdnArea(bucketWebsiteCdnDomains[i].Area), Origin: bucketWebsiteHost, - OriginType: api.CDN_ORIGIN_TYPE_BUCKET, + OriginType: api.CDN_DOMAIN_ORIGIN_TYPE_BUCKET, }) } return result, nil diff --git a/pkg/multicloud/qcloud/cloud_connect_network.go b/pkg/multicloud/qcloud/cloud_connect_network.go new file mode 100644 index 0000000000..07f030f32f --- /dev/null +++ b/pkg/multicloud/qcloud/cloud_connect_network.go @@ -0,0 +1,399 @@ +// 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 qcloud + +import ( + "fmt" + "strconv" + + "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 SCcn struct { + multicloud.SResourceBase + region *SRegion + + CcnID string `json:"CcnId"` + CcnName string `json:"CcnName"` + CcnDescription string `json:"CcnDescription"` + InstanceCount int `json:"InstanceCount"` + CreateTime string `json:"CreateTime"` + State string `json:"State"` + AttachedInstances []SCcnInstance +} + +type SCcnInstance struct { + CcnID string `json:"CcnId"` + InstanceType string `json:"InstanceType"` + InstanceID string `json:"InstanceId"` + InstanceName string `json:"InstanceName"` + InstanceRegion string `json:"InstanceRegion"` + InstanceUin string `json:"InstanceUin"` + CidrBlock []string `json:"CidrBlock"` + State string `json:"State"` +} + +type SCcnAttachInstanceInput struct { + InstanceType string + InstanceId string + InstanceRegion string +} + +func (self *SRegion) DescribeCcns(ccnIds []string, offset int, limit int) ([]SCcn, int, error) { + params := map[string]string{} + params["Offset"] = strconv.Itoa(offset) + params["Limit"] = strconv.Itoa(limit) + if ccnIds != nil && len(ccnIds) > 0 { + for index, ccnId := range ccnIds { + params[fmt.Sprintf("CcnIds.%d", index)] = ccnId + } + } + resp, err := self.vpcRequest("DescribeCcns", params) + if err != nil { + return nil, 0, errors.Wrapf(err, `self.vpcRequest("DescribeCcns", %s)`, jsonutils.Marshal(params).String()) + } + ccns := []SCcn{} + err = resp.Unmarshal(&ccns, "CcnSet") + if err != nil { + return nil, 0, errors.Wrapf(err, `(%s).Unmarshal(&ccns,"CcnSet")`, jsonutils.Marshal(resp).String()) + } + total, _ := resp.Float("TotalCount") + return ccns, int(total), nil +} + +func (self *SRegion) GetAllCcns() ([]SCcn, error) { + ccns := []SCcn{} + for { + part, total, err := self.DescribeCcns(nil, len(ccns), 50) + if err != nil { + return nil, errors.Wrapf(err, "self.DescribeCcns(nil, %d, 50)", len(ccns)) + } + ccns = append(ccns, part...) + if len(ccns) >= total { + break + } + } + for i := range ccns { + ccns[i].region = self + } + return ccns, nil +} + +func (self *SRegion) GetCcnById(ccnId string) (*SCcn, error) { + ccns, _, err := self.DescribeCcns([]string{ccnId}, 0, 50) + if err != nil { + return nil, errors.Wrapf(err, "self.DescribeCcns(%s, 0, 50)", ccnId) + } + if len(ccns) < 1 { + return nil, errors.Wrap(cloudprovider.ErrNotFound, "DescribeCcns") + } + if len(ccns) > 1 { + return nil, errors.Wrap(cloudprovider.ErrDuplicateId, "DescribeCcns") + } + ccns[0].region = self + return &ccns[0], nil +} + +func (self *SRegion) DescribeCcnAttachedInstances(ccnId string, offset int, limit int) ([]SCcnInstance, int, error) { + params := map[string]string{} + params["Offset"] = strconv.Itoa(offset) + params["Limit"] = strconv.Itoa(limit) + params["Filters.0.Name"] = "ccn-id" + params["Filters.0.Values.0"] = ccnId + resp, err := self.vpcRequest("DescribeCcnAttachedInstances", params) + if err != nil { + return nil, 0, errors.Wrapf(err, `self.vpcRequest("DescribeCcnAttachedInstances", %s)`, jsonutils.Marshal(params).String()) + } + instances := []SCcnInstance{} + err = resp.Unmarshal(&instances, "InstanceSet") + if err != nil { + return nil, 0, errors.Wrapf(err, `(%s).Unmarshal(&instances, "InstanceSet")`, jsonutils.Marshal(resp).String()) + } + total, _ := resp.Float("TotalCount") + return instances, int(total), nil +} + +func (self *SRegion) GetAllCcnAttachedInstances(ccnId string) ([]SCcnInstance, error) { + ccnInstances := []SCcnInstance{} + for { + part, total, err := self.DescribeCcnAttachedInstances(ccnId, len(ccnInstances), 50) + if err != nil { + return nil, errors.Wrapf(err, "self.DescribeCcns(nil, %d, 50)", len(ccnInstances)) + } + ccnInstances = append(ccnInstances, part...) + if len(ccnInstances) >= total { + break + } + } + return ccnInstances, nil +} + +func (self *SRegion) CreateCcn(opts *cloudprovider.SInterVpcNetworkCreateOptions) (string, error) { + params := make(map[string]string) + params["CcnName"] = opts.Name + params["CcnDescription"] = opts.Desc + // 默认的后付费不能被支持 + params["InstanceChargeType"] = "PREPAID" + // 预付费仅支持地域间限速 + params["BandwidthLimitType"] = "INTER_REGION_LIMIT" + resp, err := self.vpcRequest("CreateCcn", params) + if err != nil { + return "", errors.Wrapf(err, `self.vpcRequest("CreateCcn", %s)`, jsonutils.Marshal(params).String()) + } + ccn := &SCcn{} + err = resp.Unmarshal(ccn, "Ccn") + if err != nil { + return "", errors.Wrapf(err, `(%s).Unmarshal(ccn, "Ccn")`, jsonutils.Marshal(resp).String()) + } + return ccn.CcnID, nil +} + +func (self *SRegion) AcceptAttachCcnInstances(ccnId string, instances []SCcnAttachInstanceInput) error { + params := make(map[string]string) + params["CcnId"] = ccnId + for i := range instances { + params[fmt.Sprintf("Instances.%d.InstanceType", i)] = instances[i].InstanceType + params[fmt.Sprintf("Instances.%d.InstanceId", i)] = instances[i].InstanceId + params[fmt.Sprintf("Instances.%d.InstanceRegion", i)] = instances[i].InstanceRegion + } + _, err := self.vpcRequest("AcceptAttachCcnInstances", params) + if err != nil { + return errors.Wrapf(err, `self.vpcRequest("AcceptAttachCcnInstances", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) DeleteCcn(ccnId string) error { + params := make(map[string]string) + params["CcnId"] = ccnId + _, err := self.vpcRequest("DeleteCcn", params) + if err != nil { + return errors.Wrapf(err, ` self.vpcRequest("DeleteCcn", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) AttachCcnInstances(ccnId string, ccnUin string, instances []SCcnAttachInstanceInput) error { + params := make(map[string]string) + params["CcnId"] = ccnId + if len(ccnUin) > 0 { + params["CcnUin"] = ccnUin + } + for i := range instances { + params[fmt.Sprintf("Instances.%d.InstanceType", i)] = instances[i].InstanceType + params[fmt.Sprintf("Instances.%d.InstanceId", i)] = instances[i].InstanceId + params[fmt.Sprintf("Instances.%d.InstanceRegion", i)] = instances[i].InstanceRegion + } + + _, err := self.vpcRequest("AttachCcnInstances", params) + if err != nil { + return errors.Wrapf(err, ` self.vpcRequest("AttachCcnInstances", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) DetachCcnInstances(ccnId string, instances []SCcnAttachInstanceInput) error { + params := make(map[string]string) + params["CcnId"] = ccnId + + for i := range instances { + params[fmt.Sprintf("Instances.%d.InstanceType", i)] = instances[i].InstanceType + params[fmt.Sprintf("Instances.%d.InstanceId", i)] = instances[i].InstanceId + params[fmt.Sprintf("Instances.%d.InstanceRegion", i)] = instances[i].InstanceRegion + } + + _, err := self.vpcRequest("DetachCcnInstances", params) + if err != nil { + return errors.Wrapf(err, ` self.vpcRequest("DetachCcnInstances", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SCcn) GetId() string { + return self.CcnID +} + +func (self *SCcn) GetName() string { + return self.CcnName +} + +func (self *SCcn) GetGlobalId() string { + return self.GetId() +} + +func (self *SCcn) GetStatus() string { + switch self.State { + case "AVAILABLE": + return api.INTER_VPC_NETWORK_STATUS_AVAILABLE + case "ISOLATED": + return api.INTER_VPC_NETWORK_STATUS_UNKNOWN + default: + return api.INTER_VPC_NETWORK_STATUS_UNKNOWN + } +} + +func (self *SCcn) Refresh() error { + ccn, err := self.region.GetCcnById(self.GetId()) + if err != nil { + return errors.Wrapf(err, "self.region.GetCcnById(%s)", self.GetId()) + } + return jsonutils.Update(self, ccn) +} + +func (self *SCcn) GetAuthorityOwnerId() string { + return self.region.client.ownerName +} + +func (self *SCcn) fetchAttachedInstances() error { + if self.AttachedInstances != nil { + return nil + } + + instances, err := self.region.GetAllCcnAttachedInstances(self.GetId()) + if err != nil { + return errors.Wrapf(err, "self.region.GetAllCcnAttachedInstances(%s)", self.GetId()) + } + self.AttachedInstances = instances + return nil +} + +func (self *SCcn) GetICloudVpcIds() ([]string, error) { + err := self.fetchAttachedInstances() + if err != nil { + return nil, errors.Wrap(err, "self.fetchAttachedInstances()") + } + result := []string{} + for i := range self.AttachedInstances { + if self.AttachedInstances[i].InstanceType == "VPC" { + result = append(result, self.AttachedInstances[i].InstanceID) + } + } + return result, nil +} + +func (self *SCcn) AttachVpc(opts *cloudprovider.SInterVpcNetworkAttachVpcOption) error { + if self.GetAuthorityOwnerId() == opts.VpcAuthorityOwnerId { + return nil + } + instance := SCcnAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: opts.VpcId, + InstanceRegion: opts.VpcRegionId, + } + err := self.region.AcceptAttachCcnInstances(self.GetId(), []SCcnAttachInstanceInput{instance}) + if err != nil { + return errors.Wrapf(err, "self.region.AcceptAttachCcnInstance(%s,%s)", self.GetId(), jsonutils.Marshal(opts).String()) + } + return nil +} + +func (self *SCcn) DetachVpc(opts *cloudprovider.SInterVpcNetworkDetachVpcOption) error { + instance := SCcnAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: opts.VpcId, + InstanceRegion: opts.VpcRegionId, + } + err := self.region.DetachCcnInstances(self.GetId(), []SCcnAttachInstanceInput{instance}) + if err != nil { + return errors.Wrapf(err, "self.client.DetachCcnInstance(%s,%s)", self.GetId(), jsonutils.Marshal(opts).String()) + } + return nil +} + +func (self *SCcn) Delete() error { + err := self.region.DeleteCcn(self.GetId()) + if err != nil { + return errors.Wrapf(err, "self.region.DeleteCcn(%s)", self.GetId()) + } + return nil +} + +func (self *SCcn) GetIRoutes() ([]cloudprovider.ICloudInterVpcNetworkRoute, error) { + routes, err := self.region.GetAllCcnRouteSets(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, " self.region.GetAllCcnRouteSets(self.GetId())") + } + result := []cloudprovider.ICloudInterVpcNetworkRoute{} + for i := range routes { + result = append(result, &routes[i]) + } + return result, nil +} + +func (self *SCcn) EnableRouteEntry(routeId string) error { + err := self.region.EnableCcnRoutes(self.GetId(), []string{routeId}) + if err != nil { + return errors.Wrap(err, "self.region.EnableCcnRoutes") + } + return nil +} + +func (self *SCcn) DisableRouteEntry(routeId string) error { + err := self.region.DisableCcnRoutes(self.GetId(), []string{routeId}) + if err != nil { + return errors.Wrap(err, "self.region.DisableCcnRoutes") + } + return nil +} + +func (client *SQcloudClient) GetICloudInterVpcNetworks() ([]cloudprovider.ICloudInterVpcNetwork, error) { + region, err := client.getDefaultRegion() + if err != nil { + return nil, errors.Wrap(err, "getDefaultRegion") + } + sccns, err := region.GetAllCcns() + if err != nil { + return nil, errors.Wrap(err, " region.GetAllCcns()") + } + result := []cloudprovider.ICloudInterVpcNetwork{} + for i := range sccns { + result = append(result, &sccns[i]) + } + return result, nil +} + +func (client *SQcloudClient) GetICloudInterVpcNetworkById(id string) (cloudprovider.ICloudInterVpcNetwork, error) { + region, err := client.getDefaultRegion() + if err != nil { + return nil, errors.Wrap(err, "getDefaultRegion") + } + ccn, err := region.GetCcnById(id) + if err != nil { + return nil, errors.Wrapf(err, "region.GetCcnById(%s)", id) + } + return ccn, nil +} + +func (client *SQcloudClient) CreateICloudInterVpcNetwork(opts *cloudprovider.SInterVpcNetworkCreateOptions) (cloudprovider.ICloudInterVpcNetwork, error) { + region, err := client.getDefaultRegion() + if err != nil { + return nil, errors.Wrap(err, "getDefaultRegion") + } + + ccnId, err := region.CreateCcn(opts) + if err != nil { + return nil, errors.Wrapf(err, "region.CreateCcn(%s)", jsonutils.Marshal(opts).String()) + } + iNetwork, err := client.GetICloudInterVpcNetworkById(ccnId) + if err != nil { + return nil, errors.Wrapf(err, "client.GetICloudInterVpcNetworkById(%s)", ccnId) + } + return iNetwork, nil +} diff --git a/pkg/multicloud/qcloud/cloud_connect_network_route.go b/pkg/multicloud/qcloud/cloud_connect_network_route.go new file mode 100644 index 0000000000..e7558c751d --- /dev/null +++ b/pkg/multicloud/qcloud/cloud_connect_network_route.go @@ -0,0 +1,164 @@ +// 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 qcloud + +import ( + "fmt" + "strconv" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SCcnRouteSet struct { + RouteID string `json:"RouteId"` + DestinationCidrBlock string `json:"DestinationCidrBlock"` + InstanceType string `json:"InstanceType"` + InstanceID string `json:"InstanceId"` + InstanceName string `json:"InstanceName"` + InstanceRegion string `json:"InstanceRegion"` + InstanceUin string `json:"InstanceUin"` + UpdateTime string `json:"UpdateTime"` + Enabled bool `json:"Enabled"` + ExtraState string `json:"ExtraState"` +} +type SCcnRouteSets struct { + RouteSet []SCcnRouteSet `json:"RouteSet"` + TotalCount int `json:"TotalCount"` + RequestID string `json:"RequestId"` +} + +func (self *SRegion) DescribeCcnRoutes(ccnId string, offset int, limit int) ([]SCcnRouteSet, int, error) { + params := map[string]string{} + params["Offset"] = strconv.Itoa(offset) + params["Limit"] = strconv.Itoa(limit) + params["CcnId"] = ccnId + resp, err := self.vpcRequest("DescribeCcnRoutes", params) + if err != nil { + return nil, 0, errors.Wrapf(err, `self.vpcRequest("DescribeCcnRoutes", %s)`, jsonutils.Marshal(params).String()) + } + routes := []SCcnRouteSet{} + err = resp.Unmarshal(&routes, "RouteSet") + if err != nil { + return nil, 0, errors.Wrapf(err, `(%s).Unmarshal(&routes,"RouteSet")`, jsonutils.Marshal(resp).String()) + } + total, _ := resp.Float("TotalCount") + return routes, int(total), nil +} + +func (self *SRegion) GetAllCcnRouteSets(ccnId string) ([]SCcnRouteSet, error) { + routes := []SCcnRouteSet{} + for { + part, total, err := self.DescribeCcnRoutes(ccnId, len(routes), 50) + if err != nil { + return nil, errors.Wrapf(err, "self.DescribeCcns(nil, %d, 50)", len(routes)) + } + routes = append(routes, part...) + if len(routes) >= total { + break + } + } + return routes, nil +} + +func (self *SRegion) EnableCcnRoutes(ccnId string, routeIds []string) error { + params := map[string]string{} + params["CcnId"] = ccnId + for i := range routeIds { + params[fmt.Sprintf("RouteIds.%d", i)] = routeIds[i] + } + _, err := self.vpcRequest("EnableCcnRoutes", params) + if err != nil { + return errors.Wrapf(err, `self.vpcRequest("EnableCcnRoutes", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) DisableCcnRoutes(ccnId string, routeIds []string) error { + params := map[string]string{} + params["CcnId"] = ccnId + for i := range routeIds { + params[fmt.Sprintf("RouteIds.%d", i)] = routeIds[i] + } + _, err := self.vpcRequest("DisableCcnRoutes", params) + if err != nil { + return errors.Wrapf(err, `self.vpcRequest("DisableCcnRoutes", %s)`, jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SCcnRouteSet) GetId() string { + return self.RouteID +} + +func (self *SCcnRouteSet) GetName() string { + return "" +} + +func (self *SCcnRouteSet) GetGlobalId() string { + return self.RouteID +} + +func (self *SCcnRouteSet) GetStatus() string { + switch self.ExtraState { + case "Disable": + return api.ROUTE_ENTRY_STATUS_DISABLED + case "Running": + return api.ROUTE_ENTRY_STATUS_AVAILIABLE + default: + return api.ROUTE_ENTRY_STATUS_UNKNOWN + } +} + +func (self *SCcnRouteSet) Refresh() error { + return nil +} + +func (self *SCcnRouteSet) IsEmulated() bool { + return false +} + +func (self *SCcnRouteSet) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SCcnRouteSet) GetInstanceId() string { + return self.InstanceID +} + +func (self *SCcnRouteSet) GetInstanceType() string { + switch self.InstanceType { + case "VPC": + return api.Next_HOP_TYPE_VPC + case "DIRECTCONNECT": + return api.Next_HOP_TYPE_VBR + default: + return "" + } +} + +func (self *SCcnRouteSet) GetInstanceRegionId() string { + return self.InstanceRegion +} + +func (self *SCcnRouteSet) GetEnabled() bool { + return self.Enabled +} + +func (self *SCcnRouteSet) GetCidr() string { + return self.DestinationCidrBlock +} diff --git a/pkg/multicloud/qcloud/instance.go b/pkg/multicloud/qcloud/instance.go index 46eacc70ab..e88af28717 100644 --- a/pkg/multicloud/qcloud/instance.go +++ b/pkg/multicloud/qcloud/instance.go @@ -417,8 +417,8 @@ func (self *SInstance) StartVM(ctx context.Context) error { return cloudprovider.ErrTimeout } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { - err := self.host.zone.region.StopVM(self.InstanceId, isForce) +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := self.host.zone.region.StopVM(self.InstanceId, opts) if err != nil { return err } @@ -467,7 +467,10 @@ func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SMan if err != nil { return "", err } - self.StopVM(ctx, true) + opts := &cloudprovider.ServerStopOptions{ + IsForce: true, + } + self.StopVM(ctx, opts) instance, err := self.host.zone.region.GetInstance(self.InstanceId) if err != nil { return "", err @@ -638,15 +641,18 @@ func (self *SRegion) doStartVM(instanceId string) error { return self.instanceOperation(instanceId, "StartInstances", nil, true) } -func (self *SRegion) doStopVM(instanceId string, isForce bool) error { +func (self *SRegion) doStopVM(instanceId string, opts *cloudprovider.ServerStopOptions) error { params := make(map[string]string) - if isForce { + if opts.IsForce { // params["ForceStop"] = "FALSE" params["StopType"] = "HARD" } else { // params["ForceStop"] = "FALSE" params["StopType"] = "SOFT" } + if opts.StopCharging { + params["StoppedMode"] = "STOP_CHARGING" + } return self.instanceOperation(instanceId, "StopInstances", params, true) } @@ -672,7 +678,7 @@ func (self *SRegion) StartVM(instanceId string) error { return self.doStartVM(instanceId) } -func (self *SRegion) StopVM(instanceId string, isForce bool) error { +func (self *SRegion) StopVM(instanceId string, opts *cloudprovider.ServerStopOptions) error { status, err := self.GetInstanceStatus(instanceId) if err != nil { log.Errorf("Fail to get instance status on StopVM: %s", err) @@ -681,7 +687,7 @@ func (self *SRegion) StopVM(instanceId string, isForce bool) error { if status == InstanceStatusStopped { return nil } - return self.doStopVM(instanceId, isForce) + return self.doStopVM(instanceId, opts) } func (self *SRegion) DeleteVM(instanceId string) error { @@ -690,8 +696,7 @@ func (self *SRegion) DeleteVM(instanceId string) error { if errors.Cause(err) == cloudprovider.ErrNotFound { return nil } - log.Errorf("Fail to get instance status on DeleteVM: %s", err) - return err + return errors.Wrapf(err, "self.GetInstanceStatus") } log.Debugf("Instance status on delete is %s", status) if status != InstanceStatusStopped { @@ -742,10 +747,11 @@ func (self *SRegion) DeployVM(instanceId string, name string, password string, k } func (self *SInstance) DeleteVM(ctx context.Context) error { - if err := self.host.zone.region.DeleteVM(self.InstanceId); err != nil { - return err + err := self.host.zone.region.DeleteVM(self.InstanceId) + if err != nil { + return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId) } - return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes + return cloudprovider.WaitDeleted(self, 10*time.Second, 10*time.Minute) // 5minutes } func (self *SRegion) UpdateVM(instanceId string, name, osType string) error { @@ -875,6 +881,7 @@ func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { eip.AddressName = address eip.AddressType = EIP_TYPE_WANIP eip.AddressStatus = EIP_STATUS_BIND + eip.Bandwidth = self.InternetAccessible.InternetMaxBandwidthOut return &eip, nil } return nil, nil diff --git a/pkg/multicloud/qcloud/provider/provider.go b/pkg/multicloud/qcloud/provider/provider.go index 1a94641255..b5bf9e082f 100644 --- a/pkg/multicloud/qcloud/provider/provider.go +++ b/pkg/multicloud/qcloud/provider/provider.go @@ -425,3 +425,15 @@ func (self *SQcloudProvider) GetICloudroleByName(name string) (cloudprovider.ICl func (self *SQcloudProvider) GetICloudroleById(id string) (cloudprovider.ICloudrole, error) { return self.GetICloudroleByName(id) } + +func (self *SQcloudProvider) GetICloudInterVpcNetworks() ([]cloudprovider.ICloudInterVpcNetwork, error) { + return self.client.GetICloudInterVpcNetworks() +} + +func (self *SQcloudProvider) GetICloudInterVpcNetworkById(id string) (cloudprovider.ICloudInterVpcNetwork, error) { + return self.client.GetICloudInterVpcNetworkById(id) +} + +func (self *SQcloudProvider) CreateICloudInterVpcNetwork(opts *cloudprovider.SInterVpcNetworkCreateOptions) (cloudprovider.ICloudInterVpcNetwork, error) { + return self.client.CreateICloudInterVpcNetwork(opts) +} diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go index 288c1289f6..3e47c44054 100644 --- a/pkg/multicloud/qcloud/qcloud.go +++ b/pkg/multicloud/qcloud/qcloud.go @@ -967,6 +967,8 @@ func (self *SQcloudClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_CLOUDID, cloudprovider.CLOUD_CAPABILITY_DNSZONE, cloudprovider.CLOUD_CAPABILITY_PUBLIC_IP, + cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK, + cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, } return caps } diff --git a/pkg/multicloud/qcloud/route_table.go b/pkg/multicloud/qcloud/route_table.go index 989feaf7db..9b184d3ac3 100644 --- a/pkg/multicloud/qcloud/route_table.go +++ b/pkg/multicloud/qcloud/route_table.go @@ -107,7 +107,7 @@ func (self *SRouteTableSet) GetGlobalId() string { } func (self *SRouteTableSet) GetStatus() string { - return "" + return api.ROUTE_TABLE_AVAILABLE } func (self *SRouteTableSet) Refresh() error { diff --git a/pkg/multicloud/qcloud/shell/ccn_route.go b/pkg/multicloud/qcloud/shell/ccn_route.go new file mode 100644 index 0000000000..cfcd30db17 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/ccn_route.go @@ -0,0 +1,58 @@ +// 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/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CcnRouteListOption struct { + CCNID string + } + shellutils.R(&CcnRouteListOption{}, "ccn-route-list", "List cloud connect network route", func(cli *qcloud.SRegion, args *CcnRouteListOption) error { + routes, err := cli.GetAllCcnRouteSets(args.CCNID) + if err != nil { + return err + } + printList(routes, len(routes), 0, len(routes), []string{}) + return nil + }) + + type CcnRouteEnableOption struct { + CCNID string + ROUTEID string + } + shellutils.R(&CcnRouteEnableOption{}, "ccn-route-enable", "enable cloud connect network route", func(cli *qcloud.SRegion, args *CcnRouteEnableOption) error { + err := cli.EnableCcnRoutes(args.CCNID, []string{args.ROUTEID}) + if err != nil { + return err + } + return nil + }) + + type CcnRouteDisableOption struct { + CCNID string + ROUTEID string + } + shellutils.R(&CcnRouteDisableOption{}, "ccn-route-disable", "disable cloud connect network route", func(cli *qcloud.SRegion, args *CcnRouteDisableOption) error { + err := cli.DisableCcnRoutes(args.CCNID, []string{args.ROUTEID}) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/qcloud/shell/cloud_connect_network.go b/pkg/multicloud/qcloud/shell/cloud_connect_network.go new file mode 100644 index 0000000000..3a35db76df --- /dev/null +++ b/pkg/multicloud/qcloud/shell/cloud_connect_network.go @@ -0,0 +1,127 @@ +// 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/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CcnListOption struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&CcnListOption{}, "ccn-list", "List cloud connect network", func(cli *qcloud.SRegion, args *CcnListOption) error { + vpcs, total, err := cli.DescribeCcns(nil, args.Offset, args.Limit) + if err != nil { + return err + } + printList(vpcs, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type CcnShowOption struct { + CCNID string + } + shellutils.R(&CcnShowOption{}, "ccn-show", "show cloud connect network", func(cli *qcloud.SRegion, args *CcnShowOption) error { + ccn, err := cli.GetCcnById(args.CCNID) + if err != nil { + return err + } + printObject(ccn) + return nil + }) + + type CcnChildListOption struct { + CCNID string + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&CcnChildListOption{}, "ccn-child-list", "List cloud connect network attatched instance", func(cli *qcloud.SRegion, args *CcnChildListOption) error { + vpcs, total, err := cli.DescribeCcnAttachedInstances(args.CCNID, args.Offset, args.Limit) + if err != nil { + return err + } + printList(vpcs, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type CenCreateOptions struct { + Name string + Description string + } + shellutils.R(&CenCreateOptions{}, "ccn-create", "create cloud connect network", func(cli *qcloud.SRegion, args *CenCreateOptions) error { + opts := cloudprovider.SInterVpcNetworkCreateOptions{} + opts.Name = args.Name + opts.Desc = args.Description + ccnId, err := cli.CreateCcn(&opts) + if err != nil { + return err + } + print(ccnId) + return nil + }) + + type CenDeleteOptions struct { + ID string + } + shellutils.R(&CenDeleteOptions{}, "ccn-delete", "delete cloud connect network", func(cli *qcloud.SRegion, args *CenDeleteOptions) error { + err := cli.DeleteCcn(args.ID) + if err != nil { + return err + } + return nil + }) + + type CenAddVpcOptions struct { + ID string + OwnerId string + VpcId string + VpcRegionId string + } + shellutils.R(&CenAddVpcOptions{}, "ccn-add-vpc", "add vpc to cloud connect network attatched instance", func(cli *qcloud.SRegion, args *CenAddVpcOptions) error { + + instance := qcloud.SCcnAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: args.VpcId, + InstanceRegion: args.VpcRegionId, + } + err := cli.AttachCcnInstances(args.ID, args.OwnerId, []qcloud.SCcnAttachInstanceInput{instance}) + if err != nil { + return err + } + return nil + }) + + type CenRemoveVpcOptions struct { + ID string + VpcId string + VpcRegionId string + } + shellutils.R(&CenAddVpcOptions{}, "ccn-remove-vpc", "remove vpc to cloud connect network attatched instance", func(cli *qcloud.SRegion, args *CenAddVpcOptions) error { + instance := qcloud.SCcnAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: args.VpcId, + InstanceRegion: args.VpcRegionId, + } + err := cli.DetachCcnInstances(args.ID, []qcloud.SCcnAttachInstanceInput{instance}) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/qcloud/shell/instance.go b/pkg/multicloud/qcloud/shell/instance.go index a44fa437a8..b36d1080ac 100644 --- a/pkg/multicloud/qcloud/shell/instance.go +++ b/pkg/multicloud/qcloud/shell/instance.go @@ -18,6 +18,7 @@ import ( "fmt" "strings" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud/qcloud" "yunion.io/x/onecloud/pkg/util/shellutils" ) @@ -122,11 +123,16 @@ func init() { }) type InstanceStopOptions struct { - ID string `help:"instance ID"` - Force bool `help:"Force stop instance"` + ID string `help:"instance ID"` + Force bool `help:"Force stop instance"` + StopCharging bool `help:"Stop charging"` } shellutils.R(&InstanceStopOptions{}, "instance-stop", "Stop a instance", func(cli *qcloud.SRegion, args *InstanceStopOptions) error { - err := cli.StopVM(args.ID, args.Force) + opts := &cloudprovider.ServerStopOptions{ + IsForce: args.Force, + StopCharging: args.StopCharging, + } + err := cli.StopVM(args.ID, opts) if err != nil { return err } diff --git a/pkg/multicloud/qcloud/vpc.go b/pkg/multicloud/qcloud/vpc.go index 7600206b7e..2d98fbff25 100644 --- a/pkg/multicloud/qcloud/vpc.go +++ b/pkg/multicloud/qcloud/vpc.go @@ -408,3 +408,16 @@ func (self *SVpc) AcceptICloudVpcPeeringConnection(id string) error { func (self *SVpc) GetAuthorityOwnerId() string { return self.region.client.ownerName } + +func (self *SVpc) ProposeJoinICloudInterVpcNetwork(opts *cloudprovider.SVpcJointInterVpcNetworkOption) error { + instance := SCcnAttachInstanceInput{ + InstanceType: "VPC", + InstanceId: self.GetId(), + InstanceRegion: self.region.GetId(), + } + err := self.region.AttachCcnInstances(opts.InterVpcNetworkId, opts.NetworkAuthorityOwnerId, []SCcnAttachInstanceInput{instance}) + if err != nil { + return errors.Wrapf(err, "self.region.AttachCcnInstance(%s,%s,%s)", jsonutils.Marshal(opts).String(), self.GetId(), self.region.GetId()) + } + return nil +} diff --git a/pkg/multicloud/test/doc.go b/pkg/multicloud/test/doc.go new file mode 100644 index 0000000000..60f91195ac --- /dev/null +++ b/pkg/multicloud/test/doc.go @@ -0,0 +1 @@ +package test // import "yunion.io/x/onecloud/pkg/multicloud/test" diff --git a/pkg/multicloud/test/readonly.go b/pkg/multicloud/test/readonly.go new file mode 100644 index 0000000000..56f7c949c7 --- /dev/null +++ b/pkg/multicloud/test/readonly.go @@ -0,0 +1,255 @@ +// 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 test + +import ( + "os" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/printutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func TestShell() { + + type RegionResourceCount struct { + ZoneCount int + VpcCount int + NetworkCount int + EipCount int + VmCount int + LbCount int + LbCertCount int + DiskCount int + SnapshotCount int + SnapshotPolicyCount int + ImageCount int + NetworkInterfaceCount int + BucketCount int + RdsCount int + } + + type ReadonlyTestOptions struct { + TestVpc bool `default:"true"` + TestLb bool `default:"true"` + } + + var list = func(parent cloudprovider.ICloudResource, resource string, callback func() (interface{}, error)) interface{} { + result, err := callback() + if err != nil { + if errors.Cause(err) != cloudprovider.ErrNotImplemented && errors.Cause(err) != cloudprovider.ErrNotSupported { + return result + } + log.Errorf("list %s error: %v", resource, err) + os.Exit(-1) + } + log.Debugf("%s(%s) %s:", parent.GetName(), parent.GetGlobalId(), resource) + printutils.PrintGetterList(result, nil) + return result + } + + var show = func(parent cloudprovider.ICloudResource, resource string, callback func() (interface{}, error)) interface{} { + result, err := callback() + if err != nil { + if errors.Cause(err) != cloudprovider.ErrNotImplemented && errors.Cause(err) != cloudprovider.ErrNotSupported { + return result + } + log.Errorf("show %s(%s) %s error: %v", parent.GetName(), parent.GetGlobalId(), resource, err) + os.Exit(-1) + } + log.Debugf("%s(%s) %s:", parent.GetName(), parent.GetGlobalId(), resource) + printutils.PrintGetterObject(result) + return result + } + + shellutils.R(&ReadonlyTestOptions{}, "test-readonly", "Test read", func(cli cloudprovider.ICloudRegion, args *ReadonlyTestOptions) error { + result := RegionResourceCount{ + ZoneCount: 0, + VpcCount: 0, + NetworkCount: 0, + EipCount: 0, + VmCount: 0, + LbCount: 0, + LbCertCount: 0, + DiskCount: 0, + SnapshotCount: 0, + SnapshotPolicyCount: 0, + ImageCount: 0, + NetworkInterfaceCount: 0, + BucketCount: 0, + } + if args.TestVpc { + _vpcs := list(cli, "vpcs", func() (interface{}, error) { + return cli.GetIVpcs() + }) + vpcs := _vpcs.([]cloudprovider.ICloudVpc) + result.VpcCount += len(vpcs) + for i := range vpcs { + _wire := list(vpcs[i], "wire", func() (interface{}, error) { + return vpcs[i].GetIWires() + }) + wires := _wire.([]cloudprovider.ICloudWire) + for j := range wires { + _networks := list(wires[j], "networks", func() (interface{}, error) { + return wires[j].GetINetworks() + }) + networks := _networks.([]cloudprovider.ICloudNetwork) + result.NetworkCount += len(networks) + } + } + } + _eips := list(cli, "eips", func() (interface{}, error) { + return cli.GetIEips() + }) + eips := _eips.([]cloudprovider.ICloudEIP) + result.EipCount = len(eips) + _snapshots := list(cli, "snapshots", func() (interface{}, error) { + return cli.GetISnapshots() + }) + snapshots := _snapshots.([]cloudprovider.ICloudSnapshot) + result.SnapshotCount = len(snapshots) + _snapshotPolicies := list(cli, "snapshot policies", func() (interface{}, error) { + return cli.GetISnapshotPolicies() + }) + snapshotPolicies := _snapshotPolicies.([]cloudprovider.ICloudSnapshotPolicy) + result.SnapshotPolicyCount = len(snapshotPolicies) + _nics := list(cli, "network interfaces", func() (interface{}, error) { + return cli.GetINetworkInterfaces() + }) + nics := _nics.([]cloudprovider.ICloudNetworkInterface) + result.NetworkInterfaceCount = len(nics) + + _buckets := list(cli, "buckets", func() (interface{}, error) { + return cli.GetIBuckets() + }) + buckets := _buckets.([]cloudprovider.ICloudBucket) + result.BucketCount = len(buckets) + for i := range buckets { + list(buckets[i], "bucket objects", func() (interface{}, error) { + return buckets[i].ListObjects("", "", "", 10) + }) + } + + list(cli, "storages", func() (interface{}, error) { + return cli.GetIStorages() + }) + _caches := list(cli, "storagecaches", func() (interface{}, error) { + return cli.GetIStoragecaches() + }) + caches := _caches.([]cloudprovider.ICloudStoragecache) + for i := range caches { + _images := list(caches[i], "images", func() (interface{}, error) { + return caches[i].GetIImages() + }) + images := _images.([]cloudprovider.ICloudImage) + result.ImageCount = len(images) + } + + _zones := list(cli, "zones", func() (interface{}, error) { + return cli.GetIZones() + }) + zones := _zones.([]cloudprovider.ICloudZone) + result.ZoneCount = len(zones) + for i := range zones { + _hosts := list(zones[i], "host", func() (interface{}, error) { + return zones[i].GetIHosts() + }) + hosts := _hosts.([]cloudprovider.ICloudHost) + for j := range hosts { + _vms := list(hosts[j], "vms", func() (interface{}, error) { + return hosts[j].GetIVMs() + }) + vms := _vms.([]cloudprovider.ICloudVM) + result.VmCount += len(vms) + for k := range vms { + list(vms[k], "vm disks", func() (interface{}, error) { + return vms[k].GetIDisks() + }) + list(vms[k], "vm nics", func() (interface{}, error) { + return vms[k].GetINics() + }) + show(vms[k], "vm eip", func() (interface{}, error) { + return vms[k].GetIEIP() + }) + } + } + _storages := list(zones[i], "storages", func() (interface{}, error) { + return zones[i].GetIStorages() + }) + storages := _storages.([]cloudprovider.ICloudStorage) + for j := range storages { + _disks := list(storages[j], "disks", func() (interface{}, error) { + return storages[j].GetIDisks() + }) + disks := _disks.([]cloudprovider.ICloudDisk) + result.DiskCount += len(disks) + } + } + if args.TestLb { + /* + list(cli, "lb acls", func() (interface{}, error) { + return cli.GetILoadBalancerAcls() + }) + */ + _certs := list(cli, "lb certificates", func() (interface{}, error) { + return cli.GetILoadBalancerCertificates() + }) + certs := _certs.([]cloudprovider.ICloudLoadbalancerCertificate) + result.LbCertCount = len(certs) + /* + list(cli, "lb backend groups", func() (interface{}, error) { + return cli.GetILoadBalancerBackendGroups() + }) + */ + _lbs := list(cli, "lbs", func() (interface{}, error) { + return cli.GetILoadBalancers() + }) + lbs := _lbs.([]cloudprovider.ICloudLoadbalancer) + result.LbCount += len(lbs) + for i := range lbs { + _listeners := list(lbs[i], "lb listeners", func() (interface{}, error) { + return lbs[i].GetILoadBalancerListeners() + }) + listeners := _listeners.([]cloudprovider.ICloudLoadbalancerListener) + for j := range listeners { + list(listeners[j], "listener rules", func() (interface{}, error) { + return listeners[j].GetILoadbalancerListenerRules() + }) + } + _groups := list(lbs[i], "lb backend groups", func() (interface{}, error) { + return lbs[i].GetILoadBalancerBackendGroups() + }) + groups := _groups.([]cloudprovider.ICloudLoadbalancerBackendGroup) + for j := range groups { + list(groups[j], "lb backend", func() (interface{}, error) { + return groups[j].GetILoadbalancerBackends() + }) + } + } + } + + _rds := list(cli, "rds", func() (interface{}, error) { + return cli.GetIDBInstances() + }) + rds := _rds.([]cloudprovider.ICloudDBInstance) + result.RdsCount = len(rds) + log.Println("resource sum for ", cli.GetName(), jsonutils.Marshal(result).PrettyString()) + return nil + }) +} diff --git a/pkg/multicloud/ucloud/instance.go b/pkg/multicloud/ucloud/instance.go index ce46b864b4..8c1e189fbd 100644 --- a/pkg/multicloud/ucloud/instance.go +++ b/pkg/multicloud/ucloud/instance.go @@ -400,7 +400,7 @@ func (self *SInstance) StartVM(ctx context.Context) error { return nil } -func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { err := self.host.zone.region.StopVM(self.GetId()) if err != nil { return err diff --git a/pkg/multicloud/vpc_base.go b/pkg/multicloud/vpc_base.go index 75c2c0aefd..724cf54345 100644 --- a/pkg/multicloud/vpc_base.go +++ b/pkg/multicloud/vpc_base.go @@ -59,3 +59,7 @@ func (self *SVpc) CreateRouteToVpcPeeringConnection(cidrBlock, peerId string) er func (self *SVpc) DeleteVpcPeeringConnectionRoute(vpcPeeringConnectionId string) error { return errors.Wrapf(cloudprovider.ErrNotImplemented, "DeleteVpcPeeringConnectionRoute") } + +func (self *SVpc) ProposeJoinICloudInterVpcNetwork(opts *cloudprovider.SVpcJointInterVpcNetworkOption) error { + return errors.Wrapf(cloudprovider.ErrNotImplemented, "ProposeJoinICloudInterVpcNetwork") +} diff --git a/pkg/multicloud/zstack/instance.go b/pkg/multicloud/zstack/instance.go index 62038461f8..b9c6bf9e5d 100644 --- a/pkg/multicloud/zstack/instance.go +++ b/pkg/multicloud/zstack/instance.go @@ -279,8 +279,8 @@ func (region *SRegion) StartVM(instanceId string) error { return err } -func (instance *SInstance) StopVM(ctx context.Context, isForce bool) error { - err := instance.host.zone.region.StopVM(instance.UUID, isForce) +func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + err := instance.host.zone.region.StopVM(instance.UUID, opts.IsForce) if err != nil { return err } diff --git a/pkg/util/httputils/httputils.go b/pkg/util/httputils/httputils.go index 40d280142e..e864d62aa7 100644 --- a/pkg/util/httputils/httputils.go +++ b/pkg/util/httputils/httputils.go @@ -88,6 +88,10 @@ type JSONClientError struct { Data Error `json:"data,omitempty"` } +type sClient interface { + Do(req *http.Request) (*http.Response, error) +} + // body might have been consumed, so body is provided separately func newJsonClientErrorFromRequest(req *http.Request, body string) *JSONClientError { return newJsonClientErrorFromRequest2(req.Method, req.URL.String(), req.Header, body) @@ -146,7 +150,7 @@ type JSONClientErrorMsg struct { } type JsonClient struct { - client *http.Client + client sClient } type JsonRequest interface { @@ -224,7 +228,7 @@ func (ce *JSONClientError) ParseErrorFromJsonResponse(statusCode int, body jsonu return ce } -func NewJsonClient(client *http.Client) *JsonClient { +func NewJsonClient(client sClient) *JsonClient { return &JsonClient{client: client} } @@ -427,7 +431,7 @@ func GetDefaultClient() *http.Client { return GetClient(true, time.Second*15) } -func Request(client *http.Client, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Response, error) { +func Request(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Response, error) { req, resp, err := requestInternal(client, ctx, method, urlStr, header, body, debug) if err != nil { var reqBody string @@ -454,7 +458,7 @@ func Request(client *http.Client, ctx context.Context, method THttpMethod, urlSt return resp, nil } -func requestInternal(client *http.Client, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Request, *http.Response, error) { +func requestInternal(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Request, *http.Response, error) { if client == nil { client = defaultHttpClient } diff --git a/pkg/util/logclient/consts.go b/pkg/util/logclient/consts.go index a3ee04ce31..5a5ed48c25 100644 --- a/pkg/util/logclient/consts.go +++ b/pkg/util/logclient/consts.go @@ -187,4 +187,7 @@ const ( ACT_SYNC_RECORD_SETS = "sync_record_sets" ACT_DETACH_ALERTRESOURCE = "detach_alertresoruce" + ACT_NETWORK_ADD_VPC = "network_add_vpc" + ACT_NETWORK_REMOVE_VPC = "network_remove_vpc" + ACT_NETWORK_MODIFY_ROUTE = "network_modify_route" ) diff --git a/pkg/util/logclient/consts_i18n.go b/pkg/util/logclient/consts_i18n.go index 44b59d274a..db47e036a2 100644 --- a/pkg/util/logclient/consts_i18n.go +++ b/pkg/util/logclient/consts_i18n.go @@ -633,4 +633,16 @@ func init() { EN("Detach AlertResource"). CN("取消关联报警资源"), ) + t.Set(ACT_NETWORK_ADD_VPC, i18n.NewTableEntry(). + EN("Network Add Vpc"). + CN("网络加入vpc实例"), + ) + t.Set(ACT_NETWORK_REMOVE_VPC, i18n.NewTableEntry(). + EN("Network Remove Vpc"). + CN("网络移除vpc实例"), + ) + t.Set(ACT_NETWORK_MODIFY_ROUTE, i18n.NewTableEntry(). + EN("Modify Network Route"). + CN("修改网络路由策略"), + ) } diff --git a/pkg/util/oidcutils/client/client.go b/pkg/util/oidcutils/client/client.go index 8de4982104..d054b66359 100644 --- a/pkg/util/oidcutils/client/client.go +++ b/pkg/util/oidcutils/client/client.go @@ -126,6 +126,9 @@ func (cli *SOIDCClient) FetchToken(ctx context.Context, code string, redirUri st if err != nil { return nil, errors.Wrap(err, "request access token") } + if respJson.Contains("data") && !respJson.Contains("access_token") { + respJson, _ = respJson.Get("data") + } log.Debugf("AccesToken response: %s", respJson) accessTokenResp := oidcutils.SOIDCAccessTokenResponse{} err = respJson.Unmarshal(&accessTokenResp) @@ -148,6 +151,9 @@ func (cli *SOIDCClient) FetchUserInfo(ctx context.Context, accessToken string) ( if err != nil { return nil, errors.Wrap(err, "request userinfo") } + if body.Contains("data") { + body, _ = body.Get("data") + } info := make(map[string]string) err = body.Unmarshal(&info) if err != nil { diff --git a/pkg/util/seclib2/seclib.go b/pkg/util/seclib2/seclib.go index b0ac3105f5..862887583c 100644 --- a/pkg/util/seclib2/seclib.go +++ b/pkg/util/seclib2/seclib.go @@ -101,7 +101,7 @@ func AnalyzePasswordStrenth(passwd string) PasswordStrength { ps.Lowercases += 1 } else if strings.IndexByte(ALL_UPPERS, passwd[i]) >= 0 { ps.Uppercases += 1 - } else if strings.IndexByte(ALL_PUNC, passwd[i]) >= 0 { + } else if strings.IndexByte(PUNC, passwd[i]) >= 0 { ps.Punctuats += 1 } } diff --git a/pkg/util/seclib2/seclib_test.go b/pkg/util/seclib2/seclib_test.go index fe23ad5a0c..f3a508991c 100644 --- a/pkg/util/seclib2/seclib_test.go +++ b/pkg/util/seclib2/seclib_test.go @@ -31,7 +31,8 @@ func TestMeetComplxity(t *testing.T) { want bool }{ {"123456", false}, - {"123abcABC!@#", true}, + {"123abcABC!@#", false}, + {"123abcABC-@=", true}, } for _, c := range cases { if c.want != MeetComplxity(c.in) { diff --git a/pkg/vpcagent/models/modelset.go b/pkg/vpcagent/models/modelset.go index 78d49b9974..163bf45879 100644 --- a/pkg/vpcagent/models/modelset.go +++ b/pkg/vpcagent/models/modelset.go @@ -15,6 +15,8 @@ package models import ( + "fmt" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -33,7 +35,7 @@ type ( SecurityGroupRules map[string]*SecurityGroupRule Elasticips map[string]*Elasticip - Guestnetworks map[string]*Guestnetwork // key: guestId/ifname + Guestnetworks map[string]*Guestnetwork // key: rowId Guestsecgroups map[string]*Guestsecgroup // key: guestId/secgroupId DnsRecords map[string]*DnsRecord @@ -332,7 +334,8 @@ func (set Guestnetworks) NewModel() db.IModel { func (set Guestnetworks) AddModel(i db.IModel) { m := i.(*Guestnetwork) - set[m.GuestId+"/"+m.Ifname] = m + k := fmt.Sprintf("%d", m.RowId) + set[k] = m } func (set Guestnetworks) Copy() apihelper.IModelSet { diff --git a/scripts/codegen.py b/scripts/codegen.py index bf8eff2562..36d1f5b0f2 100755 --- a/scripts/codegen.py +++ b/scripts/codegen.py @@ -134,6 +134,7 @@ class ModelAPI(FuncDispatcher): self.run_model("image") self.run_model("cloudid") self.run_model("notify") + self.run_model("cloudevent") def gen_monitor(self): self.run(pkg=["monitor", "models"], out=["monitor"]) @@ -187,6 +188,11 @@ class SwaggerCode(FuncDispatcher): def gen_notify(self): self.run("notify", pkg=["models"], out="notify") + def gen_cloudevent(self): + self.run("cloudevent", pkg=["models"], out="cloudevent") + + + class SwaggerYAML(FuncDispatcher): def __init__(self, swagger_dir, out_dir): @@ -222,6 +228,9 @@ class SwaggerYAML(FuncDispatcher): def gen_notify(self): self.run("notify") + def gen_cloudevent(self): + self.run("cloudevent") + class SwaggerServe(object): def __init__(self, output_dir):