diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 4442a73453..21ca227cb1 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -39,17 +39,19 @@ const ( CLOUD_PROVIDER_UCLOUD = "Ucloud" CLOUD_PROVIDER_ZSTACK = "ZStack" - CLOUD_PROVIDER_HEALTH_NORMAL = "normal" // 远端处于健康状态 - CLOUD_PROVIDER_HEALTH_INSUFFICIENT = "insufficient" // 不足按需资源余额 - CLOUD_PROVIDER_HEALTH_SUSPENDED = "suspended" // 远端处于冻结状态 - CLOUD_PROVIDER_HEALTH_ARREARS = "arrears" // 远端处于欠费状态 - CLOUD_PROVIDER_HEALTH_UNKNOWN = "unknown" // 未知状态,查询失败 + CLOUD_PROVIDER_HEALTH_NORMAL = "normal" // 远端处于健康状态 + CLOUD_PROVIDER_HEALTH_INSUFFICIENT = "insufficient" // 不足按需资源余额 + CLOUD_PROVIDER_HEALTH_SUSPENDED = "suspended" // 远端处于冻结状态 + CLOUD_PROVIDER_HEALTH_ARREARS = "arrears" // 远端处于欠费状态 + CLOUD_PROVIDER_HEALTH_UNKNOWN = "unknown" // 未知状态,查询失败 + CLOUD_PROVIDER_HEALTH_NO_PERMISSION = "no permission" // 没有权限获取账单信息 ZSTACK_BRAND_DSTACK = "DStack" ) var ( - CLOUD_PROVIDER_VALID_STATUS = []string{CLOUD_PROVIDER_CONNECTED} + CLOUD_PROVIDER_VALID_STATUS = []string{CLOUD_PROVIDER_CONNECTED} + CLOUD_PROVIDER_VALID_HEALTH_STATUS = []string{CLOUD_PROVIDER_HEALTH_NORMAL, CLOUD_PROVIDER_HEALTH_NO_PERMISSION} CLOUD_PROVIDERS = []string{ CLOUD_PROVIDER_ONECLOUD, diff --git a/pkg/cloudprovider/consts.go b/pkg/cloudprovider/consts.go index 96bbdf8e62..381d3a2d5c 100644 --- a/pkg/cloudprovider/consts.go +++ b/pkg/cloudprovider/consts.go @@ -34,3 +34,4 @@ var ErrTimeout = errors.New("timeout") var ErrNotImplemented = errors.New("Not implemented") var ErrNotSupported = errors.New("Not supported") var ErrInvalidProvider = errors.New("Invalid provider") +var ErrNoBalancePermission = errors.New("No balance permission") diff --git a/pkg/compute/models/cachedimages.go b/pkg/compute/models/cachedimages.go index 7c627a3135..3bb3fb73a1 100644 --- a/pkg/compute/models/cachedimages.go +++ b/pkg/compute/models/cachedimages.go @@ -510,7 +510,7 @@ func (image *SCachedimage) getValidStoragecache() []SStoragecache { q = q.Join(storagecacheimages, sqlchemy.Equals(storagecaches.Field("id"), storagecacheimages.Field("storagecache_id"))) q = q.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) q = q.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - q = q.Filter(sqlchemy.Equals(providers.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + q = q.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) q = q.Filter(sqlchemy.Equals(storagecacheimages.Field("cachedimage_id"), image.Id)) q = q.Filter(sqlchemy.Equals(storagecacheimages.Field("status"), api.CACHED_IMAGE_STATUS_READY)) diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index e662c42cec..212789f364 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -1204,11 +1204,14 @@ func (account *SCloudaccount) probeAccountStatus(ctx context.Context, userCred m } balance, status, err := manager.GetBalance() if err != nil { - if err != cloudprovider.ErrNotSupported { + switch err { + case cloudprovider.ErrNotSupported: + status = api.CLOUD_PROVIDER_HEALTH_NORMAL + case cloudprovider.ErrNoBalancePermission: + status = api.CLOUD_PROVIDER_HEALTH_NO_PERMISSION + default: log.Errorf("manager.GetBalance %s fail %s", account.Name, err) status = api.CLOUD_PROVIDER_HEALTH_UNKNOWN - } else { - status = api.CLOUD_PROVIDER_HEALTH_NORMAL } } version := manager.GetVersion() diff --git a/pkg/compute/models/cloudregions.go b/pkg/compute/models/cloudregions.go index 1205d0370c..76e3df1bbb 100644 --- a/pkg/compute/models/cloudregions.go +++ b/pkg/compute/models/cloudregions.go @@ -542,7 +542,7 @@ func (manager *SCloudregionManager) ListItemFilter(ctx context.Context, q *sqlch } sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) sq = sq.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - sq = sq.Filter(sqlchemy.Equals(providers.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + sq = sq.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) if usableVpc { sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), api.VPC_STATUS_AVAILABLE)) } diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index f440c88435..33a586308b 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -359,7 +359,7 @@ func (manager *SHostManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQu hostQ1 = hostQ1.Join(networks, sqlchemy.Equals(hostwires.Field("wire_id"), networks.Field("wire_id"))) hostQ1 = hostQ1.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) hostQ1 = hostQ1.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - hostQ1 = hostQ1.Filter(sqlchemy.Equals(providers.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + hostQ1 = hostQ1.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) hostQ1 = hostQ1.Filter(sqlchemy.Equals(networks.Field("status"), api.NETWORK_STATUS_AVAILABLE)) hostQ1 = hostQ1.Filter(sqlchemy.IsTrue(hosts.Field("enabled"))) diff --git a/pkg/compute/models/skus.go b/pkg/compute/models/skus.go index 526cb88c93..1dc6ca40ab 100644 --- a/pkg/compute/models/skus.go +++ b/pkg/compute/models/skus.go @@ -493,7 +493,7 @@ func networkUsableRegionQueries(f sqlchemy.IQueryField) []sqlchemy.ICondition { sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), api.NETWORK_STATUS_AVAILABLE)) sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) sq = sq.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - sq = sq.Filter(sqlchemy.Equals(providers.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + sq = sq.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), api.VPC_STATUS_AVAILABLE)) sq2 := vpcs.Query(sqlchemy.DISTINCT("cloudregion_id", vpcs.Field("cloudregion_id"))) @@ -518,7 +518,7 @@ func providerFilter(q *sqlchemy.SQuery, provider string, public_cloud bool) *sql subq = subq.Join(providerTable, sqlchemy.Equals(providerRegionTable.Field("cloudprovider_id"), providerTable.Field("id"))) subq = subq.Filter(sqlchemy.IsTrue(providerTable.Field("enabled"))) subq = subq.Filter(sqlchemy.In(providerTable.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - subq = subq.Filter(sqlchemy.Equals(providerTable.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + subq = subq.Filter(sqlchemy.In(providerTable.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) q = q.Filter(sqlchemy.In(q.Field("cloudregion_id"), subq.SubQuery())) } diff --git a/pkg/compute/models/zones.go b/pkg/compute/models/zones.go index 6660f11539..f69d593ab5 100644 --- a/pkg/compute/models/zones.go +++ b/pkg/compute/models/zones.go @@ -435,7 +435,7 @@ func usableZoneQ1(providers, vpcs, wires, networks *sqlchemy.SSubQuery, usableNe sq = sq.Filter(sqlchemy.IsNotEmpty(wires.Field("zone_id"))) sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) sq = sq.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - sq = sq.Filter(sqlchemy.Equals(providers.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + sq = sq.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) if usableVpc { sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), api.VPC_STATUS_AVAILABLE)) } @@ -491,7 +491,7 @@ func usableZoneQ3(providers, vpcs, wires, networks, zones *sqlchemy.SSubQuery, u sq = sq.Filter(sqlchemy.IsNullOrEmpty(wires.Field("zone_id"))) sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) sq = sq.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS)) - sq = sq.Filter(sqlchemy.Equals(providers.Field("health_status"), api.CLOUD_PROVIDER_HEALTH_NORMAL)) + sq = sq.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS)) if usableVpc { sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), api.VPC_STATUS_AVAILABLE)) } diff --git a/pkg/scheduler/algorithm/predicates/guest/status_predicate.go b/pkg/scheduler/algorithm/predicates/guest/status_predicate.go index 32ff819ce4..947eb3fa5a 100644 --- a/pkg/scheduler/algorithm/predicates/guest/status_predicate.go +++ b/pkg/scheduler/algorithm/predicates/guest/status_predicate.go @@ -72,8 +72,8 @@ func (p *StatusPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core if !utils.IsInStringArray(cloudprovider.Status, api.CLOUD_PROVIDER_VALID_STATUS) { h.Exclude2("cloud_provider_status", cloudprovider.Status, api.CLOUD_PROVIDER_VALID_STATUS) } - if cloudprovider.HealthStatus != api.CLOUD_PROVIDER_HEALTH_NORMAL { - h.Exclude2("cloud_provider_health_status", cloudprovider.HealthStatus, api.CLOUD_PROVIDER_HEALTH_NORMAL) + if !utils.IsInStringArray(cloudprovider.HealthStatus, api.CLOUD_PROVIDER_VALID_HEALTH_STATUS) { + h.Exclude2("cloud_provider_health_status", cloudprovider.HealthStatus, api.CLOUD_PROVIDER_VALID_HEALTH_STATUS) } } diff --git a/pkg/util/aliyun/business.go b/pkg/util/aliyun/business.go index dbf0242c28..cc9e3359ad 100644 --- a/pkg/util/aliyun/business.go +++ b/pkg/util/aliyun/business.go @@ -17,6 +17,8 @@ package aliyun import ( "time" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" @@ -66,14 +68,15 @@ type SPrepaidCard struct { func (self *SAliyunClient) QueryAccountBalance() (*SAccountBalance, error) { body, err := self.businessRequest("QueryAccountBalance", nil) if err != nil { - log.Errorf("QueryAccountBalance fail %s", err) - return nil, err + if isError(err, "NotApplicable") { + return nil, cloudprovider.ErrNoBalancePermission + } + return nil, errors.Wrapf(err, "QueryAccountBalance") } balance := SAccountBalance{} err = body.Unmarshal(&balance, "Data") if err != nil { - log.Errorf("Unmarshal AccountBalance fail %s", err) - return nil, err + return nil, errors.Wrapf(err, "Unmarshal AccountBalance") } return &balance, nil } diff --git a/pkg/util/qcloud/qcloud.go b/pkg/util/qcloud/qcloud.go index c70ce73fb0..618927de2f 100644 --- a/pkg/util/qcloud/qcloud.go +++ b/pkg/util/qcloud/qcloud.go @@ -540,6 +540,9 @@ func (client *SQcloudClient) QueryAccountBalance() (*SAccountBalance, error) { balance := SAccountBalance{} body, err := client.billingRequest("DescribeAccountBalance", nil) if err != nil { + if isError(err, []string{"UnauthorizedOperation.NotFinanceAuth"}) { + return nil, cloudprovider.ErrNoBalancePermission + } log.Errorf("DescribeAccountBalance fail %s", err) return nil, err } diff --git a/pkg/util/qcloud/region.go b/pkg/util/qcloud/region.go index d204b4757d..a2d37a4e80 100644 --- a/pkg/util/qcloud/region.go +++ b/pkg/util/qcloud/region.go @@ -799,3 +799,7 @@ func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) { } return instance.InstanceState, nil } + +func (self *SRegion) QueryAccountBalance() (*SAccountBalance, error) { + return self.client.QueryAccountBalance() +} diff --git a/pkg/util/qcloud/shell/balance.go b/pkg/util/qcloud/shell/balance.go new file mode 100644 index 0000000000..b5e16d5f60 --- /dev/null +++ b/pkg/util/qcloud/shell/balance.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/util/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type BalanceOptions struct { + } + shellutils.R(&BalanceOptions{}, "balance-show", "Show balance", func(cli *qcloud.SRegion, args *BalanceOptions) error { + balance, err := cli.QueryAccountBalance() + if err != nil { + return err + } + printObject(balance) + return nil + }) +}