diff --git a/cmd/climc/shell/dnsrecords.go b/cmd/climc/shell/dnsrecords.go index 0a6894f388..fa017724a6 100644 --- a/cmd/climc/shell/dnsrecords.go +++ b/cmd/climc/shell/dnsrecords.go @@ -1,64 +1,16 @@ package shell import ( - "fmt" - - "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" ) -type DNSRecordOptions struct { - A []string `help:"DNS A record" metavar:"A_RECORD" positional:"false"` - AAAA []string `help:"DNS AAAA record" metavar:"AAAA_RECORD" positional:"false"` - CNAME string `help:"DNS CNAME record" metavar:"CNAME_RECORD" positional:"false"` - PTR string `help:"DNS PTR record" metavar:"PTR_RECORD" positional:"false"` - - SRVHost string `help:"(deprecated) DNS SRV record, server of service" metavar:"SRV_RECORD_HOST" positional:"false"` - SRVPort int64 `help:"(deprecated) DNS SRV record, port of service" metavar:"SRV_RECORD_PORT" positional:"false"` - SRV []string `help:"DNS SRV record, in the format of host:port:weight:priority" metavar:"SRV_RECORD" positional:"false"` -} - -func parseDNSRecords(args *DNSRecordOptions, params *jsonutils.JSONDict) { - if len(args.A) > 0 || len(args.AAAA) > 0 { - for i, a := range args.A { - params.Add(jsonutils.NewString(a), fmt.Sprintf("A.%d", i)) - } - for i, a := range args.AAAA { - params.Add(jsonutils.NewString(a), fmt.Sprintf("AAAA.%d", i)) - } - } else if len(args.CNAME) > 0 { - params.Add(jsonutils.NewString(args.CNAME), "CNAME") - } else if len(args.SRV) > 0 || (len(args.SRVHost) > 0 && args.SRVPort > 0) { - for i, s := range args.SRV { - params.Set(fmt.Sprintf("SRV.%d", i), jsonutils.NewString(s)) - } - // Keep using the original argument passing method in case a - // newer climc is used against old service - if len(args.SRVHost) > 0 && args.SRVPort > 0 { - params.Set("SRV_host", jsonutils.NewString(args.SRVHost)) - params.Set("SRV_port", jsonutils.NewInt(args.SRVPort)) - } - } else if len(args.PTR) > 0 { - params.Add(jsonutils.NewString(args.PTR), "PTR") - } -} - func init() { - type DNSListOptions struct { - options.BaseListOptions - } - R(&DNSListOptions{}, "dns-list", "List dns records", func(s *mcclient.ClientSession, suboptions *DNSListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = suboptions.BaseListOptions.Params() - if err != nil { - return err - - } + R(&options.DNSListOptions{}, "dns-list", "List dns records", func(s *mcclient.ClientSession, opts *options.DNSListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err } result, err := modules.DNSRecords.List(s, params) if err != nil { @@ -68,24 +20,10 @@ func init() { return nil }) - type DNSCreateOptions struct { - NAME string `help:"DNS name to create"` - TTL int64 `help:"TTL in seconds" positional:"false"` - Desc string `help:"Description" metavar:"DESCRIPTION"` - DNSRecordOptions - } - R(&DNSCreateOptions{}, "dns-create", "Create dns record", func(s *mcclient.ClientSession, args *DNSCreateOptions) error { - params := jsonutils.NewDict() - parseDNSRecords(&args.DNSRecordOptions, params) - if params.Size() == 0 { - return fmt.Errorf("No records to create") - } - params.Add(jsonutils.NewString(args.NAME), "name") - if args.TTL > 0 { - params.Add(jsonutils.NewInt(args.TTL), "ttl") - } - if len(args.Desc) > 0 { - params.Add(jsonutils.NewString(args.Desc), "description") + R(&options.DNSCreateOptions{}, "dns-create", "Create dns record", func(s *mcclient.ClientSession, opts *options.DNSCreateOptions) error { + params, err := opts.Params() + if err != nil { + return err } rec, e := modules.DNSRecords.Create(s, params) if e != nil { @@ -95,11 +33,8 @@ func init() { return nil }) - type DNSShowOptions struct { - ID string `help:"ID of DNS record to show"` - } - R(&DNSShowOptions{}, "dns-show", "Show details of a dns records", func(s *mcclient.ClientSession, args *DNSShowOptions) error { - dns, e := modules.DNSRecords.Get(s, args.ID, nil) + R(&options.DNSGetOptions{}, "dns-show", "Show details of a dns records", func(s *mcclient.ClientSession, opts *options.DNSGetOptions) error { + dns, e := modules.DNSRecords.Get(s, opts.ID, nil) if e != nil { return e } @@ -107,29 +42,12 @@ func init() { return nil }) - type DNSUpdateOptions struct { - ID string `help:"ID of DNS record to update"` - Name string `help:"Domain name"` - TTL int64 `help:"TTL in seconds" positional:"false"` - Desc string `help:"Description"` - DNSRecordOptions - } - R(&DNSUpdateOptions{}, "dns-update", "Update details of a dns records", func(s *mcclient.ClientSession, args *DNSUpdateOptions) error { - params := jsonutils.NewDict() - if len(args.Name) > 0 { - params.Add(jsonutils.NewString(args.Name), "name") + R(&options.DNSUpdateOptions{}, "dns-update", "Update details of a dns records", func(s *mcclient.ClientSession, opts *options.DNSUpdateOptions) error { + params, err := opts.Params() + if err != nil { + return err } - if len(args.Desc) > 0 { - params.Add(jsonutils.NewString(args.Desc), "description") - } - if args.TTL > 0 { - params.Add(jsonutils.NewInt(args.TTL), "ttl") - } - parseDNSRecords(&args.DNSRecordOptions, params) - if params.Size() == 0 { - return InvalidUpdateError() - } - dns, e := modules.DNSRecords.Update(s, args.ID, params) + dns, e := modules.DNSRecords.Update(s, opts.ID, params) if e != nil { return e } @@ -137,8 +55,8 @@ func init() { return nil }) - R(&DNSShowOptions{}, "dns-delete", "Delete a dns record", func(s *mcclient.ClientSession, args *DNSShowOptions) error { - dns, e := modules.DNSRecords.Delete(s, args.ID, nil) + R(&options.DNSGetOptions{}, "dns-delete", "Delete a dns record", func(s *mcclient.ClientSession, opts *options.DNSGetOptions) error { + dns, e := modules.DNSRecords.Delete(s, opts.ID, nil) if e != nil { return e } @@ -146,8 +64,8 @@ func init() { return nil }) - R(&DNSShowOptions{}, "dns-public", "Make a dns record publicly available", func(s *mcclient.ClientSession, args *DNSShowOptions) error { - dns, e := modules.DNSRecords.PerformAction(s, args.ID, "public", nil) + R(&options.DNSGetOptions{}, "dns-public", "Make a dns record publicly available", func(s *mcclient.ClientSession, opts *options.DNSGetOptions) error { + dns, e := modules.DNSRecords.PerformAction(s, opts.ID, "public", nil) if e != nil { return e } @@ -155,8 +73,8 @@ func init() { return nil }) - R(&DNSShowOptions{}, "dns-private", "Make a dns record private", func(s *mcclient.ClientSession, args *DNSShowOptions) error { - dns, e := modules.DNSRecords.PerformAction(s, args.ID, "private", nil) + R(&options.DNSGetOptions{}, "dns-private", "Make a dns record private", func(s *mcclient.ClientSession, opts *options.DNSGetOptions) error { + dns, e := modules.DNSRecords.PerformAction(s, opts.ID, "private", nil) if e != nil { return e } @@ -164,8 +82,8 @@ func init() { return nil }) - R(&DNSShowOptions{}, "dns-enable", "Enable dns record", func(s *mcclient.ClientSession, args *DNSShowOptions) error { - dns, e := modules.DNSRecords.PerformAction(s, args.ID, "enable", nil) + R(&options.DNSGetOptions{}, "dns-enable", "Enable dns record", func(s *mcclient.ClientSession, opts *options.DNSGetOptions) error { + dns, e := modules.DNSRecords.PerformAction(s, opts.ID, "enable", nil) if e != nil { return e } @@ -173,8 +91,8 @@ func init() { return nil }) - R(&DNSShowOptions{}, "dns-disable", "Disable dns record", func(s *mcclient.ClientSession, args *DNSShowOptions) error { - dns, e := modules.DNSRecords.PerformAction(s, args.ID, "disable", nil) + R(&options.DNSGetOptions{}, "dns-disable", "Disable dns record", func(s *mcclient.ClientSession, opts *options.DNSGetOptions) error { + dns, e := modules.DNSRecords.PerformAction(s, opts.ID, "disable", nil) if e != nil { return e } @@ -182,17 +100,12 @@ func init() { return nil }) - type DNSUpdateRecordsOptions struct { - ID string `help:"ID of dns record to modify"` - DNSRecordOptions - } - R(&DNSUpdateRecordsOptions{}, "dns-add-records", "Add DNS records to a name", func(s *mcclient.ClientSession, args *DNSUpdateRecordsOptions) error { - params := jsonutils.NewDict() - parseDNSRecords(&args.DNSRecordOptions, params) - if params.Size() == 0 { - return fmt.Errorf("Nothing to add") + R(&options.DNSUpdateRecordsOptions{}, "dns-add-records", "Add DNS records to a name", func(s *mcclient.ClientSession, opts *options.DNSUpdateRecordsOptions) error { + params, err := opts.Params() + if err != nil { + return err } - dns, e := modules.DNSRecords.PerformAction(s, args.ID, "add-records", params) + dns, e := modules.DNSRecords.PerformAction(s, opts.ID, "add-records", params) if e != nil { return e } @@ -200,13 +113,12 @@ func init() { return nil }) - R(&DNSUpdateRecordsOptions{}, "dns-remove-records", "Remove DNS records from a name", func(s *mcclient.ClientSession, args *DNSUpdateRecordsOptions) error { - params := jsonutils.NewDict() - parseDNSRecords(&args.DNSRecordOptions, params) - if params.Size() == 0 { - return fmt.Errorf("Nothing to remove") + R(&options.DNSUpdateRecordsOptions{}, "dns-remove-records", "Remove DNS records from a name", func(s *mcclient.ClientSession, opts *options.DNSUpdateRecordsOptions) error { + params, err := opts.Params() + if err != nil { + return err } - dns, e := modules.DNSRecords.PerformAction(s, args.ID, "remove-records", params) + dns, e := modules.DNSRecords.PerformAction(s, opts.ID, "remove-records", params) if e != nil { return e } diff --git a/cmd/climc/shell/schedpolicies.go b/cmd/climc/shell/schedpolicies.go index fac45935b7..758b6c0f86 100644 --- a/cmd/climc/shell/schedpolicies.go +++ b/cmd/climc/shell/schedpolicies.go @@ -111,6 +111,18 @@ func init() { return nil }) + type SchedpoliciesShowOptions struct { + ID string `help:"ID or name of the sched policy"` + } + R(&SchedpoliciesShowOptions{}, "sched-policy-show", "show details of a sched policy", func(s *mcclient.ClientSession, args *SchedpoliciesShowOptions) error { + result, err := modules.Schedpolicies.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + type SchedpoliciesEvaluateOptions struct { ID string `help:"ID or name of the sched policy"` SERVER string `help:"ID or name of the server"` diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index 1f4b543a69..c2d72e15f2 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -57,12 +57,16 @@ func (dispatcher *DBModelDispatcher) ContextKeywordPlural() []string { } func (dispatcher *DBModelDispatcher) Filter(f appsrv.FilterHandler) appsrv.FilterHandler { - return auth.Authenticate(f) + if consts.IsRbacEnabled() { + return auth.AuthenticateWithDelayDecision(f, true) + } else { + return auth.Authenticate(f) + } } func fetchUserCredential(ctx context.Context) mcclient.TokenCredential { token := auth.FetchUserCredential(ctx) - if token == nil { + if token == nil && !consts.IsRbacEnabled() { log.Fatalf("user token credential not found?") } return token @@ -479,14 +483,14 @@ func listItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok if err != nil { return nil, httperrors.NewGeneralError(err) } - retConut := len(retList) + retCount := len(retList) // apply customizeFilters retList, err = customizeFilters.DoApply(retList) if err != nil { return nil, httperrors.NewGeneralError(err) } - if len(retList) != retConut { + if len(retList) != retCount { totalCnt = int64(len(retList)) } paginate := false @@ -500,7 +504,7 @@ func listItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok func calculateListResult(data []jsonutils.JSONObject, total, limit, offset int64, paginate bool) *modules.ListResult { if paginate { // do offset first - if offset != 0 { + if offset > 0 { if total > offset { data = data[offset:] } else { @@ -508,11 +512,13 @@ func calculateListResult(data []jsonutils.JSONObject, total, limit, offset int64 } } // do limit - if total > limit { + if limit > 0 && total > limit { data = data[:limit] } } + retResult := modules.ListResult{Data: data, Total: int(total), Limit: int(limit), Offset: int(offset)} + return &retResult } @@ -725,7 +731,7 @@ func fetchOwnerProjectId(ctx context.Context, manager IModelManager, userCred mc if consts.IsRbacEnabled() { result := policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), policy.PolicyDelegation, "") - if result == rbacutils.Allow { + if result == rbacutils.AdminAllow { isAllow = true } } else { @@ -957,6 +963,34 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json return results, nil } +func managerPerformCheckCreateData( + manager IModelManager, + ctx context.Context, + userCred mcclient.TokenCredential, + action string, + ownerProjId string, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) (jsonutils.JSONObject, error) { + body, err := data.(*jsonutils.JSONDict).Get(manager.Keyword()) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + bodyDict := body.(*jsonutils.JSONDict) + + var isAllow bool + if consts.IsRbacEnabled() { + isAllow = isClassActionRbacAllowed(manager, userCred, ownerProjId, policy.PolicyActionPerform, action) + } else { + isAllow = manager.AllowPerformCheckCreateData(ctx, userCred, query, data) + } + if !isAllow { + return nil, httperrors.NewForbiddenError("not allow to perform %s", action) + } + + return manager.ValidateCreateData(ctx, userCred, ownerProjId, query, bodyDict) +} + func (dispatcher *DBModelDispatcher) PerformClassAction(ctx context.Context, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { userCred := fetchUserCredential(ctx) @@ -969,25 +1003,8 @@ func (dispatcher *DBModelDispatcher) PerformClassAction(ctx context.Context, act defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId) if action == "check-create-data" { - manager := dispatcher.modelManager - - body, err := data.(*jsonutils.JSONDict).Get(manager.Keyword()) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - data := body.(*jsonutils.JSONDict) - - var isAllow bool - if consts.IsRbacEnabled() { - isAllow = isClassActionRbacAllowed(manager, userCred, ownerProjId, policy.PolicyActionPerform, action) - } else { - isAllow = manager.AllowPerformCheckCreateData(ctx, userCred, query, data) - } - if !isAllow { - return nil, httperrors.NewForbiddenError("not allow to perform %s", action) - } - - return manager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + return managerPerformCheckCreateData(dispatcher.modelManager, + ctx, userCred, action, ownerProjId, query, data) } managerValue := reflect.ValueOf(dispatcher.modelManager) @@ -1022,8 +1039,8 @@ func objectPerformAction(dispatcher *DBModelDispatcher, model IModel, modelValue isGeneral := false funcName := fmt.Sprintf("Perform%s", utils.Kebab2Camel(action, "-")) - funcValue := modelValue.MethodByName(funcName) + if !funcValue.IsValid() || funcValue.IsNil() { funcValue = modelValue.MethodByName(generalFuncName) if !funcValue.IsValid() || funcValue.IsNil() { @@ -1057,7 +1074,12 @@ func objectPerformAction(dispatcher *DBModelDispatcher, model IModel, modelValue var isAllow bool if consts.IsRbacEnabled() { - isAllow = isObjectRbacAllowed(dispatcher.modelManager, model, userCred, policy.PolicyActionPerform, action) + if model == nil { + ownerProjId, _ := fetchOwnerProjectId(ctx, dispatcher.modelManager, userCred, data) + isAllow = isClassActionRbacAllowed(dispatcher.modelManager, userCred, ownerProjId, policy.PolicyActionPerform, action) + } else { + isAllow = isObjectRbacAllowed(dispatcher.modelManager, model, userCred, policy.PolicyActionPerform, action) + } } else { allowFuncName := "Allow" + funcName allowFuncValue := modelValue.MethodByName(allowFuncName) diff --git a/pkg/cloudcommon/db/quotas/handler.go b/pkg/cloudcommon/db/quotas/handler.go index 6d16c8c708..ed27a32b72 100644 --- a/pkg/cloudcommon/db/quotas/handler.go +++ b/pkg/cloudcommon/db/quotas/handler.go @@ -91,7 +91,7 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request if consts.IsRbacEnabled() { result := policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), policy.PolicyDelegation, policy.PolicyActionGet) - isAllow = result == rbacutils.Allow + isAllow = result == rbacutils.AdminAllow } else { isAllow = userCred.IsSystemAdmin() } @@ -101,7 +101,7 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request } if consts.IsRbacEnabled() { if policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), - "quotas", policy.PolicyActionGet) != rbacutils.Allow { + "quotas", policy.PolicyActionGet) != rbacutils.AdminAllow { httperrors.ForbiddenError(w, "not allow to query quota") return } @@ -138,7 +138,7 @@ func setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request var isAllow bool if consts.IsRbacEnabled() { isAllow = policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), - "quotas", policy.PolicyActionUpdate) == rbacutils.Allow + "quotas", policy.PolicyActionUpdate) == rbacutils.AdminAllow } else { isAllow = userCred.IsSystemAdmin() } @@ -201,7 +201,7 @@ func checkQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Reque isAllow := false if consts.IsRbacEnabled() { isAllow = policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), - policy.PolicyDelegation, policy.PolicyActionGet) == rbacutils.Allow + policy.PolicyDelegation, policy.PolicyActionGet) == rbacutils.AdminAllow } else { isAllow = userCred.IsSystemAdmin() } @@ -211,7 +211,7 @@ func checkQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Reque } if consts.IsRbacEnabled() { if policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), - "quotas", policy.PolicyActionGet) != rbacutils.Allow { + "quotas", policy.PolicyActionGet) != rbacutils.AdminAllow { httperrors.ForbiddenError(w, "not allow to query quota") return } diff --git a/pkg/cloudcommon/db/rbac.go b/pkg/cloudcommon/db/rbac.go index 15798c9536..d2565e2bd2 100644 --- a/pkg/cloudcommon/db/rbac.go +++ b/pkg/cloudcommon/db/rbac.go @@ -28,14 +28,22 @@ func isListRbacAllowedInternal(manager IModelManager, resource string, userCred } result := policy.PolicyManager.Allow(false, userCred, consts.GetServiceType(), resource, policy.PolicyActionList) - log.Debugf("allow list for non-admin %s %v ownerId: %s", result, requireAdmin, ownerId) - if (result == rbacutils.OwnerAllow && !requireAdmin) || (result == rbacutils.Allow && requireAdmin) { + + switch { + case result == rbacutils.GuestAllow: + return true + case result == rbacutils.UserAllow && userCred != nil && userCred.IsValid(): + return true + case result == rbacutils.OwnerAllow && !requireAdmin: + return true + case result == rbacutils.AdminAllow && requireAdmin: return true } + result = policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), resource, policy.PolicyActionList) - log.Debugf("allow list for admin %s %v ownerId: %s", result, requireAdmin, ownerId) - return result == rbacutils.Allow + + return result == rbacutils.AdminAllow } func isJointListRbacAllowed(manager IJointModelManager, userCred mcclient.TokenCredential, isAdminMode bool) bool { @@ -54,16 +62,23 @@ func isClassActionRbacAllowed(manager IModelManager, userCred mcclient.TokenCred } else { requireAdmin = true } - // if !requireAdmin { + result := policy.PolicyManager.Allow(false, userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...) - if result == rbacutils.Allow || (!requireAdmin && result == rbacutils.OwnerAllow) { + switch { + case result == rbacutils.GuestAllow: + return true + case result == rbacutils.UserAllow && userCred != nil && userCred.IsValid(): + return true + case result == rbacutils.OwnerAllow && !requireAdmin: + return true + case result == rbacutils.AdminAllow && requireAdmin: return true } - // } + result = policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...) - return result == rbacutils.Allow + return result == rbacutils.AdminAllow } func isObjectRbacAllowed(manager IModelManager, model IModel, userCred mcclient.TokenCredential, action string, extra ...string) bool { @@ -85,16 +100,22 @@ func isObjectRbacAllowed(manager IModelManager, model IModel, userCred mcclient. requireAdmin = true } - //if !requireAdmin { result := policy.PolicyManager.Allow(false, userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...) - if result == rbacutils.Allow || (!requireAdmin && result == rbacutils.OwnerAllow && isOwner) { + switch { + case result == rbacutils.GuestAllow: + return true + case result == rbacutils.UserAllow && userCred != nil && userCred.IsValid(): + return true + case result == rbacutils.OwnerAllow && isOwner && !requireAdmin: + return true + case result == rbacutils.AdminAllow && (requireAdmin || isOwner): return true } - //} + result = policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), manager.KeywordPlural(), action, extra...) - return result == rbacutils.Allow + return result == rbacutils.AdminAllow } func isJointObjectRbacAllowed(manager IJointModelManager, item IJointModel, userCred mcclient.TokenCredential, action string, extra ...string) bool { diff --git a/pkg/cloudcommon/db/sharablevirtual.go b/pkg/cloudcommon/db/sharablevirtual.go index 62c38c442d..8d1cf2db5f 100644 --- a/pkg/cloudcommon/db/sharablevirtual.go +++ b/pkg/cloudcommon/db/sharablevirtual.go @@ -11,7 +11,7 @@ import ( type SSharableVirtualResourceBase struct { SVirtualResourceBase - IsPublic bool `default:"false" nullable:"false" list:"user"` + IsPublic bool `default:"false" nullable:"false" create:"admin_optional" list:"user"` } type SSharableVirtualResourceBaseManager struct { diff --git a/pkg/cloudcommon/db/taskman/tasks.go b/pkg/cloudcommon/db/taskman/tasks.go index 9b4f175e46..5460fff7cc 100644 --- a/pkg/cloudcommon/db/taskman/tasks.go +++ b/pkg/cloudcommon/db/taskman/tasks.go @@ -89,10 +89,6 @@ func (manager *STaskManager) FilterByName(q *sqlchemy.SQuery, name string) *sqlc return q } -func (manager *STaskManager) FilterByOwner(q *sqlchemy.SQuery, owner string) *sqlchemy.SQuery { - return q -} - func (manager *STaskManager) AllowPerformAction(ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return true } @@ -105,6 +101,19 @@ func (manager *STaskManager) PerformAction(ctx context.Context, userCred mcclien return resp, nil } +func (manager *STaskManager) GetOwnerId(userCred mcclient.IIdentityProvider) string { + return userCred.GetProjectId() +} + +func (self *STask) GetOwnerProjectId() string { + return self.UserCred.GetProjectId() +} + +func (manager *STaskManager) FilterByOwner(q *sqlchemy.SQuery, owner string) *sqlchemy.SQuery { + q = q.Contains("user_cred", owner) + return q +} + func (self *STask) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { return userCred.IsSystemAdmin() || userCred.GetProjectId() == self.UserCred.GetProjectId() } @@ -414,7 +423,7 @@ func execITask(taskValue reflect.Value, task *STask, odata jsonutils.JSONObject, return } - log.Debugf("Call %s %s: %s with %s", task.TaskName, stageName, funcValue, params) + log.Debugf("Call %s %s", task.TaskName, stageName) funcValue.Call(params) // call save request context diff --git a/pkg/cloudcommon/options.go b/pkg/cloudcommon/options.go index 0106a3e6c9..fa7730c4a9 100644 --- a/pkg/cloudcommon/options.go +++ b/pkg/cloudcommon/options.go @@ -40,7 +40,7 @@ type Options struct { SslCertfile string `help:"ssl certification file"` SslKeyfile string `help:"ssl certification key file"` - EnableRbac bool `help:"Switch on Role-based Access Control" default:"false"` + EnableRbac bool `help:"Switch on Role-based Access Control" default:"true"` RbacDebug bool `help:"turn on rbac debug log" default:"false"` RbacPolicySyncPeriodSeconds int `help:"policy sync interval in seconds, default 15 minutes" default:"900"` RbacPolicySyncFailedRetrySeconds int `help:"seconds to wait after a failed sync, default 30 seconds" default:"30"` diff --git a/pkg/cloudcommon/policy/policy.go b/pkg/cloudcommon/policy/policy.go index 623b2a7ee0..7517dfa087 100644 --- a/pkg/cloudcommon/policy/policy.go +++ b/pkg/cloudcommon/policy/policy.go @@ -30,8 +30,37 @@ const ( var ( PolicyManager *SPolicyManager - PolicyFailedRetryInterval = 15 * time.Second - PolicyRefreshInterval = 15 * time.Minute + defaultRules = []rbacutils.SRbacRule{ + { + Resource: "tasks", + Action: PolicyActionPerform, + Result: rbacutils.UserAllow, + }, + { + Service: "compute", + Resource: "zones", + Action: PolicyActionList, + Result: rbacutils.UserAllow, + }, + { + Service: "compute", + Resource: "zones", + Action: PolicyActionGet, + Result: rbacutils.UserAllow, + }, + { + Service: "compute", + Resource: "cloudregions", + Action: PolicyActionList, + Result: rbacutils.UserAllow, + }, + { + Service: "compute", + Resource: "cloudregions", + Action: PolicyActionGet, + Result: rbacutils.UserAllow, + }, + } ) func init() { @@ -41,7 +70,11 @@ func init() { type SPolicyManager struct { policies map[string]rbacutils.SRbacPolicy adminPolicies map[string]rbacutils.SRbacPolicy + defaultPolicy *rbacutils.SRbacPolicy lastSync time.Time + + failedRetryInterval time.Duration + refreshInterval time.Duration } func parseJsonPolicy(obj jsonutils.JSONObject) (string, rbacutils.SRbacPolicy, error) { @@ -114,8 +147,13 @@ func fetchPolicies() (map[string]rbacutils.SRbacPolicy, map[string]rbacutils.SRb func (manager *SPolicyManager) start(refreshInterval time.Duration, retryInterval time.Duration) { log.Infof("PolicyManager start to fetch policies ...") - PolicyRefreshInterval = refreshInterval - PolicyFailedRetryInterval = retryInterval + manager.refreshInterval = refreshInterval + manager.failedRetryInterval = retryInterval + if len(defaultRules) > 0 { + manager.defaultPolicy = &rbacutils.SRbacPolicy{ + Rules: rbacutils.CompactRules(defaultRules), + } + } manager.sync() } @@ -124,13 +162,14 @@ func (manager *SPolicyManager) sync() { policies, adminPolicies, err := fetchPolicies() if err != nil { log.Errorf("sync policy fail %s", err) - time.AfterFunc(PolicyFailedRetryInterval, manager.sync) + time.AfterFunc(manager.failedRetryInterval, manager.sync) return } manager.policies = policies manager.adminPolicies = adminPolicies + manager.lastSync = time.Now() - time.AfterFunc(PolicyRefreshInterval, manager.sync) + time.AfterFunc(manager.refreshInterval, manager.sync) } func (manager *SPolicyManager) Allow(isAdmin bool, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { @@ -148,7 +187,13 @@ func (manager *SPolicyManager) Allow(isAdmin bool, userCred mcclient.TokenCreden currentPriv := rbacutils.Deny for _, p := range policies { result := p.Allow(userCredJson, service, resource, action, extra...) - if result.IsHigherPrivilege(currentPriv) { + if currentPriv.StricterThan(result) { + currentPriv = result + } + } + if manager.defaultPolicy != nil { + result := manager.defaultPolicy.Allow(userCredJson, service, resource, action, extra...) + if currentPriv.StricterThan(result) { currentPriv = result } } @@ -165,8 +210,10 @@ func (manager *SPolicyManager) explainPolicy(userCred mcclient.TokenCredential, } isAdmin, _ := policySeq[0].Bool() if !consts.IsRbacEnabled() { - if !isAdmin || (isAdmin && userCred.IsSystemAdmin()) { - return rbacutils.Allow, nil + if !isAdmin { + return rbacutils.OwnerAllow, nil + } else if isAdmin && userCred.IsSystemAdmin() { + return rbacutils.AdminAllow, nil } else { return rbacutils.Deny, httperrors.NewForbiddenError("operation not allowed") } @@ -210,6 +257,10 @@ func (manager *SPolicyManager) ExplainRpc(userCred mcclient.TokenCredential, par } func (manager *SPolicyManager) IsAdminCapable(userCred mcclient.TokenCredential) bool { + if !consts.IsRbacEnabled() && userCred.IsSystemAdmin() { + return true + } + userCredJson := userCred.ToJson() for _, p := range manager.adminPolicies { match, _ := conditionparser.Eval(p.Condition, userCredJson) diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index f0f6b428ab..c5c8ccde7c 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -251,12 +251,10 @@ type ICloudDisk interface { type ICloudSnapshot interface { ICloudResource - GetManagerId() string GetSize() int32 GetDiskId() string GetDiskType() string Delete() error - GetRegionId() string } type ICloudVpc interface { diff --git a/pkg/compute/guestdrivers/aliyun.go b/pkg/compute/guestdrivers/aliyun.go index 8c6f694524..ae607e644e 100644 --- a/pkg/compute/guestdrivers/aliyun.go +++ b/pkg/compute/guestdrivers/aliyun.go @@ -504,30 +504,3 @@ func (self *SAliyunGuestDriver) OnGuestDeployTaskDataReceived(ctx context.Contex func (self *SAliyunGuestDriver) AllowReconfigGuest() bool { return true } - -func (self *SAliyunGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, snapshotId, diskId string) error { - iDisk, _ := models.DiskManager.FetchById(diskId) - disk := iDisk.(*models.SDisk) - providerDisk, err := disk.GetIDisk() - if err != nil { - return err - } - iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) - snapshot := iSnapshot.(*models.SSnapshot) - taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - cloudSnapshot, err := providerDisk.CreateISnapshot(snapshot.Name, "") - if err != nil { - return nil, err - } - res := jsonutils.NewDict() - res.Set("snapshot_id", jsonutils.NewString(cloudSnapshot.GetId())) - res.Set("manager_id", jsonutils.NewString(cloudSnapshot.GetManagerId())) - cloudRegion, err := models.CloudregionManager.FetchByExternalId("Aliyun/" + cloudSnapshot.GetRegionId()) - if err != nil { - return nil, fmt.Errorf("Cloud region not found? %s", err) - } - res.Set("cloudregion_id", jsonutils.NewString(cloudRegion.GetId())) - return res, nil - }) - return nil -} diff --git a/pkg/compute/guestdrivers/aws.go b/pkg/compute/guestdrivers/aws.go index 76449f0b64..ff6bb28987 100644 --- a/pkg/compute/guestdrivers/aws.go +++ b/pkg/compute/guestdrivers/aws.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "yunion.io/x/onecloud/pkg/util/ansible" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -158,7 +160,7 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, DEFAULT_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) return data, nil }) case "rebuild": @@ -210,7 +212,7 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest } } - data := fetchIVMinfo(desc, iVM, guest.Id, DEFAULT_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) return data, nil }) @@ -288,29 +290,6 @@ func (self *SAwsGuestDriver) OnGuestDeployTaskDataReceived(ctx context.Context, return nil } -func (self *SAwsGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, snapshotId, diskId string) error { - iDisk, _ := models.DiskManager.FetchById(diskId) - disk := iDisk.(*models.SDisk) - providerDisk, err := disk.GetIDisk() - if err != nil { - return err - } - iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) - snapshot := iSnapshot.(*models.SSnapshot) - taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - cloudSnapshot, err := providerDisk.CreateISnapshot(snapshot.Name, "") - if err != nil { - return nil, err - } - res := jsonutils.NewDict() - res.Set("snapshot_id", jsonutils.NewString(cloudSnapshot.GetId())) - res.Set("manager_id", jsonutils.NewString(cloudSnapshot.GetManagerId())) - res.Set("cloudregion_id", jsonutils.NewString(cloudSnapshot.GetRegionId())) - return res, nil - }) - return nil -} - func init() { driver := SAwsGuestDriver{} models.RegisterGuestDriver(&driver) diff --git a/pkg/compute/guestdrivers/azure.go b/pkg/compute/guestdrivers/azure.go index 139dbad0e5..6afc543997 100644 --- a/pkg/compute/guestdrivers/azure.go +++ b/pkg/compute/guestdrivers/azure.go @@ -9,6 +9,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/ansible" "yunion.io/x/onecloud/pkg/util/seclib2" "yunion.io/x/pkg/utils" @@ -22,10 +23,6 @@ type SAzureGuestDriver struct { SManagedVirtualizedGuestDriver } -const ( - DEFAULT_USER = "yunion" -) - func init() { driver := SAzureGuestDriver{} models.RegisterGuestDriver(&driver) @@ -169,7 +166,7 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, DEFAULT_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) return data, nil } }) @@ -192,7 +189,7 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue if err != nil { return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, DEFAULT_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) return data, nil }) } else if action == "rebuild" { @@ -209,7 +206,7 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue } log.Debugf("VMrebuildRoot %s, and status is ready", iVM.GetGlobalId()) - data := fetchIVMinfo(desc, iVM, guest.Id, DEFAULT_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) return data, nil }) diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index 1274329af4..1cb9d0d586 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -49,29 +49,29 @@ func (self *SKVMGuestDriver) DoGuestCreateDisksTask(ctx context.Context, guest * } func (self *SKVMGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, snapshotId, diskId string) error { - url := fmt.Sprintf("/servers/%s/snapshot", guest.Id) + host := guest.GetHost() + url := fmt.Sprintf("%s/servers/%s/snapshot", host.ManagerUri, guest.Id) body := jsonutils.NewDict() body.Set("disk_id", jsonutils.NewString(diskId)) body.Set("snapshot_id", jsonutils.NewString(snapshotId)) header := self.getTaskRequestHeader(task) - host := guest.GetHost() - _, err := host.Request(task.GetUserCred(), "POST", url, header, body) + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) return err } func (self *SKVMGuestDriver) RequestDeleteSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, params *jsonutils.JSONDict) error { - url := fmt.Sprintf("/servers/%s/delete-snapshot", guest.Id) - header := self.getTaskRequestHeader(task) host := guest.GetHost() - _, err := host.Request(task.GetUserCred(), "POST", url, header, params) + url := fmt.Sprintf("%s/servers/%s/delete-snapshot", host.ManagerUri, guest.Id) + header := self.getTaskRequestHeader(task) + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, params, false) return err } func (self *SKVMGuestDriver) RequestReloadDiskSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, params *jsonutils.JSONDict) error { - url := fmt.Sprintf("/servers/%s/reload-disk-snapshot", guest.Id) - header := self.getTaskRequestHeader(task) host := guest.GetHost() - _, err := host.Request(task.GetUserCred(), "POST", url, header, params) + url := fmt.Sprintf("%s/servers/%s/reload-disk-snapshot", host.ManagerUri, guest.Id) + header := self.getTaskRequestHeader(task) + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, params, false) return err } @@ -294,9 +294,9 @@ func (self *SKVMGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest if fw_only, _ := task.GetParams().Bool("fw_only"); fw_only { body.Add(jsonutils.JSONTrue, "fw_only") } - url := fmt.Sprintf("/servers/%s/sync", guest.Id) + url := fmt.Sprintf("%s/servers/%s/sync", host.ManagerUri, guest.Id) header := self.getTaskRequestHeader(task) - _, err := host.Request(task.GetUserCred(), "POST", url, header, body) + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) return err } diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 0a8388410f..30dbc10618 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -351,3 +351,24 @@ func (self *SManagedVirtualizedGuestDriver) RequestSyncConfigOnHost(ctx context. }) return nil } + +func (self *SManagedVirtualizedGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, snapshotId, diskId string) error { + iDisk, _ := models.DiskManager.FetchById(diskId) + disk := iDisk.(*models.SDisk) + providerDisk, err := disk.GetIDisk() + if err != nil { + return err + } + iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) + snapshot := iSnapshot.(*models.SSnapshot) + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + cloudSnapshot, err := providerDisk.CreateISnapshot(snapshot.Name, "") + if err != nil { + return nil, err + } + res := jsonutils.NewDict() + res.Set("snapshot_id", jsonutils.NewString(cloudSnapshot.GetId())) + return res, nil + }) + return nil +} diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index 9c523c085c..cdcb19335f 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -345,30 +345,3 @@ func (self *SQcloudGuestDriver) OnGuestDeployTaskDataReceived(ctx context.Contex func (self *SQcloudGuestDriver) AllowReconfigGuest() bool { return true } - -func (self *SQcloudGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *models.SGuest, task taskman.ITask, snapshotId, diskId string) error { - iDisk, _ := models.DiskManager.FetchById(diskId) - disk := iDisk.(*models.SDisk) - providerDisk, err := disk.GetIDisk() - if err != nil { - return err - } - iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) - snapshot := iSnapshot.(*models.SSnapshot) - taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - cloudSnapshot, err := providerDisk.CreateISnapshot(snapshot.Name, "") - if err != nil { - return nil, err - } - res := jsonutils.NewDict() - res.Set("snapshot_id", jsonutils.NewString(cloudSnapshot.GetId())) - res.Set("manager_id", jsonutils.NewString(cloudSnapshot.GetManagerId())) - cloudRegion, err := models.CloudregionManager.FetchByExternalId("Aliyun/" + cloudSnapshot.GetRegionId()) - if err != nil { - return nil, fmt.Errorf("Cloud region not found? %s", err) - } - res.Set("cloudregion_id", jsonutils.NewString(cloudRegion.GetId())) - return res, nil - }) - return nil -} diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 4441e2a808..32b4762ff5 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -147,7 +147,7 @@ func (manager *SCloudaccountManager) ValidateCreateData(ctx context.Context, use if err == cloudprovider.ErrNoSuchProvder { return nil, httperrors.NewResourceNotFoundError("no such provider %s", provider) } - return nil, httperrors.NewInvalidCredentialError("invalid cloud account info") + return nil, httperrors.NewInputParameterError("invalid cloud account info") } return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) @@ -386,6 +386,7 @@ func (self *SCloudaccount) ImportSubAccount(ctx context.Context, userCred mcclie newCloudprovider.CloudaccountId = self.Id newCloudprovider.Provider = self.Provider newCloudprovider.Enabled = true + newCloudprovider.Status = CLOUD_PROVIDER_CONNECTED newCloudprovider.Name = subAccount.Name if !autoCreateProject { newCloudprovider.ProjectId = auth.AdminCredential().GetProjectId() diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 13fad325ae..c0a992900b 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1101,6 +1101,7 @@ type DiskInfo struct { MediumType string Driver string Cache string + DiskType string } func (self *SDisk) ToDiskInfo() DiskInfo { @@ -1110,6 +1111,7 @@ func (self *SDisk) ToDiskInfo() DiskInfo { MountPoint: self.GetMountPoint(), Format: self.DiskFormat, Size: int64(self.DiskSize), + DiskType: self.DiskType, } storage := self.GetStorage() if storage == nil { diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 208e5a5ce3..2ec5b33ec2 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -3915,6 +3915,7 @@ func (self *SGuest) GetSpec(checkStatus bool) *jsonutils.JSONDict { diskSpec.Set("size", jsonutils.NewInt(info.Size)) diskSpec.Set("backend", jsonutils.NewString(info.Backend)) diskSpec.Set("medium_type", jsonutils.NewString(info.MediumType)) + diskSpec.Set("disk_type", jsonutils.NewString(info.DiskType)) diskSpecs.Add(diskSpec) } spec.Set("disk", diskSpecs) diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 5a52ab1505..79028ba701 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -958,7 +958,7 @@ func (manager *SHostManager) getHostsByZone(zone *SZone, provider *SCloudprovide return hosts, nil } -func (manager *SHostManager) SyncHosts(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, zone *SZone, hosts []cloudprovider.ICloudHost) ([]SHost, []cloudprovider.ICloudHost, compare.SyncResult) { +func (manager *SHostManager) SyncHosts(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, zone *SZone, hosts []cloudprovider.ICloudHost, projectSync bool) ([]SHost, []cloudprovider.ICloudHost, compare.SyncResult) { localHosts := make([]SHost, 0) remoteHosts := make([]cloudprovider.ICloudHost, 0) syncResult := compare.SyncResult{} @@ -1002,7 +1002,7 @@ func (manager *SHostManager) SyncHosts(ctx context.Context, userCred mcclient.To } } for i := 0; i < len(commondb); i += 1 { - err = commondb[i].syncWithCloudHost(commonext[i]) + err = commondb[i].syncWithCloudHost(commonext[i], projectSync) if err != nil { syncResult.UpdateError(err) } else { @@ -1025,7 +1025,7 @@ func (manager *SHostManager) SyncHosts(ctx context.Context, userCred mcclient.To return localHosts, remoteHosts, syncResult } -func (self *SHost) syncWithCloudHost(extHost cloudprovider.ICloudHost) error { +func (self *SHost) syncWithCloudHost(extHost cloudprovider.ICloudHost, projectSync bool) error { _, err := self.GetModelManager().TableSpec().Update(self, func() error { self.Name = extHost.GetName() self.Status = extHost.GetStatus() @@ -1052,6 +1052,13 @@ func (self *SHost) syncWithCloudHost(extHost cloudprovider.ICloudHost) error { if err != nil { log.Errorf("syncWithCloudZone error %s", err) } + + if projectSync { + if err := HostManager.ClearSchedDescCache(self.Id); err != nil { + log.Errorf("ClearSchedDescCache for host %s error %v", self.Name, err) + } + } + return err } @@ -1089,6 +1096,11 @@ func (manager *SHostManager) newFromCloudHost(extHost cloudprovider.ICloudHost, log.Errorf("newFromCloudHost fail %s", err) return nil, err } + + if err := manager.ClearSchedDescCache(host.Id); err != nil { + log.Errorf("ClearSchedDescCache for host %s error %v", host.Name, err) + } + return &host, nil } diff --git a/pkg/compute/models/loadbalancerlistenerrules.go b/pkg/compute/models/loadbalancerlistenerrules.go index e4f5455e0c..71be2e4b89 100644 --- a/pkg/compute/models/loadbalancerlistenerrules.go +++ b/pkg/compute/models/loadbalancerlistenerrules.go @@ -43,6 +43,7 @@ type SLoadbalancerListenerRule struct { func loadbalancerListenerRuleCheckUniqueness(ctx context.Context, lbls *SLoadbalancerListener, domain, path string) error { q := LoadbalancerListenerRuleManager.Query(). + IsFalse("pending_deleted"). Equals("listener_id", lbls.Id). Equals("domain", domain). Equals("path", path) diff --git a/pkg/compute/models/loadbalancerlisteners.go b/pkg/compute/models/loadbalancerlisteners.go index 137f594698..7ce180e9c1 100644 --- a/pkg/compute/models/loadbalancerlisteners.go +++ b/pkg/compute/models/loadbalancerlisteners.go @@ -104,6 +104,7 @@ type SLoadbalancerListener struct { func (man *SLoadbalancerListenerManager) checkListenerUniqueness(ctx context.Context, lb *SLoadbalancer, listenerType string, listenerPort int64) error { q := man.Query(). + IsFalse("pending_deleted"). Equals("loadbalancer_id", lb.Id). Equals("listener_port", listenerPort) switch listenerType { @@ -260,16 +261,8 @@ func (man *SLoadbalancerListenerManager) ValidateCreateData(ctx context.Context, } } } - { - if aclStatusV.Value == LB_BOOL_ON { - acl := aclV.Model.(*SLoadbalancerAcl) - if acl == nil { - return nil, fmt.Errorf("missing acl") - } - if len(aclTypeV.Value) == 0 { - return nil, fmt.Errorf("missing acl_type") - } - } + if err := man.validateAcl(aclStatusV, aclTypeV, aclV, data); err != nil { + return nil, err } return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) } @@ -287,6 +280,20 @@ func (man *SLoadbalancerListenerManager) checkTypeV(listenerType string) validat return nil } +func (man *SLoadbalancerListenerManager) validateAcl(aclStatusV *validators.ValidatorStringChoices, aclTypeV *validators.ValidatorStringChoices, aclV *validators.ValidatorModelIdOrName, data *jsonutils.JSONDict) error { + if aclStatusV.Value == LB_BOOL_ON { + if aclV.Model == nil { + return httperrors.NewInputParameterError("missing acl") + } + if len(aclTypeV.Value) == 0 { + return httperrors.NewInputParameterError("missing acl_type") + } + } else { + data.Set("acl_id", jsonutils.NewString("")) + } + return nil +} + func (lblis *SLoadbalancerListener) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return lblis.IsOwner(userCred) || userCred.IsSystemAdmin() } @@ -295,7 +302,11 @@ func (lblis *SLoadbalancerListener) ValidateUpdateData(ctx context.Context, user ownerProjId := lblis.GetOwnerProjectId() backendGroupV := validators.NewModelIdOrNameValidator("backend_group", "loadbalancerbackendgroup", ownerProjId) aclStatusV := validators.NewStringChoicesValidator("acl_status", LB_BOOL_VALUES) + aclStatusV.Default(lblis.AclStatus) aclTypeV := validators.NewStringChoicesValidator("acl_type", LB_ACL_TYPES) + if LB_ACL_TYPES.Has(lblis.AclType) { + aclTypeV.Default(lblis.AclType) + } aclV := validators.NewModelIdOrNameValidator("acl", "loadbalanceracl", ownerProjId) certV := validators.NewModelIdOrNameValidator("certificate", "loadbalancercertificate", ownerProjId) tlsCipherPolicyV := validators.NewStringChoicesValidator("tls_cipher_policy", LB_TLS_CIPHER_POLICIES).Default(LB_TLS_CIPHER_POLICY_1_2) @@ -344,6 +355,9 @@ func (lblis *SLoadbalancerListener) ValidateUpdateData(ctx context.Context, user return nil, err } } + if err := LoadbalancerListenerManager.validateAcl(aclStatusV, aclTypeV, aclV, data); err != nil { + return nil, err + } { if backendGroup, ok := backendGroupV.Model.(*SLoadbalancerBackendGroup); ok && backendGroup.LoadbalancerId != lblis.LoadbalancerId { return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 62737711e5..c1ddf8b93d 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -145,7 +145,12 @@ func (self *SNetwork) ValidateDeleteCondition(ctx context.Context) error { } func (self *SNetwork) GetTotalNicCount() int { - return self.GetGuestnicsCount() + self.GetGroupNicsCount() + self.GetBaremetalNicsCount() + self.GetReservedNicsCount() + total := self.GetGuestnicsCount() + + self.GetGroupNicsCount() + + self.GetBaremetalNicsCount() + + self.GetReservedNicsCount() + + self.GetLoadbalancerIpsCount() + return total } func (self *SNetwork) GetGuestnicsCount() int { @@ -164,6 +169,10 @@ func (self *SNetwork) GetReservedNicsCount() int { return ReservedipManager.Query().Equals("network_id", self.Id).Count() } +func (self *SNetwork) GetLoadbalancerIpsCount() int { + return LoadbalancernetworkManager.Query().Equals("network_id", self.Id).Count() +} + func (self *SNetwork) GetUsedAddresses() map[string]bool { used := make(map[string]bool) @@ -818,6 +827,7 @@ func (self *SNetwork) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSOND extra.Add(jsonutils.JSONFalse, "exit") } extra.Add(jsonutils.NewInt(int64(self.getIPRange().AddressCount())), "ports") + extra.Add(jsonutils.NewInt(int64(self.GetTotalNicCount())), "ports_used") extra.Add(jsonutils.NewInt(int64(self.GetGuestnicsCount())), "vnics") extra.Add(jsonutils.NewInt(int64(self.GetBaremetalNicsCount())), "bm_vnics") extra.Add(jsonutils.NewInt(int64(self.GetGroupNicsCount())), "group_vnics") diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index a525d18a9c..e78a91bd1b 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -300,6 +300,7 @@ func (self *SSnapshotManager) CreateSnapshot(ctx context.Context, userCred mccli return nil, err } disk := iDisk.(*SDisk) + storage := disk.GetStorage() snapshot := &SSnapshot{} snapshot.SetModelManager(self) snapshot.ProjectId = userCred.GetProjectId() @@ -311,6 +312,8 @@ func (self *SSnapshotManager) CreateSnapshot(ctx context.Context, userCred mccli snapshot.DiskType = disk.DiskType snapshot.Location = location snapshot.CreatedBy = createdBy + snapshot.ManagerId = storage.ManagerId + snapshot.CloudregionId = storage.getZone().GetRegion().GetId() snapshot.Name = name snapshot.Status = SNAPSHOT_CREATING err = SnapshotManager.TableSpec().Insert(snapshot) @@ -467,13 +470,15 @@ func totalSnapshotCount(projectId string) int { } // Only sync snapshot status -func (self *SSnapshot) SyncWithCloudSnapshot(userCred mcclient.TokenCredential, ext cloudprovider.ICloudSnapshot, projectId string, projectSync bool) error { +func (self *SSnapshot) SyncWithCloudSnapshot(userCred mcclient.TokenCredential, ext cloudprovider.ICloudSnapshot, projectId string, projectSync bool, region *SCloudregion) error { _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.Name = ext.GetName() self.Status = ext.GetStatus() self.DiskType = ext.GetDiskType() if projectSync && len(projectId) > 0 { self.ProjectId = projectId } + self.CloudregionId = region.Id return nil }) if err != nil { @@ -482,7 +487,7 @@ func (self *SSnapshot) SyncWithCloudSnapshot(userCred mcclient.TokenCredential, return err } -func (manager *SSnapshotManager) newFromCloudSnapshot(userCred mcclient.TokenCredential, extSnapshot cloudprovider.ICloudSnapshot, region *SCloudregion, projectId string) (*SSnapshot, error) { +func (manager *SSnapshotManager) newFromCloudSnapshot(userCred mcclient.TokenCredential, extSnapshot cloudprovider.ICloudSnapshot, region *SCloudregion, projectId string, provider *SCloudprovider) (*SSnapshot, error) { snapshot := SSnapshot{} snapshot.SetModelManager(manager) @@ -500,7 +505,7 @@ func (manager *SSnapshotManager) newFromCloudSnapshot(userCred mcclient.TokenCre snapshot.DiskType = extSnapshot.GetDiskType() snapshot.Size = int(extSnapshot.GetSize()) * 1024 - snapshot.ManagerId = extSnapshot.GetManagerId() + snapshot.ManagerId = provider.Id snapshot.CloudregionId = region.Id snapshot.ProjectId = userCred.GetProjectId() @@ -555,7 +560,7 @@ func (manager *SSnapshotManager) SyncSnapshots(ctx context.Context, userCred mcc } } for i := 0; i < len(commondb); i += 1 { - err = commondb[i].SyncWithCloudSnapshot(userCred, commonext[i], projectId, projectSync) + err = commondb[i].SyncWithCloudSnapshot(userCred, commonext[i], projectId, projectSync, region) if err != nil { syncResult.UpdateError(err) } else { @@ -563,7 +568,7 @@ func (manager *SSnapshotManager) SyncSnapshots(ctx context.Context, userCred mcc } } for i := 0; i < len(added); i += 1 { - _, err := manager.newFromCloudSnapshot(userCred, added[i], region, projectId) + _, err := manager.newFromCloudSnapshot(userCred, added[i], region, projectId, provider) if err != nil { syncResult.AddError(err) } else { diff --git a/pkg/compute/models/usage.go b/pkg/compute/models/usage.go index e45bce93de..d3bc2be701 100644 --- a/pkg/compute/models/usage.go +++ b/pkg/compute/models/usage.go @@ -37,7 +37,7 @@ func AttachUsageQuery( case "vcenter": q = q.Filter(sqlchemy.Equals(hosts.Field("manager_id"), rangeObjId)) case "schedtag": - aggHosts := SchedtagManager.Query().SubQuery() + aggHosts := HostschedtagManager.Query().SubQuery() q = q.Join(aggHosts, sqlchemy.AND( sqlchemy.Equals(hosts.Field("id"), aggHosts.Field("host_id")), sqlchemy.IsFalse(aggHosts.Field("deleted")))). diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 27b38f3545..20b3eaad36 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -353,10 +353,6 @@ func (manager *SVpcManager) ValidateCreateData(ctx context.Context, userCred mcc return nil, httperrors.NewInputParameterError("Invalid cloudregion_id") } if region.isManaged() { - if region.GetVpcCount() >= MAX_VPC_PER_REGION { - return nil, httperrors.NewNotAcceptableError("Too many vpcs per region") - } - managerStr, _ := data.GetString("manager_id") if len(managerStr) == 0 { managerStr, _ = data.GetString("manager") diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index 18e75d4d03..85a3d60bb3 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -343,7 +343,7 @@ func syncZoneHosts(ctx context.Context, provider *models.SCloudprovider, task *C logSyncFailed(provider, task, msg) return } - localHosts, remoteHosts, result := models.HostManager.SyncHosts(ctx, task.UserCred, provider, localZone, hosts) + localHosts, remoteHosts, result := models.HostManager.SyncHosts(ctx, task.UserCred, provider, localZone, hosts, syncRange.ProjectSync) msg := result.Result() notes := fmt.Sprintf("SyncHosts for zone %s result: %s", localZone.Name, msg) log.Infof(notes) diff --git a/pkg/compute/tasks/guest_disk_snapshot_task.go b/pkg/compute/tasks/guest_disk_snapshot_task.go index bd651f909b..2c61be664e 100644 --- a/pkg/compute/tasks/guest_disk_snapshot_task.go +++ b/pkg/compute/tasks/guest_disk_snapshot_task.go @@ -52,32 +52,25 @@ func (self *GuestDiskSnapshotTask) DoDiskSnapshot(ctx context.Context, guest *mo func (self *GuestDiskSnapshotTask) OnDiskSnapshotComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { res := data.(*jsonutils.JSONDict) + snapshotId, _ := self.Params.GetString("snapshot_id") + iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) + snapshot := iSnapshot.(*models.SSnapshot) if guest.Hypervisor == models.HYPERVISOR_KVM { location, err := res.GetString("location") if err != nil { log.Infof("OnDiskSnapshotComplete called with data no location") return } - snapshotId, _ := self.Params.GetString("snapshot_id") - iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) - snapshot := iSnapshot.(*models.SSnapshot) models.SnapshotManager.TableSpec().Update(snapshot, func() error { snapshot.Location = location snapshot.Status = models.SNAPSHOT_READY return nil }) } else { - snapshotId, _ := self.Params.GetString("snapshot_id") - iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) - snapshot := iSnapshot.(*models.SSnapshot) extSnapshotId, _ := data.GetString("snapshot_id") - cloudregionId, _ := data.GetString("cloudregion_id") - managerId, _ := data.GetString("manager_id") models.SnapshotManager.TableSpec().Update(snapshot, func() error { - snapshot.CloudregionId = cloudregionId snapshot.ExternalId = extSnapshotId snapshot.Status = models.SNAPSHOT_READY - snapshot.ManagerId = managerId return nil }) } diff --git a/pkg/compute/tasks/guest_start_task.go b/pkg/compute/tasks/guest_start_task.go index 99dae67cf3..2eb1ca3db7 100644 --- a/pkg/compute/tasks/guest_start_task.go +++ b/pkg/compute/tasks/guest_start_task.go @@ -48,8 +48,9 @@ func (self *GuestStartTask) startStart(ctx context.Context, guest *models.SGuest self.onStartGuestFailed(ctx, guest, err) } else { if result != nil && jsonutils.QueryBoolean(result, "is_running", false) { - guest.SetStatus(self.UserCred, models.VM_RUNNING, "start") - self.taskComplete(ctx, guest) + self.OnStartComplete(ctx, guest, nil) + // guest.SetStatus(self.UserCred, models.VM_RUNNING, "start") + // self.taskComplete(ctx, guest) } } } diff --git a/pkg/compute/usages/handler.go b/pkg/compute/usages/handler.go index c8b5797364..a1e9a57b9e 100644 --- a/pkg/compute/usages/handler.go +++ b/pkg/compute/usages/handler.go @@ -272,7 +272,7 @@ func ReportGeneralUsage(userCred mcclient.TokenCredential, rangeObj db.IStandalo if consts.IsRbacEnabled() { if policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(), - "usages", policy.PolicyActionGet) == rbacutils.Allow { + "usages", policy.PolicyActionGet) == rbacutils.AdminAllow { isAdmin = true } } else { diff --git a/pkg/dns/dns.go b/pkg/dns/dns.go index 44382c02eb..7d26da73a2 100644 --- a/pkg/dns/dns.go +++ b/pkg/dns/dns.go @@ -7,7 +7,6 @@ import ( "fmt" "strconv" "strings" - "sync" "time" "github.com/coredns/coredns/plugin" @@ -26,7 +25,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" - "yunion.io/x/jsonutils" ylog "yunion.io/x/log" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -35,7 +33,6 @@ import ( "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" - kubeserver "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" "yunion.io/x/onecloud/pkg/util/k8s" ) @@ -74,14 +71,11 @@ type SRegionDNS struct { AdminUser string AdminPassword string Region string - k8sConfigLock *sync.RWMutex - k8sConfig string + K8sManager *k8s.SKubeClusterManager } func New() *SRegionDNS { - r := &SRegionDNS{ - k8sConfigLock: new(sync.RWMutex), - } + r := &SRegionDNS{} return r } @@ -106,6 +100,12 @@ func (r *SRegionDNS) initDB(c *caddy.Controller) error { return nil } +func (r *SRegionDNS) initK8s() { + r.initAuth() + r.K8sManager = k8s.NewKubeClusterManager(r.Region, 30*time.Second) + r.K8sManager.Start() +} + func (r *SRegionDNS) getAdminSession() *mcclient.ClientSession { return auth.GetAdminSession(r.Region, "") } @@ -115,68 +115,6 @@ func (r *SRegionDNS) initAuth() { auth.Init(authInfo, false, true, "", "") } -func (r *SRegionDNS) getKubeClusterConfig() (string, error) { - session := r.getAdminSession() - params := jsonutils.NewDict() - params.Add(jsonutils.JSONTrue, "directly") - ret, err := kubeserver.Clusters.PerformAction(session, "default", "generate-kubeconfig", params) - if err != nil { - return "", err - } - return ret.GetString("kubeconfig") -} - -func (r *SRegionDNS) getK8sConfig() string { - r.k8sConfigLock.RLock() - defer r.k8sConfigLock.RUnlock() - return r.k8sConfig -} - -func (r *SRegionDNS) setK8sConfig(conf string) { - r.k8sConfigLock.Lock() - defer r.k8sConfigLock.Unlock() - r.k8sConfig = conf -} - -func (r *SRegionDNS) isK8sHealthy() bool { - if r.getK8sConfig() == "" { - return false - } - cli, err := r.getK8sClient() - if err != nil { - return false - } - _, err = cli.Discovery().ServerVersion() - if err != nil { - ylog.Errorf("Discovery k8s version: %v", err) - return false - } - return true -} - -func (r *SRegionDNS) startRefreshKubeConfig() { - r.initAuth() - r.refreshKubeConfig() - tick := time.Tick(30 * time.Second) - for { - select { - case <-tick: - r.refreshKubeConfig() - } - } -} - -func (r *SRegionDNS) refreshKubeConfig() { - if r.isK8sHealthy() { - return - } - kubeConfig, err := r.getKubeClusterConfig() - if err != nil { - ylog.Errorf("Get default k8s config from kube server error: %v", err) - } - r.setK8sConfig(kubeConfig) -} - func (r *SRegionDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, rmsg *dns.Msg) (int, error) { var ( records []dns.RR @@ -341,12 +279,7 @@ func getK8sServiceBackends(cli *kubernetes.Clientset, req *recordRequest) ([]str } func (r *SRegionDNS) getK8sClient() (*kubernetes.Clientset, error) { - cli, err := k8s.NewClientByContent([]byte(r.getK8sConfig()), nil) - if err != nil { - ylog.Warningf("Init kubernetes client error: %v", err) - return nil, err - } - return cli, nil + return r.K8sManager.GetK8sClient() } func getK8sServicePods(cli *kubernetes.Clientset, namespace, name string) ([]v1.Pod, error) { diff --git a/pkg/dns/setup.go b/pkg/dns/setup.go index f753d80724..94613fa2c2 100644 --- a/pkg/dns/setup.go +++ b/pkg/dns/setup.go @@ -36,7 +36,7 @@ func setup(c *caddy.Controller) error { return plugin.Error(PluginName, err) } - go rDNS.startRefreshKubeConfig() + go rDNS.initK8s() dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { rDNS.Next = next diff --git a/pkg/mcclient/auth/middleware.go b/pkg/mcclient/auth/middleware.go index f779b132ff..fa14ac898c 100644 --- a/pkg/mcclient/auth/middleware.go +++ b/pkg/mcclient/auth/middleware.go @@ -17,25 +17,38 @@ const ( ) func Authenticate(f appsrv.FilterHandler) appsrv.FilterHandler { + return AuthenticateWithDelayDecision(f, false) +} + +func AuthenticateWithDelayDecision(f appsrv.FilterHandler, delayDecision bool) appsrv.FilterHandler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { tokenStr := r.Header.Get(mcclient.AUTH_TOKEN) if len(tokenStr) == 0 { - httperrors.UnauthorizedError(w, "Unauthorized") - return + log.Errorf("no auth_token found!") + if !delayDecision { + httperrors.UnauthorizedError(w, "Unauthorized") + return + } } token, err := Verify(tokenStr) if err != nil { log.Errorf("Verify token failed: %s", err) - httperrors.UnauthorizedError(w, "InvalidToken") - return + if !delayDecision { + httperrors.UnauthorizedError(w, "InvalidToken") + return + } } - ctx = context.WithValue(ctx, AUTH_TOKEN, token) + if token != nil { + ctx = context.WithValue(ctx, AUTH_TOKEN, token) + } + if taskId := r.Header.Get(mcclient.TASK_ID); taskId != "" { ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_TASK_ID, taskId) } if taskNotifyUrl := r.Header.Get(mcclient.TASK_NOTIFY_URL); taskNotifyUrl != "" { ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_TASK_NOTIFY_URL, taskNotifyUrl) } + f(ctx, w, r) } } diff --git a/pkg/mcclient/options/dnsrecords.go b/pkg/mcclient/options/dnsrecords.go new file mode 100644 index 0000000000..12796f8565 --- /dev/null +++ b/pkg/mcclient/options/dnsrecords.go @@ -0,0 +1,104 @@ +package options + +import ( + "fmt" + + "yunion.io/x/jsonutils" +) + +type DNSRecordOptions struct { + A []string `help:"DNS A record" metavar:"A_RECORD" positional:"false"` + AAAA []string `help:"DNS AAAA record" metavar:"AAAA_RECORD" positional:"false"` + CNAME string `help:"DNS CNAME record" metavar:"CNAME_RECORD" positional:"false"` + PTR string `help:"DNS PTR record" metavar:"PTR_RECORD" positional:"false"` + + SRVHost string `help:"(deprecated) DNS SRV record, server of service" metavar:"SRV_RECORD_HOST" positional:"false"` + SRVPort int64 `help:"(deprecated) DNS SRV record, port of service" metavar:"SRV_RECORD_PORT" positional:"false"` + SRV []string `help:"DNS SRV record, in the format of host:port:weight:priority" metavar:"SRV_RECORD" positional:"false"` +} + +func parseDNSRecords(opts *DNSRecordOptions, params *jsonutils.JSONDict) { + if len(opts.A) > 0 || len(opts.AAAA) > 0 { + for i, a := range opts.A { + params.Add(jsonutils.NewString(a), fmt.Sprintf("A.%d", i)) + } + for i, a := range opts.AAAA { + params.Add(jsonutils.NewString(a), fmt.Sprintf("AAAA.%d", i)) + } + } else if len(opts.CNAME) > 0 { + params.Add(jsonutils.NewString(opts.CNAME), "CNAME") + } else if len(opts.SRV) > 0 || (len(opts.SRVHost) > 0 && opts.SRVPort > 0) { + for i, s := range opts.SRV { + params.Set(fmt.Sprintf("SRV.%d", i), jsonutils.NewString(s)) + } + // Keep using the original argument passing method in case a + // newer climc is used against old service + if len(opts.SRVHost) > 0 && opts.SRVPort > 0 { + params.Set("SRV_host", jsonutils.NewString(opts.SRVHost)) + params.Set("SRV_port", jsonutils.NewInt(opts.SRVPort)) + } + } else if len(opts.PTR) > 0 { + params.Add(jsonutils.NewString(opts.PTR), "PTR") + } +} + +type DNSCreateOptions struct { + NAME string `help:"DNS name to create"` + TTL int64 `help:"TTL in seconds" positional:"false"` + Desc string `help:"Description" json:"description"` + IsPublic *bool `help:"Make the newly created record public to all"` + + DNSRecordOptions +} + +func (opts *DNSCreateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := StructToParams(opts) + if err != nil { + return nil, err + } + parseDNSRecords(&opts.DNSRecordOptions, params) + return params, nil +} + +type DNSUpdateOptions struct { + ID string `help:"ID of DNS record to update" json:"-"` + Name string `help:"Domain name"` + TTL int64 `help:"TTL in seconds" positional:"false"` + Desc string `help:"Description" json:"description"` + + DNSRecordOptions +} + +func (opts *DNSUpdateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := StructToParams(opts) + if err != nil { + return nil, err + } + parseDNSRecords(&opts.DNSRecordOptions, params) + return params, nil +} + +type DNSUpdateRecordsOptions struct { + ID string `help:"ID of dns record to modify" json:"-"` + + DNSRecordOptions +} + +func (opts *DNSUpdateRecordsOptions) Params() (*jsonutils.JSONDict, error) { + params := jsonutils.NewDict() + parseDNSRecords(&opts.DNSRecordOptions, params) + if params.Size() == 0 { + return nil, fmt.Errorf("Nothing to add") + } + return params, nil +} + +type DNSListOptions struct { + BaseListOptions + + IsPublic string `choices:"0|1"` +} + +type DNSGetOptions struct { + ID string `help:"ID of DNS record to show" json:"-"` +} diff --git a/pkg/scheduler/algorithm/predicates/k8s/host_status_predicate.go b/pkg/scheduler/algorithm/predicates/k8s/host_status_predicate.go index ee9b20b422..d7463902cf 100644 --- a/pkg/scheduler/algorithm/predicates/k8s/host_status_predicate.go +++ b/pkg/scheduler/algorithm/predicates/k8s/host_status_predicate.go @@ -4,6 +4,7 @@ import ( "fmt" "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" "yunion.io/x/onecloud/pkg/scheduler/algorithm/predicates/guest" "yunion.io/x/onecloud/pkg/scheduler/cache/candidate" @@ -19,11 +20,11 @@ func (p *HostStatusPredicate) Name() string { return "host_status" } -func (p *HostStatusPredicate) PreExecute(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool { +func (p *HostStatusPredicate) PreExecute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool { return true } -func (p *HostStatusPredicate) Execute(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) { +func (p *HostStatusPredicate) Execute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) { if host.Status != guest.ExpectedStatus { return false, fmt.Errorf("Host status is %s", host.Status) } diff --git a/pkg/scheduler/algorithm/predicates/k8s/k8s.go b/pkg/scheduler/algorithm/predicates/k8s/k8s.go index f3911e15ed..657c49d89e 100644 --- a/pkg/scheduler/algorithm/predicates/k8s/k8s.go +++ b/pkg/scheduler/algorithm/predicates/k8s/k8s.go @@ -4,6 +4,7 @@ import ( "fmt" "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" "yunion.io/x/onecloud/pkg/scheduler/cache/candidate" ) @@ -16,14 +17,15 @@ func init() { PredicatesManager.Register( &HostStatusPredicate{}, &NetworkPredicate{}, + &LocalVolumePredicate{}, ) } type IPredicate interface { Name() string Clone() IPredicate - PreExecute(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool - Execute(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) + PreExecute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool + Execute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) } type SPredicatesManager struct { @@ -58,13 +60,18 @@ func (man *SPredicatesManager) Has(newPre IPredicate) bool { return false } -func (man *SPredicatesManager) DoFilter(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) { +func (man *SPredicatesManager) DoFilter( + k8sCli *kubernetes.Clientset, + pod *v1.Pod, + node *v1.Node, + host *candidate.HostDesc, +) (bool, error) { for _, pre := range man.predicates { tmpPre := pre.Clone() - if !tmpPre.PreExecute(pod, node, host) { + if !tmpPre.PreExecute(k8sCli, pod, node, host) { continue } - fit, err := tmpPre.Execute(pod, node, host) + fit, err := tmpPre.Execute(k8sCli, pod, node, host) if err != nil { return false, err } diff --git a/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go b/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go index bd2562ad2f..46e9612138 100644 --- a/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go +++ b/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go @@ -4,6 +4,7 @@ import ( "fmt" "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" "yunion.io/x/pkg/util/errors" @@ -30,7 +31,7 @@ func (p *NetworkPredicate) Name() string { return "network" } -func (p *NetworkPredicate) PreExecute(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool { +func (p *NetworkPredicate) PreExecute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool { net, netCont := pod.Annotations[YUNION_CNI_NETWORK_ANNOTATION] ipAddr, ipCont := pod.Annotations[YUNION_CNI_IPADDR_ANNOTATION] p.network = net @@ -38,7 +39,7 @@ func (p *NetworkPredicate) PreExecute(pod *v1.Pod, node *v1.Node, host *candidat return netCont || ipCont } -func (p *NetworkPredicate) Execute(pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) { +func (p *NetworkPredicate) Execute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) { hostNets := host.Networks if p.network != "" { err := p.checkByNetworks(hostNets) diff --git a/pkg/scheduler/algorithm/predicates/k8s/volume_predicate.go b/pkg/scheduler/algorithm/predicates/k8s/volume_predicate.go new file mode 100644 index 0000000000..95fe32dc4e --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/k8s/volume_predicate.go @@ -0,0 +1,89 @@ +package k8s + +import ( + "fmt" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/scheduler/cache/candidate" +) + +const ( + YUNION_CSI_STORAGECLASS = "csi-yunion" +) + +type LocalVolumePredicate struct { + pvcs []*v1.PersistentVolumeClaim +} + +func (p *LocalVolumePredicate) Clone() IPredicate { + return &LocalVolumePredicate{} +} + +func (p *LocalVolumePredicate) Name() string { + return "local-volume" +} + +func (p *LocalVolumePredicate) PreExecute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) bool { + if cli == nil { + log.Errorf("k8s client is nil, not execute %s filter", p.Name()) + return false + } + pvcs := make([]*v1.PersistentVolumeClaim, 0) + if pod.Spec.Volumes != nil && len(pod.Spec.Volumes) > 0 { + for _, v := range pod.Spec.Volumes { + if v.PersistentVolumeClaim == nil { + continue + } + pvcName := v.PersistentVolumeClaim.ClaimName + pvc, err := cli.CoreV1().PersistentVolumeClaims(pod.Namespace).Get(pvcName, metav1.GetOptions{}) + if err != nil { + log.Warningf("Not found pvc %s for pod %s/%s", pvcName, pod.Namespace, pod.Name) + return false + } + // pvc's StorageClassName must "csi-yunion" + if pvc.Spec.StorageClassName != nil && *(pvc.Spec.StorageClassName) != YUNION_CSI_STORAGECLASS { + continue + } + pvcs = append(pvcs, pvc) + } + } + p.pvcs = pvcs + return len(pvcs) != 0 +} + +func (p *LocalVolumePredicate) getPvcRequestSize(pvc *v1.PersistentVolumeClaim) int64 { + req := pvc.Spec.Resources.Requests[v1.ResourceStorage] + return req.Value() +} + +func (p *LocalVolumePredicate) Execute(cli *kubernetes.Clientset, pod *v1.Pod, node *v1.Node, host *candidate.HostDesc) (bool, error) { + var reqSize int64 + for _, pvc := range p.pvcs { + pvName := pvc.Spec.VolumeName + if pvName != "" { + pv, _ := cli.CoreV1().PersistentVolumes().Get(pvName, metav1.GetOptions{}) + if pv != nil { + // PersistentVolume already exists + log.V(10).Debugf("PV %s already exists", pv) + continue + } + } + reqSize += p.getPvcRequestSize(pvc) + } + reqSizeMB := reqSize / 1024 / 1024 + return p.canHostStorageCreateVol(host, reqSizeMB) +} + +func (p *LocalVolumePredicate) canHostStorageCreateVol(host *candidate.HostDesc, reqSize int64) (bool, error) { + freeSize := host.GetFreeStorageSizeOfType("local", false) + log.Debugf("[host %s] PVC request %dMB, free %dMB", host.Name, reqSize, freeSize) + if freeSize > reqSize { + return true, nil + } + return false, fmt.Errorf("Out of local storage for volume: %d/%d (request/free)MB", reqSize, freeSize) +} diff --git a/pkg/scheduler/handler/k8s_extender.go b/pkg/scheduler/handler/k8s_extender.go index fecde8d795..4a659a7fcd 100644 --- a/pkg/scheduler/handler/k8s_extender.go +++ b/pkg/scheduler/handler/k8s_extender.go @@ -9,6 +9,8 @@ import ( "k8s.io/api/core/v1" schedulerapi "k8s.io/kubernetes/pkg/scheduler/api/v1" + "yunion.io/x/log" + k8spredicates "yunion.io/x/onecloud/pkg/scheduler/algorithm/predicates/k8s" schedman "yunion.io/x/onecloud/pkg/scheduler/manager" ) @@ -70,7 +72,11 @@ func doK8sPredicates(pod *v1.Pod, node *v1.Node) (bool, error) { if len(hosts) == 0 { return false, fmt.Errorf("Not found candidate host %s", node.Name) } - return k8spredicates.PredicatesManager.DoFilter(pod, node, hosts[0]) + k8sCli, err := schedman.GetK8sClient() + if err != nil { + log.Warningf("Get k8s client error: %v, some predicates will not execute", err) + } + return k8spredicates.PredicatesManager.DoFilter(k8sCli, pod, node, hosts[0]) } func k8sPrioritizeHandler(c *gin.Context) { diff --git a/pkg/scheduler/manager/manager.go b/pkg/scheduler/manager/manager.go index d442065434..5949af00eb 100644 --- a/pkg/scheduler/manager/manager.go +++ b/pkg/scheduler/manager/manager.go @@ -6,13 +6,18 @@ import ( "strings" "time" + "k8s.io/client-go/kubernetes" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/api" "yunion.io/x/onecloud/pkg/scheduler/cache/candidate" candidatecache "yunion.io/x/onecloud/pkg/scheduler/cache/candidate" "yunion.io/x/onecloud/pkg/scheduler/core" "yunion.io/x/onecloud/pkg/scheduler/data_manager" - "yunion.io/x/pkg/utils" + "yunion.io/x/onecloud/pkg/util/k8s" ) const defaultIgnorePool = true @@ -29,6 +34,7 @@ type SchedulerManager struct { CandidateManager *data_manager.CandidateManager //ReservedPoolManager *data_manager.ReservedPoolManager //NetworkManager *data_manager.NetworkManager + KubeClusterManager *k8s.SKubeClusterManager } func NewSchedulerManager(stopCh <-chan struct{}) *SchedulerManager { @@ -41,10 +47,19 @@ func NewSchedulerManager(stopCh <-chan struct{}) *SchedulerManager { sm.TaskManager = NewTaskManager(stopCh) //sm.ReservedPoolManager = data_manager.NewReservedPoolManager(stopCh) //sm.NetworkManager = data_manager.NewNetworkManager(sm.DataManager, sm.ReservedPoolManager) + sm.KubeClusterManager = k8s.NewKubeClusterManager(o.GetOptions().Region, 30*time.Second) return sm } +func GetScheduleManager() *SchedulerManager { + return schedManager +} + +func GetK8sClient() (*kubernetes.Clientset, error) { + return GetScheduleManager().KubeClusterManager.GetK8sClient() +} + func InitAndStart(stopCh <-chan struct{}) { if schedManager != nil { log.Warningf("Global scheduler already init.") @@ -65,6 +80,7 @@ func (sm *SchedulerManager) start() { sm.CandidateManager.Run, //sm.ReservedPoolManager.Run, //sm.NetworkManager.Run, + sm.KubeClusterManager.Start, } for _, f := range startFuncs { go f() diff --git a/pkg/util/aliyun/snapshot.go b/pkg/util/aliyun/snapshot.go index a082b4522b..e19ace3e3c 100644 --- a/pkg/util/aliyun/snapshot.go +++ b/pkg/util/aliyun/snapshot.go @@ -51,14 +51,6 @@ func (self *SSnapshot) GetStatus() string { } } -func (self *SSnapshot) GetManagerId() string { - return self.region.client.providerId -} - -func (self *SSnapshot) GetRegionId() string { - return self.region.GetId() -} - func (self *SSnapshot) GetSize() int32 { return self.SourceDiskSize } diff --git a/pkg/util/ansible/const.go b/pkg/util/ansible/const.go index 06ac34ff47..0f74502789 100644 --- a/pkg/util/ansible/const.go +++ b/pkg/util/ansible/const.go @@ -1,5 +1,5 @@ package ansible const ( - PUBLIC_CLOUD_ANSIBLE_USER = "yunionroot" + PUBLIC_CLOUD_ANSIBLE_USER = "cloudroot" ) diff --git a/pkg/util/azure/azure.go b/pkg/util/azure/azure.go index 82811e8af1..ad7268feb6 100644 --- a/pkg/util/azure/azure.go +++ b/pkg/util/azure/azure.go @@ -106,8 +106,10 @@ func (self *SAzureClient) getDefaultClient() (*autorest.Client, error) { return nil, err } client.Authorizer = authorizer - // client.RequestInspector = LogRequest() - // client.ResponseInspector = LogResponse() + if DEBUG { + client.RequestInspector = LogRequest() + client.ResponseInspector = LogResponse() + } return &client, nil } @@ -116,7 +118,7 @@ func (self *SAzureClient) jsonRequest(method, url string, body string) (jsonutil if err != nil { return nil, err } - return jsonRequest(cli, method, self.domain, url, body) + return jsonRequest(cli, method, self.domain, url, self.subscriptionId, body) } func (self *SAzureClient) Get(resourceId string, params []string, retVal interface{}) error { @@ -131,7 +133,7 @@ func (self *SAzureClient) Get(resourceId string, params []string, retVal interfa if err != nil { return err } - body, err := jsonRequest(cli, "GET", self.domain, path, "") + body, err := jsonRequest(cli, "GET", self.domain, path, self.subscriptionId, "") if err != nil { return err } @@ -151,7 +153,7 @@ func (self *SAzureClient) ListVmSizes(location string) (jsonutils.JSONObject, er 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, "") + return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") } func (self *SAzureClient) ListClassicDisks() (jsonutils.JSONObject, error) { @@ -163,7 +165,7 @@ func (self *SAzureClient) ListClassicDisks() (jsonutils.JSONObject, error) { return nil, fmt.Errorf("need subscription id") } url := fmt.Sprintf("/subscriptions/%s/services/disks", self.subscriptionId) - return jsonRequest(cli, "GET", self.domain, url, "") + return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") } func (self *SAzureClient) ListAll(resourceType string, retVal interface{}) error { @@ -178,7 +180,7 @@ func (self *SAzureClient) ListAll(resourceType string, retVal interface{}) error if len(resourceType) > 0 { url += fmt.Sprintf("/providers/%s", resourceType) } - body, err := jsonRequest(cli, "GET", self.domain, url, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") if err != nil { return err } @@ -193,7 +195,7 @@ func (self *SAzureClient) ListSubscriptions() (jsonutils.JSONObject, error) { if err != nil { return nil, err } - return jsonRequest(cli, "GET", self.domain, "/subscriptions", "") + return jsonRequest(cli, "GET", self.domain, "/subscriptions", self.subscriptionId, "") } func (self *SAzureClient) List(golbalResource string, retVal interface{}) error { @@ -208,7 +210,7 @@ func (self *SAzureClient) List(golbalResource string, retVal interface{}) error if len(self.subscriptionId) > 0 && len(golbalResource) > 0 { url += fmt.Sprintf("/%s", golbalResource) } - body, err := jsonRequest(cli, "GET", self.domain, url, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") if err != nil { return err } @@ -224,7 +226,7 @@ func (self *SAzureClient) ListByTypeWithResourceGroup(resourceGroupName string, 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, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") if err != nil { return err } @@ -236,7 +238,7 @@ func (self *SAzureClient) Delete(resourceId string) error { if err != nil { return err } - _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, "") + _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, self.subscriptionId, "") return err } @@ -246,7 +248,7 @@ func (self *SAzureClient) PerformAction(resourceId string, action string, body s return nil, err } url := fmt.Sprintf("%s/%s", resourceId, action) - return jsonRequest(cli, "POST", self.domain, url, body) + return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body) } func (self *SAzureClient) fetchResourceGroup(cli *autorest.Client, location string) error { @@ -261,7 +263,7 @@ func (self *SAzureClient) fetchResourceGroup(cli *autorest.Client, location stri if len(self.ressourceGroups) == 0 { //Create Default resourceGroup _url := fmt.Sprintf("/subscriptions/%s/resourcegroups/Default", self.subscriptionId) - body, err := jsonRequest(cli, "PUT", self.domain, _url, fmt.Sprintf(`{"name": "Default", "location": "%s"}`, location)) + body, err := jsonRequest(cli, "PUT", self.domain, _url, self.subscriptionId, fmt.Sprintf(`{"name": "Default", "location": "%s"}`, location)) if err != nil { return err } @@ -280,13 +282,25 @@ func (self *SAzureClient) checkParams(body jsonutils.JSONObject, params []string for i := 0; i < len(params); i++ { data, err := body.GetString(params[i]) if err != nil { - return nil, fmt.Errorf("Missing %s params") + return nil, fmt.Errorf("Missing %s params", params[i]) } result[params[i]] = data } return result, nil } +type AzureErrorDetail struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Target string `json:"target,omitempty"` +} + +type AzureError struct { + Code string `json:"code,omitempty"` + Details []AzureErrorDetail `json:"details,omitempty"` + Message string `json:"message,omitempty"` +} + func (self *SAzureClient) Create(body jsonutils.JSONObject, retVal interface{}) error { cli, err := self.getDefaultClient() if err != nil { @@ -307,11 +321,14 @@ func (self *SAzureClient) Create(body jsonutils.JSONObject, retVal interface{}) return fmt.Errorf("Create Default resourceGroup error?") } url := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", self.subscriptionId, self.ressourceGroups[0].Name, params["type"], params["name"]) - result, err := jsonRequest(cli, "PUT", self.domain, url, body.String()) + result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String()) if err != nil { return err } - return result.Unmarshal(retVal) + if retVal != nil { + return result.Unmarshal(retVal) + } + return nil } func (self *SAzureClient) CheckNameAvailability(Type string, body string) (jsonutils.JSONObject, error) { @@ -323,7 +340,7 @@ func (self *SAzureClient) CheckNameAvailability(Type string, body string) (jsonu 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, body) + return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body) } func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) error { @@ -332,7 +349,7 @@ func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) return err } url, err := body.GetString("id") - result, err := jsonRequest(cli, "PUT", self.domain, url, body.String()) + result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String()) if err != nil { return err } @@ -342,8 +359,86 @@ func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) return nil } -func jsonRequest(client *autorest.Client, method, domain, baseUrl string, body string) (jsonutils.JSONObject, error) { - return _jsonRequest(client, method, domain, baseUrl, body) +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), "") + 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)) + } + return fmt.Errorf("wait service %s register timeout", serviceType) +} + +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, "") + if err != nil || result.Contains("error") { + return fmt.Errorf("failed to register %s service", serviceType) + } + if state, _ := result.GetString("registrationState"); state == "Registered" { + return nil + } + return waitRegisterComplete(client, domain, subscriptionId, serviceType) +} + +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 + } + return false +} + +func jsonRequest(client *autorest.Client, method, domain, baseUrl string, subscriptionId string, body string) (jsonutils.JSONObject, error) { + result, err := _jsonRequest(client, method, domain, baseUrl, body) + if err != nil { + return nil, err + } + if result.Contains("error") { + azureError := AzureError{} + if err := result.Unmarshal(&azureError, "error"); err != nil { + return nil, fmt.Errorf(result.String()) + } + if recoverFromError(client, domain, subscriptionId, azureError) { + return _jsonRequest(client, method, domain, baseUrl, body) + } + 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) { @@ -470,15 +565,7 @@ func _jsonRequest(client *autorest.Client, method, domain, baseURL, body string) return nil, err } _data := strings.Replace(string(data), "\r", "", -1) - result, err = jsonutils.Parse([]byte(_data)) - if err != nil { - return nil, err - } - if result.Contains("error") { - log.Errorf("Azure %s request: %s \nbody: %s error: %v", req.Method, req.URL.String(), body, result.String()) - return nil, fmt.Errorf(result.String()) - } - return result, nil + return jsonutils.Parse([]byte(_data)) } func (self *SAzureClient) UpdateAccount(tenantId, secret, envName string) error { diff --git a/pkg/util/azure/classic_host.go b/pkg/util/azure/classic_host.go index d7595963f1..f2c7aa5654 100644 --- a/pkg/util/azure/classic_host.go +++ b/pkg/util/azure/classic_host.go @@ -13,10 +13,6 @@ type SClassicHost struct { zone *SZone } -const ( - DEFAULT_USER = "yunion" -) - func (self *SClassicHost) GetMetadata() *jsonutils.JSONDict { return nil } @@ -26,7 +22,7 @@ func (self *SClassicHost) GetId() string { } func (self *SClassicHost) GetName() string { - return fmt.Sprintf("%s-classic", self.zone.region.client.subscriptionId) + return fmt.Sprintf("%s/%s-classic", self.zone.region.GetGlobalId(), self.zone.region.client.subscriptionId) } func (self *SClassicHost) GetGlobalId() string { @@ -73,7 +69,7 @@ func (self *SClassicHost) GetMemSizeMB() int { return 0 } func (self *SClassicHost) GetEnabled() bool { - return false + return true } func (self *SClassicHost) GetHostStatus() string { diff --git a/pkg/util/azure/debug.go b/pkg/util/azure/debug.go index 8da73e4983..57beb087d0 100644 --- a/pkg/util/azure/debug.go +++ b/pkg/util/azure/debug.go @@ -9,6 +9,10 @@ import ( "github.com/Azure/go-autorest/autorest" ) +const ( + DEBUG = false +) + func LogRequest() autorest.PrepareDecorator { return func(p autorest.Preparer) autorest.Preparer { return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { diff --git a/pkg/util/azure/host.go b/pkg/util/azure/host.go index 7472d84e93..970a497cd7 100644 --- a/pkg/util/azure/host.go +++ b/pkg/util/azure/host.go @@ -8,6 +8,7 @@ import ( "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/ansible" ) type SHost struct { @@ -23,7 +24,7 @@ func (self *SHost) GetId() string { } func (self *SHost) GetName() string { - return fmt.Sprintf("%s", self.zone.region.client.subscriptionId) + return fmt.Sprintf("%s/%s", self.zone.region.GetGlobalId(), self.zone.region.client.subscriptionId) } func (self *SHost) GetGlobalId() string { @@ -42,18 +43,47 @@ func (self *SHost) Refresh() error { return nil } -func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, networkId string, ipAddr string, desc string, passwd string, storageType string, diskSizes []int, publicKey string, secgroupId string, userData string) (cloudprovider.ICloudVM, error) { - nicId := "" - if net := self.zone.getNetworkById(networkId); net == nil { - return nil, fmt.Errorf("invalid network ID %s", networkId) - } else if nic, err := self.zone.region.CreateNetworkInterface(fmt.Sprintf("%s-ipconfig", name), ipAddr, net.GetId(), secgroupId); err != nil { - return nil, err - } else { - nicId = nic.ID - } - vmId, err := self._createVM(name, imgId, int32(sysDiskSize), cpu, memMB, nicId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, userData) +func (self *SHost) searchNetorkInterface(IPAddr string, networkId string, secgroupId string) (*SInstanceNic, error) { + interfaces, err := self.zone.region.GetNetworkInterfaces() if err != nil { - self.zone.region.DeleteNetworkInterface(nicId) + 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 %s failed %d") + return nil, err + } + } + return &interfaces[i], nil + } + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, networkId string, ipAddr string, desc string, passwd string, storageType string, diskSizes []int, publicKey string, secgroupId string, userData string) (cloudprovider.ICloudVM, error) { + net := self.zone.getNetworkById(networkId) + if net == nil { + return nil, fmt.Errorf("invalid network ID %s", networkId) + } + nic, err := self.searchNetorkInterface(ipAddr, net.GetId(), secgroupId) + if err != nil { + if err == cloudprovider.ErrNotFound { + nic, err = self.zone.region.CreateNetworkInterface(fmt.Sprintf("%s-ipconfig", name), ipAddr, net.GetId(), secgroupId) + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + vmId, err := self._createVM(name, imgId, int32(sysDiskSize), cpu, memMB, nic.ID, ipAddr, desc, passwd, storageType, diskSizes, publicKey, userData) + if err != nil { + self.zone.region.DeleteNetworkInterface(nic.ID) return nil, err } if vm, err := self.zone.region.GetInstance(vmId); err != nil { @@ -89,7 +119,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDiskSize int32, cpu i }, OsProfile: OsProfile{ ComputerName: name, - AdminUsername: DEFAULT_USER, + AdminUsername: ansible.PUBLIC_CLOUD_ANSIBLE_USER, AdminPassword: passwd, CustomData: userData, }, diff --git a/pkg/util/azure/service.go b/pkg/util/azure/service.go new file mode 100644 index 0000000000..c5fa17abe7 --- /dev/null +++ b/pkg/util/azure/service.go @@ -0,0 +1,55 @@ +package azure + +import ( + "fmt" +) + +type SServices struct { + Value []SService `json:"value,omitempty"` +} + +type SService struct { + ID string `json:"id,omitempty"` + Namespace string `json:"namespace,omitempty"` + RegistrationState string `json:"registrationState,omitempty"` + ResourceTypes []ResourceType `json:"resourceTypes,omitempty"` +} + +type ResourceType struct { + ApiVersions []string `json:"apiVersions,omitempty"` + Capabilities string `json:"capabilities,omitempty"` + Locations []string `json:"locations,omitempty"` + ResourceType string `json:"locations,omitempty"` +} + +func (self *SRegion) ListServices() ([]SService, error) { + services := []SService{} + return services, self.client.List("providers", &services) +} + +func (self *SRegion) SerciceShow(serviceType string) (*SService, error) { + service := SService{} + return &service, self.client.Get("providers/"+serviceType, []string{}, &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 + } + } + return fmt.Errorf("failed to find namespace: %s", resourceType) +} + +func (self *SRegion) ServiceRegister(resourceType string) error { + return self.serviceOperation(resourceType, "register") +} + +func (self *SRegion) ServiceUnRegister(resourceType string) error { + return self.serviceOperation(resourceType, "unregister") +} diff --git a/pkg/util/azure/shell/service.go b/pkg/util/azure/shell/service.go new file mode 100644 index 0000000000..2f1c26bb5f --- /dev/null +++ b/pkg/util/azure/shell/service.go @@ -0,0 +1,41 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/util/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ServiceListOptions struct { + } + shellutils.R(&ServiceListOptions{}, "service-list", "List providers", func(cli *azure.SRegion, args *ServiceListOptions) error { + services, err := cli.ListServices() + if err != nil { + return err + } + printList(services, len(services), 0, 0, []string{}) + return nil + }) + + type ServiceOptions struct { + NAME string `help:"Name for service register"` + } + + shellutils.R(&ServiceOptions{}, "service-register", "Register service", func(cli *azure.SRegion, args *ServiceOptions) error { + return cli.ServiceRegister(args.NAME) + }) + + shellutils.R(&ServiceOptions{}, "service-unregister", "Unregister service", func(cli *azure.SRegion, args *ServiceOptions) error { + return cli.ServiceUnRegister(args.NAME) + }) + + shellutils.R(&ServiceOptions{}, "service-show", "Show service detail", func(cli *azure.SRegion, args *ServiceOptions) error { + service, err := cli.SerciceShow(args.NAME) + if err != nil { + return err + } + printObject(service) + return nil + }) + +} diff --git a/pkg/util/azure/shell/vpc.go b/pkg/util/azure/shell/vpc.go index 6c068fdc0b..552a22c654 100644 --- a/pkg/util/azure/shell/vpc.go +++ b/pkg/util/azure/shell/vpc.go @@ -32,4 +32,23 @@ func init() { printObject(vpc) return nil }) + + shellutils.R(&VpcOptions{}, "vpc-delete", "Delete vpc", func(cli *azure.SRegion, args *VpcOptions) error { + return cli.DeleteVpc(args.ID) + }) + + type VpcCreateOptions struct { + NAME string `help:"vpc Name"` + CIDR string `help:"vpc cidr"` + Desc string `help:"vpc description"` + } + + shellutils.R(&VpcCreateOptions{}, "vpc-create", "Create vpc", func(cli *azure.SRegion, args *VpcCreateOptions) error { + vpc, err := cli.CreateIVpc(args.NAME, args.Desc, args.CIDR) + if err != nil { + return err + } + printObject(vpc) + return nil + }) } diff --git a/pkg/util/azure/snapshot.go b/pkg/util/azure/snapshot.go index 5db8d6da4c..ccc78658bd 100644 --- a/pkg/util/azure/snapshot.go +++ b/pkg/util/azure/snapshot.go @@ -22,8 +22,9 @@ type SSnapshot struct { Name string Location string ManagedBy string - Sku SnapshotSku + Sku *SnapshotSku Properties DiskProperties + Type string } func (self *SSnapshot) GetId() string { @@ -57,7 +58,23 @@ func (self *SSnapshot) IsEmulated() bool { } func (self *SRegion) CreateSnapshot(diskId, snapName, desc string) (*SSnapshot, error) { - snapshot := SSnapshot{} + 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, + }, + DiskSizeGB: disk.Properties.DiskSizeGB, + }, + Type: "Microsoft.Compute/snapshots", + } return &snapshot, self.client.Create(jsonutils.Marshal(snapshot), &snapshot) } @@ -137,9 +154,11 @@ func (self *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { classicSnapshots = append(classicSnapshots, _classicSnapshots...) isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)+len(classicSnapshots)) for i := 0; i < len(snapshots); i++ { + snapshots[i].region = self isnapshots[i] = &snapshots[i] } for i := 0; i < len(classicSnapshots); i++ { + classicSnapshots[i].region = self isnapshots[len(snapshots)+i] = &classicSnapshots[i] } return isnapshots, nil @@ -149,14 +168,6 @@ func (self *SSnapshot) GetDiskId() string { return self.Properties.CreationData.SourceResourceID } -func (self *SSnapshot) GetManagerId() string { - return self.region.client.providerId -} - -func (self *SSnapshot) GetRegionId() string { - return self.region.GetId() -} - func (self *SSnapshot) GetDiskType() string { return "" } diff --git a/pkg/util/azure/vpc.go b/pkg/util/azure/vpc.go index b75d14bbde..07060c4a4c 100644 --- a/pkg/util/azure/vpc.go +++ b/pkg/util/azure/vpc.go @@ -77,7 +77,11 @@ func (self *SVpc) GetCidrBlock() string { } func (self *SVpc) Delete() error { - return self.region.client.Delete(self.ID) + return self.region.DeleteVpc(self.ID) +} + +func (self *SRegion) DeleteVpc(vpcId string) error { + return self.client.Delete(vpcId) } func (self *SVpc) getSecurityGroups() ([]SSecurityGroup, error) { diff --git a/pkg/util/k8s/cluster.go b/pkg/util/k8s/cluster.go new file mode 100644 index 0000000000..b6d6828680 --- /dev/null +++ b/pkg/util/k8s/cluster.go @@ -0,0 +1,103 @@ +package k8s + +import ( + "sync" + "time" + + "k8s.io/client-go/kubernetes" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/mcclient/auth" + kubeserver "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" +) + +type SKubeClusterManager struct { + k8sConfigLock *sync.RWMutex + k8sConfig string + interval time.Duration + region string +} + +func NewKubeClusterManager(region string, interval time.Duration) *SKubeClusterManager { + return &SKubeClusterManager{ + k8sConfigLock: new(sync.RWMutex), + interval: interval, + region: region, + } +} + +func (man *SKubeClusterManager) GetK8sConfig() string { + man.k8sConfigLock.RLock() + defer man.k8sConfigLock.RUnlock() + return man.k8sConfig +} + +func (man *SKubeClusterManager) GetK8sClient() (*kubernetes.Clientset, error) { + cli, err := NewClientByContent([]byte(man.GetK8sConfig()), nil) + if err != nil { + log.Warningf("Init kubernetes client error: %v", err) + return nil, err + } + return cli, nil +} + +func (man *SKubeClusterManager) Start() { + go man.startRefreshKubeConfig() +} + +func (man *SKubeClusterManager) setK8sConfig(conf string) { + man.k8sConfigLock.Lock() + defer man.k8sConfigLock.Unlock() + man.k8sConfig = conf +} + +func (man *SKubeClusterManager) isK8sHealthy() bool { + if man.GetK8sConfig() == "" { + return false + } + cli, err := man.GetK8sClient() + if err != nil { + return false + } + _, err = cli.Discovery().ServerVersion() + if err != nil { + log.Errorf("Discovery k8s version: %v", err) + return false + } + return true +} + +func (man *SKubeClusterManager) startRefreshKubeConfig() { + man.refreshKubeConfig() + tick := time.Tick(man.interval) + for { + select { + case <-tick: + man.refreshKubeConfig() + } + } +} + +func (man *SKubeClusterManager) refreshKubeConfig() { + if man.isK8sHealthy() { + return + } + kubeConfig, err := man.getKubeClusterConfig() + if err != nil { + log.Errorf("Get default k8s config from kube server error: %v", err) + } + man.setK8sConfig(kubeConfig) +} + +func (man *SKubeClusterManager) getKubeClusterConfig() (string, error) { + session := auth.GetAdminSession(man.region, "v1") + params := jsonutils.NewDict() + params.Add(jsonutils.JSONTrue, "directly") + ret, err := kubeserver.Clusters.PerformAction(session, "default", "generate-kubeconfig", params) + if err != nil { + return "", err + } + return ret.GetString("kubeconfig") +} diff --git a/pkg/util/rbacutils/rabc.go b/pkg/util/rbacutils/rabc.go index ebf1fb911c..47c8ff6d69 100644 --- a/pkg/util/rbacutils/rabc.go +++ b/pkg/util/rbacutils/rabc.go @@ -13,29 +13,29 @@ type TRbacResult string const ( WILD_MATCH = "*" - Allow = TRbacResult("allow") + AdminAllow = TRbacResult("allow") OwnerAllow = TRbacResult("owner") + UserAllow = TRbacResult("user") + GuestAllow = TRbacResult("guest") Deny = TRbacResult("deny") ) -func (result TRbacResult) IsHigherPrivilege(r2 TRbacResult) bool { - switch result { - case Allow: - if r2 == Allow { - return false - } else { - return true - } - case OwnerAllow: - if r2 == Deny { - return true - } else { - return false - } - case Deny: - return false +var ( + strictness = map[TRbacResult]int{ + Deny: 0, + AdminAllow: 1, + OwnerAllow: 2, + UserAllow: 3, + GuestAllow: 4, } - return false +) + +func (r TRbacResult) Strictness() int { + return strictness[r] +} + +func (r1 TRbacResult) StricterThan(r2 TRbacResult) bool { + return r1.Strictness() < r2.Strictness() } type SRbacPolicy struct { @@ -79,6 +79,10 @@ func (rule *SRbacRule) contains(rule2 *SRbacRule) bool { return true } +func (rule *SRbacRule) stricterThan(r2 *SRbacRule) bool { + return rule.Result.StricterThan(r2.Result) +} + func (rule *SRbacRule) match(service string, resource string, action string, extra ...string) (bool, int, int) { matched := 0 weight := 0 @@ -144,7 +148,9 @@ func (policy *SRbacPolicy) GetMatchRule(service string, resource string, action var matchRule *SRbacRule for i := 0; i < len(policy.Rules); i += 1 { match, matchCnt, weight := policy.Rules[i].match(service, resource, action, extra...) - if match && (maxMatchCnt < matchCnt || (maxMatchCnt == matchCnt && minWeight > weight)) { + if match && (maxMatchCnt < matchCnt || + (maxMatchCnt == matchCnt && minWeight > weight) || + (maxMatchCnt == matchCnt && minWeight == weight && matchRule.stricterThan(&policy.Rules[i]))) { maxMatchCnt = matchCnt minWeight = weight matchRule = &policy.Rules[i] @@ -153,7 +159,7 @@ func (policy *SRbacPolicy) GetMatchRule(service string, resource string, action return matchRule } -func compactRules(rules []SRbacRule) []SRbacRule { +func CompactRules(rules []SRbacRule) []SRbacRule { output := make([]SRbacRule, 1) output[0] = rules[0] for i := 1; i < len(rules); i += 1 { @@ -190,7 +196,7 @@ func (policy *SRbacPolicy) Decode(policyJson jsonutils.JSONObject) error { return err } - policy.Rules = compactRules(rules) + policy.Rules = CompactRules(rules) return nil } @@ -208,10 +214,14 @@ func decode(rules jsonutils.JSONObject, decodeRule SRbacRule, level int) ([]SRba ruleJsonStr := rules.(*jsonutils.JSONString) ruleStr, _ := ruleJsonStr.GetString() switch ruleStr { - case string(Allow): - decodeRule.Result = Allow + case string(AdminAllow): + decodeRule.Result = AdminAllow case string(OwnerAllow): decodeRule.Result = OwnerAllow + case string(UserAllow): + decodeRule.Result = UserAllow + case string(GuestAllow): + decodeRule.Result = GuestAllow case string(Deny): decodeRule.Result = Deny default: diff --git a/pkg/webconsole/command/ssh_command.go b/pkg/webconsole/command/ssh_command.go index f1bbb15f67..05aa9fea17 100644 --- a/pkg/webconsole/command/ssh_command.go +++ b/pkg/webconsole/command/ssh_command.go @@ -31,7 +31,7 @@ type SSHtoolSol struct { func getCommand(ctx context.Context, userCred mcclient.TokenCredential, ip string) (string, *BaseCommand, error) { cmd := NewBaseCommand(o.Options.SshToolPath) s := auth.GetAdminSession(o.Options.Region, "v2") - key, err := modules.Sshkeypairs.GetById(s, userCred.GetProjectId(), jsonutils.NewDict()) + key, err := modules.Sshkeypairs.GetById(s, userCred.GetProjectId(), jsonutils.Marshal(map[string]bool{"admin": true})) if err != nil { return "", nil, err } @@ -85,7 +85,7 @@ func NewSSHtoolSolCommand(ctx context.Context, userCred mcclient.TokenCredential func (c *SSHtoolSol) GetCommand() *exec.Cmd { if c.BaseCommand != nil { cmd := c.BaseCommand.GetCommand() - cmd.Env = append(cmd.Env, "TERM=screen-256color") + cmd.Env = append(cmd.Env, "TERM=xterm-256color") return cmd } return nil diff --git a/pkg/webconsole/server/tty_server.go b/pkg/webconsole/server/tty_server.go index bde779afa8..09955dcc2e 100644 --- a/pkg/webconsole/server/tty_server.go +++ b/pkg/webconsole/server/tty_server.go @@ -99,7 +99,7 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) { log.Infof("exec: %s", p.Command) args := strings.Split(p.Command, " ") cmd := exec.Command(args[0], args[1:]...) - cmd.Env = append(cmd.Env, "TERM=screen-256color") + cmd.Env = append(cmd.Env, "TERM=xterm-256color") if _pty, err := pty.Start(cmd); err != nil { so.Emit(OUTPUT_EVENT, err.Error()+"\r\n") log.Errorf("exec error: %v", err)