diff --git a/cmd/climc/shell/schedtaghosts.go b/cmd/climc/shell/schedtaghosts.go index 8973621b84..b68b5144e6 100644 --- a/cmd/climc/shell/schedtaghosts.go +++ b/cmd/climc/shell/schedtaghosts.go @@ -1,6 +1,8 @@ package shell import ( + "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/mcclient" @@ -8,57 +10,84 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/options" ) -func init() { - type SchedtagHostListOptions struct { - options.BaseListOptions - Schedtag string `help:"ID or Name of schedtag"` +type schedtagModelHelper struct { + managers []modules.JointResourceManager +} + +func newSchedtagModelHelper(mans ...modules.JointResourceManager) *schedtagModelHelper { + return &schedtagModelHelper{managers: mans} +} + +func (h *schedtagModelHelper) register() { + for _, man := range h.managers { + h.list(man.Slave, man.Slave.GetKeyword()) + h.add(man, man.Slave.GetKeyword()) + h.remove(man, man.Slave.GetKeyword()) } - R(&SchedtagHostListOptions{}, "schedtag-host-list", "List all scheduler tag and host pairs", func(s *mcclient.ClientSession, args *SchedtagHostListOptions) error { - mod, err := modules.GetJointModule2(s, &modules.Schedtags, &modules.Hosts) - if err != nil { - return err - } - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() +} + +func (h *schedtagModelHelper) list(slave modules.Manager, kw string) { + R( + &options.SchedtagModelListOptions{}, + fmt.Sprintf("schedtag-%s-list", kw), + fmt.Sprintf("List all scheduler tag and %s pairs", kw), + func(s *mcclient.ClientSession, args *options.SchedtagModelListOptions) error { + mod, err := modules.GetJointModule2(s, &modules.Schedtags, slave) if err != nil { return err - } - } - var result *modules.ListResult - if len(args.Schedtag) > 0 { - result, err = mod.ListDescendent(s, args.Schedtag, params) - } else { - result, err = mod.List(s, params) - } - if err != nil { - return err - } - printList(result, mod.GetColumns(s)) - return nil - }) + params, err := args.Params() + if err != nil { + return err + } + var result *modules.ListResult + if len(args.Schedtag) > 0 { + result, err = mod.ListDescendent(s, args.Schedtag, params) + } else { + result, err = mod.List(s, params) + } + if err != nil { + return err + } + printList(result, mod.GetColumns(s)) + return nil + }, + ) +} - type SchedtagHostPair struct { - SCHEDTAG string `help:"Scheduler tag"` - HOST string `help:"Host"` - } - R(&SchedtagHostPair{}, "schedtag-host-add", "Add a schedtag to a host", func(s *mcclient.ClientSession, args *SchedtagHostPair) error { - schedtag, err := modules.Schedtaghosts.Attach(s, args.SCHEDTAG, args.HOST, nil) - if err != nil { - return err - } - printObject(schedtag) - return nil - }) +func (h *schedtagModelHelper) add(man modules.JointResourceManager, kw string) { + R( + &options.SchedtagModelPairOptions{}, + fmt.Sprintf("schedtag-%s-add", kw), + fmt.Sprintf("Add a schedtag to a %s", kw), + func(s *mcclient.ClientSession, args *options.SchedtagModelPairOptions) error { + schedtag, err := man.Attach(s, args.SCHEDTAG, args.OBJECT, nil) + if err != nil { + return err + } + printObject(schedtag) + return nil + }) +} - R(&SchedtagHostPair{}, "schedtag-host-remove", "Remove a schedtag from a host", func(s *mcclient.ClientSession, args *SchedtagHostPair) error { - schedtag, err := modules.Schedtaghosts.Detach(s, args.SCHEDTAG, args.HOST, nil) - if err != nil { - return err - } - printObject(schedtag) - return nil - }) +func (h *schedtagModelHelper) remove(man modules.JointResourceManager, kw string) { + R( + &options.SchedtagModelPairOptions{}, + fmt.Sprintf("schedtag-%s-remove", kw), + fmt.Sprintf("Remove a schedtag to a %s", kw), + func(s *mcclient.ClientSession, args *options.SchedtagModelPairOptions) error { + schedtag, err := man.Detach(s, args.SCHEDTAG, args.OBJECT, nil) + if err != nil { + return err + } + printObject(schedtag) + return nil + }) +} + +func init() { + newSchedtagModelHelper( + modules.Schedtaghosts, + modules.Schedtagstorages, + ).register() } diff --git a/cmd/climc/shell/schedtags.go b/cmd/climc/shell/schedtags.go index f4fa28a9e2..ee688dca95 100644 --- a/cmd/climc/shell/schedtags.go +++ b/cmd/climc/shell/schedtags.go @@ -13,17 +13,21 @@ import ( func init() { type SchedtagListOptions struct { options.BaseListOptions + Type string `help:"Filter by resource type"` } - R(&SchedtagListOptions{}, "schedtag-list", "List schedule tags", func(s *mcclient.ClientSession, suboptions *SchedtagListOptions) error { + R(&SchedtagListOptions{}, "schedtag-list", "List schedule tags", func(s *mcclient.ClientSession, args *SchedtagListOptions) error { var params *jsonutils.JSONDict { var err error - params, err = suboptions.BaseListOptions.Params() + params, err = args.BaseListOptions.Params() if err != nil { return err } } + if len(args.Type) > 0 { + params.Add(jsonutils.NewString(args.Type), "resource_type") + } result, err := modules.Schedtags.List(s, params) if err != nil { return err @@ -57,6 +61,7 @@ func init() { NAME string `help:"Name of new schedtag"` Strategy string `help:"Policy" choices:"require|exclude|prefer|avoid"` Desc string `help:"Description"` + Type string `help:"Resource type" choices:"hosts|storages"` } R(&SchedtagCreateOptions{}, "schedtag-create", "Create a schedule tag", func(s *mcclient.ClientSession, args *SchedtagCreateOptions) error { params := jsonutils.NewDict() @@ -67,6 +72,9 @@ func init() { if len(args.Desc) > 0 { params.Add(jsonutils.NewString(args.Desc), "description") } + if len(args.Type) > 0 { + params.Add(jsonutils.NewString(args.Type), "resource_type") + } schedtag, err := modules.Schedtags.Create(s, params) if err != nil { return err diff --git a/cmd/climc/shell/schedulers.go b/cmd/climc/shell/schedulers.go index d9433bc757..8c3c47b808 100644 --- a/cmd/climc/shell/schedulers.go +++ b/cmd/climc/shell/schedulers.go @@ -45,6 +45,7 @@ func init() { type SchedulerCandidateListOptions struct { Type string `help:"Sched type filter" choices:"baremetal|host"` + Region string `help:"Cloud region ID"` Zone string `help:"Zone ID"` Limit int `default:"50" help:"Page limit"` Offset int `default:"0" help:"Page offset"` @@ -58,6 +59,9 @@ func init() { if args.Offset > 0 { params.Add(jsonutils.NewInt(int64(args.Offset)), "offset") } + if len(args.Region) > 0 { + params.Add(jsonutils.NewString(args.Region), "region") + } if len(args.Zone) > 0 { params.Add(jsonutils.NewString(args.Zone), "zone") } diff --git a/cmd/scheduler/app/doc.go b/cmd/scheduler/app/doc.go deleted file mode 100644 index 0a42c091c2..0000000000 --- a/cmd/scheduler/app/doc.go +++ /dev/null @@ -1 +0,0 @@ -package app // import "yunion.io/x/onecloud/cmd/scheduler/app" diff --git a/cmd/scheduler/app/server.go b/cmd/scheduler/app/server.go deleted file mode 100644 index 3eaf29de64..0000000000 --- a/cmd/scheduler/app/server.go +++ /dev/null @@ -1,107 +0,0 @@ -// Package app implements a Server object for running the scheduler. -package app - -import ( - "fmt" - "io/ioutil" - "net" - "net/http" - "strconv" - - gin "gopkg.in/gin-gonic/gin.v1" - - "yunion.io/x/log" - "yunion.io/x/pkg/util/prometheus" - "yunion.io/x/pkg/utils" - - o "yunion.io/x/onecloud/cmd/scheduler/options" - "yunion.io/x/onecloud/pkg/mcclient/auth" - _ "yunion.io/x/onecloud/pkg/scheduler/algorithmprovider" - "yunion.io/x/onecloud/pkg/scheduler/db/models" - schedhandler "yunion.io/x/onecloud/pkg/scheduler/handler" - schedman "yunion.io/x/onecloud/pkg/scheduler/manager" - "yunion.io/x/onecloud/pkg/util/gin/middleware" -) - -type SchedulerServer struct { - Address string - Port int32 - SQLConn string - DBDialect string - AuthInfo *auth.AuthInfo -} - -func NewServerFromConfig() *SchedulerServer { - authURL := o.GetOptions().AuthURL - adminUser := o.GetOptions().AdminUser - adminPasswd := o.GetOptions().AdminPasswd - adminTenant := o.GetOptions().AdminTenant - a := auth.NewV2AuthInfo(authURL, adminUser, adminPasswd, adminTenant) - - s := &SchedulerServer{} - s.Address = o.GetOptions().Address - s.Port = int32(o.GetOptions().Port) - sqlDialect, sqlConn, err := utils.TransSQLAchemyURL(o.GetOptions().SqlConnection) - if err != nil { - log.Fatalf("Backup python sql_connection config err: %v", err) - } - s.SQLConn = sqlConn - s.DBDialect = sqlDialect - s.AuthInfo = a - - return s -} - -func Run(s *SchedulerServer) error { - startSched := func() { - err := models.Init(s.DBDialect, s.SQLConn) - if err != nil { - log.Fatalf("DB init error: %v, dialect: %s, url: %s", err, s.DBDialect, s.SQLConn) - } - - stopEverything := make(chan struct{}) - schedman.InitAndStart(stopEverything) - } - - debug := o.GetOptions().LogLevel == "debug" - - auth.AsyncInit(s.AuthInfo, debug, true, "", "", startSched) - - return startHTTP(s) -} - -func startHTTP(s *SchedulerServer) error { - gin.DefaultWriter = ioutil.Discard - - router := gin.Default() - router.Use(middleware.Logger()) - router.Use(middleware.ErrorHandler) - router.Use(middleware.KeystoneTokenVerifyMiddleware()) - - prometheus.InstallHandler(router) - schedhandler.InstallHandler(router) - - server := &http.Server{ - Addr: net.JoinHostPort(s.Address, strconv.Itoa(int(s.Port))), - Handler: router, - } - - log.Infof("Start server on: %s:%d", s.Address, s.Port) - - if o.GetOptions().EnableSsl { - return server.ListenAndServeTLS(o.GetOptions().SslCertfile, - o.GetOptions().SslKeyfile) - } else { - return server.ListenAndServe() - } -} - -func Execute() error { - o.Parse() - - err := Run(NewServerFromConfig()) - if err != nil { - err = fmt.Errorf("scheduler app failed to run: %v", err) - } - return err -} diff --git a/cmd/scheduler/main.go b/cmd/scheduler/main.go index 383a6d328b..cf2e23ffc1 100644 --- a/cmd/scheduler/main.go +++ b/cmd/scheduler/main.go @@ -5,11 +5,11 @@ import ( "yunion.io/x/log" - "yunion.io/x/onecloud/cmd/scheduler/app" + "yunion.io/x/onecloud/pkg/scheduler/service" ) func main() { - if err := app.Execute(); err != nil { + if err := service.StartService(); err != nil { log.Errorln(err) os.Exit(-1) } diff --git a/cmd/scheduler/options/doc.go b/cmd/scheduler/options/doc.go deleted file mode 100644 index 75aded7ae6..0000000000 --- a/cmd/scheduler/options/doc.go +++ /dev/null @@ -1 +0,0 @@ -package options // import "yunion.io/x/onecloud/cmd/scheduler/options" diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 452835442d..2b757d7f6f 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1159,6 +1159,8 @@ type SDiskConfig struct { Medium string `json:"medium"` ImageProperties map[string]string `json:"image_properties"` + Schedtags map[string]string `json:"schedtags"` + DiskId string `json:"-"` // import only } diff --git a/pkg/compute/models/dynamicschedtags.go b/pkg/compute/models/dynamicschedtags.go index 92922ff48b..a2c0059c3f 100644 --- a/pkg/compute/models/dynamicschedtags.go +++ b/pkg/compute/models/dynamicschedtags.go @@ -109,6 +109,10 @@ func (self *SDynamicschedtag) ValidateUpdateData(ctx context.Context, userCred m return self.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) } +func (self *SDynamicschedtag) GetSchedtag() *SSchedtag { + return self.getSchedtag() +} + func (self *SDynamicschedtag) getSchedtag() *SSchedtag { obj, err := SchedtagManager.FetchById(self.SchedtagId) if err != nil { @@ -139,6 +143,10 @@ func (self *SDynamicschedtag) GetExtraDetails(ctx context.Context, userCred mccl return self.getMoreColumns(extra), nil } +func (manager *SDynamicschedtagManager) GetAllEnabledDynamicSchedtags() []SDynamicschedtag { + return manager.getAllEnabledDynamicSchedtags() +} + func (manager *SDynamicschedtagManager) getAllEnabledDynamicSchedtags() []SDynamicschedtag { rules := make([]SDynamicschedtag, 0) diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index d547875b48..10bd0a64e5 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -2078,20 +2078,8 @@ func (self *SHost) GetBaremetalServer() *SGuest { return &guest } -func (self *SHost) getSchedtags() []SSchedtag { - tags := make([]SSchedtag, 0) - schedtags := SchedtagManager.Query().SubQuery() - hostschedtags := HostschedtagManager.Query().SubQuery() - q := schedtags.Query() - q = q.Join(hostschedtags, sqlchemy.AND(sqlchemy.Equals(hostschedtags.Field("schedtag_id"), schedtags.Field("id")), - sqlchemy.IsFalse(hostschedtags.Field("deleted")))) - q = q.Filter(sqlchemy.Equals(hostschedtags.Field("host_id"), self.Id)) - err := db.FetchModelObjects(SchedtagManager, q, &tags) - if err != nil { - log.Errorf("%s", err) - return nil - } - return tags +func (self *SHost) GetSchedtags() []SSchedtag { + return GetSchedtags(HostschedtagManager, self.Id) } type SHostGuestResourceUsage struct { @@ -2157,7 +2145,7 @@ func (self *SHost) getMoreDetails(ctx context.Context, extra *jsonutils.JSONDict extra.Add(jsonutils.NewInt(int64(len(nicInfos))), "nic_count") extra.Add(jsonutils.NewArray(nicInfos...), "nic_info") } - schedtags := self.getSchedtags() + schedtags := self.GetSchedtags() if schedtags != nil && len(schedtags) > 0 { info := make([]jsonutils.JSONObject, len(schedtags)) for i := 0; i < len(schedtags); i += 1 { diff --git a/pkg/compute/models/hostschedtags.go b/pkg/compute/models/hostschedtags.go index 71c80e69fc..40fc16e999 100644 --- a/pkg/compute/models/hostschedtags.go +++ b/pkg/compute/models/hostschedtags.go @@ -4,14 +4,13 @@ import ( "context" "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/mcclient" ) type SHostschedtagManager struct { - SHostJointsManager + *SSchedtagJointsManager } var HostschedtagManager *SHostschedtagManager @@ -19,11 +18,12 @@ var HostschedtagManager *SHostschedtagManager func init() { db.InitManager(func() { HostschedtagManager = &SHostschedtagManager{ - SHostJointsManager: NewHostJointsManager( + SSchedtagJointsManager: NewSchedtagJointsManager( SHostschedtag{}, "aggregate_hosts_tbl", "schedtaghost", "schedtaghosts", + HostManager, SchedtagManager, ), } @@ -31,55 +31,37 @@ func init() { } type SHostschedtag struct { - SHostJointsBase + SSchedtagJointsBase - HostId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) - SchedtagId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // =Column(VARCHAR(36, charset='ascii'), nullable=False) + HostId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) } -func (joint *SHostschedtag) Master() db.IStandaloneModel { - return db.JointMaster(joint) +func (self *SHostschedtag) GetHost() *SHost { + return self.Master().(*SHost) } -func (joint *SHostschedtag) Slave() db.IStandaloneModel { - return db.JointSlave(joint) +func (self *SHostschedtag) GetHosts() ([]SHost, error) { + hosts := []SHost{} + err := self.GetSchedtag().GetObjects(&hosts) + return hosts, err +} + +func (self *SHostschedtag) Master() db.IStandaloneModel { + return self.SSchedtagJointsBase.master(self) } func (self *SHostschedtag) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { - extra := self.SHostJointsBase.GetCustomizeColumns(ctx, userCred, query) - return db.JointModelExtra(self, extra) + return self.SSchedtagJointsBase.getCustomizeColumns(self, ctx, userCred, query) } func (self *SHostschedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { - extra, err := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query) - if err != nil { - return nil, err - } - return db.JointModelExtra(self, extra), nil -} - -func (self *SHostschedtag) getHost() *SHost { - obj, err := HostManager.FetchById(self.HostId) - if err != nil { - log.Errorf("%s", err) - return nil - } - return obj.(*SHost) -} - -func (self *SHostschedtag) getSchedtag() *SSchedtag { - obj, err := SchedtagManager.FetchById(self.SchedtagId) - if err != nil { - log.Errorf("%s", err) - return nil - } - return obj.(*SSchedtag) + return self.SSchedtagJointsBase.getExtraDetails(self, ctx, userCred, query) } func (self *SHostschedtag) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { - return db.DeleteModel(ctx, userCred, self) + return self.SSchedtagJointsBase.delete(self, ctx, userCred) } func (self *SHostschedtag) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { - return db.DetachJoint(ctx, userCred, self) + return self.SSchedtagJointsBase.detach(self, ctx, userCred) } diff --git a/pkg/compute/models/initdb.go b/pkg/compute/models/initdb.go index 498d9336df..774489d9cd 100644 --- a/pkg/compute/models/initdb.go +++ b/pkg/compute/models/initdb.go @@ -27,6 +27,7 @@ func InitDB() error { LoadbalancerListenerRuleManager, LoadbalancerBackendGroupManager, LoadbalancerBackendManager, + SchedtagManager, } { err := manager.InitializeData() if err != nil { diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 9e559fc0b5..01c79bd2ed 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -218,6 +218,10 @@ func (self *SNetwork) GetUsedAddresses() map[string]bool { return used } +func (self *SNetwork) GetIPRange() netutils.IPV4AddrRange { + return self.getIPRange() +} + func (self *SNetwork) getIPRange() netutils.IPV4AddrRange { start, _ := netutils.NewIPV4Addr(self.GuestIpStart) end, _ := netutils.NewIPV4Addr(self.GuestIpEnd) @@ -895,6 +899,10 @@ func (self *SNetwork) getRegion() *SCloudregion { return nil } +func (self *SNetwork) GetPorts() int { + return self.getIPRange().AddressCount() +} + func (self *SNetwork) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { wire := self.GetWire() extra.Add(jsonutils.NewString(wire.Name), "wire") @@ -903,7 +911,7 @@ func (self *SNetwork) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSOND } else { extra.Add(jsonutils.JSONFalse, "exit") } - extra.Add(jsonutils.NewInt(int64(self.getIPRange().AddressCount())), "ports") + extra.Add(jsonutils.NewInt(int64(self.GetPorts())), "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") diff --git a/pkg/compute/models/schedtagjoint.go b/pkg/compute/models/schedtagjoint.go new file mode 100644 index 0000000000..09087ad88d --- /dev/null +++ b/pkg/compute/models/schedtagjoint.go @@ -0,0 +1,143 @@ +package models + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SSchedtagJointsManager struct { + db.SJointResourceBaseManager +} + +func NewSchedtagJointsManager( + dt interface{}, + tableName string, + keyword string, + keywordPlural string, + master db.IStandaloneModelManager, + slave db.IStandaloneModelManager, +) *SSchedtagJointsManager { + return &SSchedtagJointsManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + dt, + tableName, + keyword, + keywordPlural, + master, + slave, + ), + } +} + +type SSchedtagJointsBase struct { + db.SJointResourceBase + + SchedtagId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // =Column(VARCHAR(36, charset='ascii'), nullable=False) +} + +func (man *SSchedtagJointsManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowList(userCred, man) +} + +func (man *SSchedtagJointsManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowCreate(userCred, man) +} + +func (man *SSchedtagJointsManager) FetchSchedtagById(id string) *SSchedtag { + schedtagObj, _ := SchedtagManager.FetchById(id) + if schedtagObj == nil { + return nil + } + return schedtagObj.(*SSchedtag) +} + +func (man *SSchedtagJointsManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + schedtagId, err := data.GetString("schedtag_id") + if err != nil || schedtagId == "" { + return nil, httperrors.NewInputParameterError("schedtag_id not provide") + } + resourceType := man.GetMasterManager().KeywordPlural() + if !utils.IsInStringArray(resourceType, SchedtagManager.GetResourceTypes()) { + return nil, httperrors.NewInputParameterError("Not support resource_type %s", resourceType) + } + schedtag := man.FetchSchedtagById(schedtagId) + if schedtag == nil { + return nil, httperrors.NewNotFoundError("Schedtag %s", schedtagId) + } + if resourceType != schedtag.ResourceType { + return nil, httperrors.NewInputParameterError("Schedtag %s resource_type mismatch: %s != %s", schedtag.GetName(), schedtag.ResourceType, resourceType) + } + return man.SJointResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) +} + +func (man *SSchedtagJointsManager) AllowListDescendent(ctx context.Context, userCred mcclient.TokenCredential, model db.IStandaloneModel, query jsonutils.JSONObject) bool { + return db.IsAdminAllowList(userCred, man) +} + +func (man *SSchedtagJointsManager) GetMasterIdKey(m db.IJointModelManager) string { + return fmt.Sprintf("%s_id", m.GetMasterManager().Keyword()) +} + +func (man *SSchedtagJointsManager) AllowAttach(ctx context.Context, userCred mcclient.TokenCredential, master db.IStandaloneModel, slave db.IStandaloneModel) bool { + return db.IsAdminAllowCreate(userCred, man) +} + +func (self *SSchedtagJointsBase) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowGet(userCred, self) +} + +func (self *SSchedtagJointsBase) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return db.IsAdminAllowUpdate(userCred, self) +} + +func (self *SSchedtagJointsBase) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowDelete(userCred, self) +} + +func (joint *SSchedtagJointsBase) master(obj db.IJointModel) db.IStandaloneModel { + return db.JointMaster(obj) +} + +func (joint *SSchedtagJointsBase) GetSchedtag() *SSchedtag { + return joint.Slave().(*SSchedtag) +} + +func (joint *SSchedtagJointsBase) Slave() db.IStandaloneModel { + return db.JointSlave(joint) +} + +func (joint *SSchedtagJointsBase) getCustomizeColumns(obj db.IJointModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := joint.SJointResourceBase.GetCustomizeColumns(ctx, userCred, query) + return db.JointModelExtra(obj, extra) +} + +func (joint *SSchedtagJointsBase) getExtraDetails(obj db.IJointModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + extra, err := joint.SJointResourceBase.GetExtraDetails(ctx, userCred, query) + if err != nil { + return nil, err + } + return db.JointModelExtra(obj, extra), nil +} + +func (joint *SSchedtagJointsBase) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return fmt.Errorf("Delete must be override") +} + +func (joint *SSchedtagJointsBase) delete(obj db.IJointModel, ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, joint) +} + +func (joint *SSchedtagJointsBase) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return fmt.Errorf("Detach must be override") +} + +func (joint *SSchedtagJointsBase) detach(obj db.IJointModel, ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DetachJoint(ctx, userCred, joint) +} diff --git a/pkg/compute/models/schedtags.go b/pkg/compute/models/schedtags.go index 8d56e24e4b..a5a3c68024 100644 --- a/pkg/compute/models/schedtags.go +++ b/pkg/compute/models/schedtags.go @@ -3,6 +3,7 @@ package models import ( "context" "database/sql" + "fmt" "strings" "yunion.io/x/jsonutils" @@ -29,8 +30,15 @@ const ( var STRATEGY_LIST = []string{STRATEGY_REQUIRE, STRATEGY_EXCLUDE, STRATEGY_PREFER, STRATEGY_AVOID} +type ISchedtagJointManager interface { + db.IJointModelManager + GetMasterIdKey(db.IJointModelManager) string +} + type SSchedtagManager struct { db.SStandaloneResourceBaseManager + + jointsManager map[string]ISchedtagJointManager } var SchedtagManager *SSchedtagManager @@ -43,19 +51,64 @@ func init() { "schedtag", "schedtags", ), + jointsManager: make(map[string]ISchedtagJointManager), } } +func (manager *SSchedtagManager) InitializeData() error { + // set old schedtags resource_type to hosts + schedtags := []SSchedtag{} + q := manager.Query().IsNullOrEmpty("resource_type") + err := db.FetchModelObjects(manager, q, &schedtags) + if err != nil { + return err + } + for _, tag := range schedtags { + tmp := &tag + db.Update(tmp, func() error { + tmp.ResourceType = HostManager.KeywordPlural() + return nil + }) + } + manager.BindJointManagers( + HostschedtagManager, + StorageschedtagManager, + ) + return nil +} + +func (manager *SSchedtagManager) BindJointManagers(ms ...ISchedtagJointManager) { + for _, m := range ms { + manager.jointsManager[m.GetMasterManager().KeywordPlural()] = m + } +} + +func (manager *SSchedtagManager) GetResourceTypes() []string { + ret := []string{} + for key := range manager.jointsManager { + ret = append(ret, key) + } + return ret +} + type SSchedtag struct { db.SStandaloneResourceBase DefaultStrategy string `width:"16" charset:"ascii" nullable:"true" default:"" list:"user" update:"admin" create:"admin_optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True, default='') + ResourceType string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"required"` // Column(VARCHAR(16, charset='ascii'), nullable=True, default='') } func (manager *SSchedtagManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { return true } +func (manager *SSchedtagManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + if resType := jsonutils.GetAnyString(query, []string{"type", "resource_type"}); resType != "" { + q = q.Equals("resource_type", resType) + } + return manager.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query) +} + func (self *SSchedtag) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { return true } @@ -111,9 +164,30 @@ func (manager *SSchedtagManager) ValidateCreateData(ctx context.Context, userCre return nil, err } } + // set resourceType to hosts if not provided by client + resourceType, _ := data.GetString("resource_type") + if resourceType == "" { + resourceType = HostManager.KeywordPlural() + data.Set("resource_type", jsonutils.NewString(resourceType)) + } + if !utils.IsInStringArray(resourceType, manager.GetResourceTypes()) { + return nil, httperrors.NewInputParameterError("Not support resource_type %s", resourceType) + } return manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) } +func (manager *SSchedtagManager) GetResourceSchedtags(resType string) ([]SSchedtag, error) { + jointMan := manager.jointsManager[resType] + if jointMan == nil { + return nil, fmt.Errorf("Not found joint manager by resource type: %s", resType) + } + tags := make([]SSchedtag, 0) + if err := manager.Query().Equals("resource_type", resType).All(&tags); err != nil { + return nil, err + } + return tags, nil +} + func (self *SSchedtag) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { defStrategy, _ := data.GetString("default_strategy") if len(defStrategy) > 0 { @@ -126,8 +200,8 @@ func (self *SSchedtag) ValidateUpdateData(ctx context.Context, userCred mcclient } func (self *SSchedtag) ValidateDeleteCondition(ctx context.Context) error { - if self.GetHostCount() > 0 { - return httperrors.NewNotEmptyError("Tag is associated with hosts") + if self.GetObjectCount() > 0 { + return httperrors.NewNotEmptyError("Tag is associated with %s", self.ResourceType) } if self.getDynamicSchedtagCount() > 0 { return httperrors.NewNotEmptyError("tag has dynamic rules") @@ -147,30 +221,35 @@ func (self *SSchedtag) AllowDeleteItem(ctx context.Context, userCred mcclient.To return userCred.IsSystemAdmin() }*/ -func (self *SSchedtag) GetHosts() []SHost { - q := self.GetHostQuery() - hosts := make([]SHost, 0) - err := db.FetchModelObjects(HostManager, q, &hosts) +func (self *SSchedtag) GetObjects(objs interface{}) error { + q := self.GetObjectQuery() + masterMan := self.GetJointManager().GetMasterManager() + err := db.FetchModelObjects(masterMan, q, objs) if err != nil { - log.Errorf("GetHosts query fail %s", err) - return nil + return err } - return hosts + return nil } -func (self *SSchedtag) GetHostQuery() *sqlchemy.SQuery { - hosts := HostManager.Query().SubQuery() - hostschedtags := HostschedtagManager.Query().SubQuery() - q := hosts.Query() - q = q.Join(hostschedtags, sqlchemy.AND(sqlchemy.Equals(hostschedtags.Field("host_id"), hosts.Field("id")), - sqlchemy.IsFalse(hostschedtags.Field("deleted")))) - q = q.Filter(sqlchemy.IsTrue(hosts.Field("enabled"))) - q = q.Filter(sqlchemy.Equals(hostschedtags.Field("schedtag_id"), self.Id)) +func (self *SSchedtag) GetObjectQuery() *sqlchemy.SQuery { + jointMan := self.GetJointManager() + masterMan := jointMan.GetMasterManager() + objs := masterMan.Query().SubQuery() + objschedtags := jointMan.Query().SubQuery() + q := objs.Query() + q = q.Join(objschedtags, sqlchemy.AND(sqlchemy.Equals(objschedtags.Field(jointMan.GetMasterIdKey(jointMan)), objs.Field("id")), + sqlchemy.IsFalse(objschedtags.Field("deleted")))) + q = q.Filter(sqlchemy.IsTrue(objs.Field("enabled"))) + q = q.Filter(sqlchemy.Equals(objschedtags.Field("schedtag_id"), self.Id)) return q } -func (self *SSchedtag) GetHostCount() int { - return HostschedtagManager.Query().Equals("schedtag_id", self.Id).Count() +func (self *SSchedtag) GetJointManager() ISchedtagJointManager { + return SchedtagManager.jointsManager[self.ResourceType] +} + +func (self *SSchedtag) GetObjectCount() int { + return self.GetJointManager().Query().Equals("schedtag_id", self.Id).Count() } func (self *SSchedtag) getSchedPoliciesCount() int { @@ -182,7 +261,7 @@ func (self *SSchedtag) getDynamicSchedtagCount() int { } func (self *SSchedtag) getMoreColumns(extra *jsonutils.JSONDict) *jsonutils.JSONDict { - extra.Add(jsonutils.NewInt(int64(self.GetHostCount())), "host_count") + extra.Add(jsonutils.NewInt(int64(self.GetObjectCount())), fmt.Sprintf("%s_count", self.GetJointManager().GetMasterManager().Keyword())) extra.Add(jsonutils.NewInt(int64(self.getDynamicSchedtagCount())), "dynamic_schedtag_count") extra.Add(jsonutils.NewInt(int64(self.getSchedPoliciesCount())), "schedpolicy_count") return extra @@ -210,3 +289,19 @@ func (self *SSchedtag) GetShortDesc(ctx context.Context) *jsonutils.JSONDict { desc.Add(jsonutils.NewString(self.DefaultStrategy), "default") return desc } + +func GetSchedtags(jointMan ISchedtagJointManager, masterId string) []SSchedtag { + tags := make([]SSchedtag, 0) + schedtags := SchedtagManager.Query().SubQuery() + objschedtags := jointMan.Query().SubQuery() + q := schedtags.Query() + q = q.Join(objschedtags, sqlchemy.AND(sqlchemy.Equals(objschedtags.Field("schedtag_id"), schedtags.Field("id")), + sqlchemy.IsFalse(objschedtags.Field("deleted")))) + q = q.Filter(sqlchemy.Equals(objschedtags.Field(jointMan.GetMasterIdKey(jointMan)), masterId)) + err := db.FetchModelObjects(SchedtagManager, q, &tags) + if err != nil { + log.Errorf("GetSchedtags error: %s", err) + return nil + } + return tags +} diff --git a/pkg/compute/models/storages.go b/pkg/compute/models/storages.go index 25ca4ba068..6357910f10 100644 --- a/pkg/compute/models/storages.go +++ b/pkg/compute/models/storages.go @@ -1158,3 +1158,7 @@ func (self *SStorage) IsPrepaidRecycleResource() bool { } return hosts[0].IsPrepaidRecycleResource() } + +func (self *SStorage) GetSchedtags() []SSchedtag { + return GetSchedtags(StorageschedtagManager, self.Id) +} diff --git a/pkg/compute/models/storageschedtags.go b/pkg/compute/models/storageschedtags.go new file mode 100644 index 0000000000..e7ccc47aee --- /dev/null +++ b/pkg/compute/models/storageschedtags.go @@ -0,0 +1,66 @@ +package models + +import ( + "context" + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SStorageschedtagManager struct { + *SSchedtagJointsManager +} + +var StorageschedtagManager *SStorageschedtagManager + +func init() { + db.InitManager(func() { + StorageschedtagManager = &SStorageschedtagManager{ + SSchedtagJointsManager: NewSchedtagJointsManager( + SStorageschedtag{}, + "schedtag_storages_tbl", + "schedtagstorage", + "schedtagstorages", + StorageManager, + SchedtagManager, + ), + } + }) +} + +type SStorageschedtag struct { + SSchedtagJointsBase + + StorageId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) +} + +func (s *SStorageschedtag) GetStorage() *SStorage { + return s.Master().(*SStorage) +} + +func (s *SStorageschedtag) GetStorages() ([]SStorage, error) { + storages := []SStorage{} + err := s.GetSchedtag().GetObjects(&storages) + return storages, err +} + +func (joint *SStorageschedtag) Master() db.IStandaloneModel { + return joint.SSchedtagJointsBase.master(joint) +} + +func (joint *SStorageschedtag) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + return joint.SSchedtagJointsBase.getCustomizeColumns(joint, ctx, userCred, query) +} + +func (joint *SStorageschedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + return joint.SSchedtagJointsBase.getExtraDetails(joint, ctx, userCred, query) +} + +func (joint *SStorageschedtag) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return joint.SSchedtagJointsBase.delete(joint, ctx, userCred) +} + +func (joint *SStorageschedtag) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return joint.SSchedtagJointsBase.detach(joint, ctx, userCred) +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 5f4aee8bb9..52d9c5537c 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -96,6 +96,7 @@ func InitHandlers(app *appsrv.Application) { models.HostnetworkManager, models.HoststorageManager, models.HostschedtagManager, + models.StorageschedtagManager, models.GuestnetworkManager, models.GuestsecgroupManager, models.LoadbalancernetworkManager, diff --git a/pkg/mcclient/modules/mod_schedtaghosts.go b/pkg/mcclient/modules/mod_schedtaghosts.go index 6b745b5654..f22dd756b9 100644 --- a/pkg/mcclient/modules/mod_schedtaghosts.go +++ b/pkg/mcclient/modules/mod_schedtaghosts.go @@ -1,14 +1,28 @@ package modules var ( - Schedtaghosts JointResourceManager + Schedtaghosts JointResourceManager + Schedtagstorages JointResourceManager ) +func newSchedtagJointManager(keyword, keywordPlural string, columns, adminColumns []string, slave Manager) JointResourceManager { + columns = append(columns, "Schedtag_ID", "Schedtag") + return NewJointComputeManager(keyword, keywordPlural, + columns, adminColumns, &Schedtags, slave) +} + func init() { - Schedtaghosts = NewJointComputeManager("schedtaghost", "schedtaghosts", - []string{"Host_ID", "Host", "Schedtag_ID", "Schedtag"}, + Schedtaghosts = newSchedtagJointManager("schedtaghost", "schedtaghosts", + []string{"Host_ID", "Host"}, []string{}, - &Schedtags, &Hosts) + + Schedtagstorages = newSchedtagJointManager("schedtagstorage", "schedtagstorages", + []string{"Storage_ID", "Storage"}, + []string{}, + &Storages) + registerCompute(&Schedtaghosts) + + registerCompute(&Schedtagstorages) } diff --git a/pkg/mcclient/modules/mod_schedtags.go b/pkg/mcclient/modules/mod_schedtags.go index 88f7230069..849c5f4a73 100644 --- a/pkg/mcclient/modules/mod_schedtags.go +++ b/pkg/mcclient/modules/mod_schedtags.go @@ -55,7 +55,7 @@ func (this *SchedtagManager) DoBatchSchedtagHostAddRemove(s *mcclient.ClientSess func init() { Schedtags = SchedtagManager{NewComputeManager("schedtag", "schedtags", - []string{"ID", "Name", "Default_strategy"}, + []string{"ID", "Name", "Default_strategy", "Resource_type"}, []string{})} registerCompute(&Schedtags) diff --git a/pkg/mcclient/modules/modules.go b/pkg/mcclient/modules/modules.go index 7bbb90ec95..a0b252cee3 100644 --- a/pkg/mcclient/modules/modules.go +++ b/pkg/mcclient/modules/modules.go @@ -11,6 +11,7 @@ import ( type BaseManagerInterface interface { Version() string + GetKeyword() string KeyString() string ServiceType() string EndpointType() string diff --git a/pkg/mcclient/modules/resource.go b/pkg/mcclient/modules/resource.go index b092dfc49a..730b2681ef 100644 --- a/pkg/mcclient/modules/resource.go +++ b/pkg/mcclient/modules/resource.go @@ -33,6 +33,10 @@ type ResourceManager struct { idFieldName string } +func (this *ResourceManager) GetKeyword() string { + return this.Keyword +} + func (this *ResourceManager) KeyString() string { return this.KeywordPlural } diff --git a/pkg/mcclient/options/schedtags.go b/pkg/mcclient/options/schedtags.go new file mode 100644 index 0000000000..43ca8e0161 --- /dev/null +++ b/pkg/mcclient/options/schedtags.go @@ -0,0 +1,23 @@ +package options + +import ( + "yunion.io/x/jsonutils" +) + +type SchedtagModelListOptions struct { + BaseListOptions + Schedtag string `help:"ID or Name of schedtag"` +} + +func (o SchedtagModelListOptions) Params() (*jsonutils.JSONDict, error) { + params, err := o.BaseListOptions.Params() + if err != nil { + return nil, err + } + return params, nil +} + +type SchedtagModelPairOptions struct { + SCHEDTAG string `help:"Scheduler tag"` + OBJECT string `help:"Object id"` +} diff --git a/pkg/mcclient/options/schedulers.go b/pkg/mcclient/options/schedulers.go index bbf92b0ea4..85832ef394 100644 --- a/pkg/mcclient/options/schedulers.go +++ b/pkg/mcclient/options/schedulers.go @@ -13,10 +13,11 @@ type SchedulerTestBaseOptions struct { Mem int64 `help:"Memory size (MB), default 512" metavar:"MEMORY" default:"512"` Ncpu int64 `help:"#CPU cores of VM server, default 1" default:"1" metavar:""` Disk []string `help:"Disk descriptions" nargs:"+"` + DiskSchedtag []string `help:"Disk schedtag description, e.g. '0::'"` BaremetalDiskConfig []string `help:"Baremetal disk layout configuration"` Net []string `help:"Network descriptions" metavar:"NETWORK"` IsolatedDevice []string `help:"Isolated device model or ID" metavar:"ISOLATED_DEVICE"` - SchedTag []string `help:"Schedule policy, key = SchedTag name, value = require|exclude|prefer|avoid" metavar:""` + Schedtag []string `help:"Schedule policy, key = SchedTag name, value = require|exclude|prefer|avoid" metavar:""` Zone string `help:"Preferred zone where virtual server should be created"` Host string `help:"Preferred host where virtual server should be created"` Project string `help:"Owner project ID or Name"` @@ -48,8 +49,8 @@ func (o SchedulerTestBaseOptions) data(s *mcclient.ClientSession) (*jsonutils.JS if len(o.Zone) > 0 { data.Add(jsonutils.NewString(o.Zone), "prefer_zone") } - if len(o.SchedTag) > 0 { - for i, aggr := range o.SchedTag { + if len(o.Schedtag) > 0 { + for i, aggr := range o.Schedtag { data.Add(jsonutils.NewString(aggr), fmt.Sprintf("aggregate.%d", i)) } } diff --git a/pkg/scheduler/algorithm/predicates/aggregate_predicate.go b/pkg/scheduler/algorithm/predicates/aggregate_predicate.go index 6563aaf7b0..bcd477b839 100644 --- a/pkg/scheduler/algorithm/predicates/aggregate_predicate.go +++ b/pkg/scheduler/algorithm/predicates/aggregate_predicate.go @@ -1,17 +1,13 @@ package predicates import ( - "fmt" - "yunion.io/x/jsonutils" - "yunion.io/x/log" + computemodels "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/scheduler/algorithm/plugin" "yunion.io/x/onecloud/pkg/scheduler/api" "yunion.io/x/onecloud/pkg/scheduler/core" "yunion.io/x/onecloud/pkg/scheduler/core/score" - "yunion.io/x/onecloud/pkg/scheduler/db/models" - "yunion.io/x/onecloud/pkg/util/conditionparser" ) // NOTE: Aggregate Description @@ -26,101 +22,16 @@ import ( type AggregatePredicate struct { BasePredicate plugin.BasePlugin - AggregateHosts hostsAggregatesMap - RequireAggregates []api.Aggregate - ExcludeAggregates []api.Aggregate - AvoidAggregates []api.Aggregate - PreferAggregates []api.Aggregate - AggregateMap map[string]api.Aggregate + + SchedtagPredicate *SchedtagPredicate } -type hostAggregates []*models.Aggregate - -type hostsAggregatesMap map[string]hostAggregates - func (p *AggregatePredicate) Name() string { return "host_aggregate" } func (p *AggregatePredicate) Clone() core.FitPredicate { - return &AggregatePredicate{ - AggregateMap: make(map[string]api.Aggregate, 0), - } -} - -func getHostAndServerSchedDesc(u *core.Unit, c core.Candidater) *jsonutils.JSONDict { - ret := jsonutils.NewDict() - hostSchedDesc := c.GetSchedDesc() - srvSchedDesc := jsonutils.Marshal(u.SchedData()) - ret.Add(hostSchedDesc, "host") - ret.Add(srvSchedDesc, "server") - return ret -} - -func getHostDynamicSchedtags(u *core.Unit, c core.Candidater) ([]*models.Aggregate, error) { - schedDesc := getHostAndServerSchedDesc(u, c) - - dynamicTags, err := models.FetchEnabledDynamicschedtags() - if err != nil { - return nil, err - } - aggs := []*models.Aggregate{} - for _, tag := range dynamicTags { - matched, err := conditionparser.Eval(tag.Condition, schedDesc) - if err != nil { - log.Errorf("Condition parse eval: condition: %q, desc: %s, error: %v", tag.Condition, schedDesc, err) - continue - } - if !matched { - continue - } - aggregate, err := tag.FetchSchedTag() - if err != nil { - log.Errorf("Get dynamic schedtag %q error: %v", tag.SchedtagId, err) - continue - } - aggs = append(aggs, aggregate) - } - return aggs, nil -} - -func mergeHostSchedtags(c core.Candidater, staticTags, dynamicTags []*models.Aggregate) []*models.Aggregate { - isIn := func(tags []*models.Aggregate, dt *models.Aggregate) bool { - for _, t := range tags { - if t.ID == dt.ID { - return true - } - } - return false - } - ret := []*models.Aggregate{} - ret = append(ret, staticTags...) - for _, dt := range dynamicTags { - if !isIn(staticTags, dt) { - ret = append(ret, dt) - log.Debugf("Append dynamic schedtag %s to host %q", dt, c.IndexKey()) - } - } - return ret -} - -func hostsAggregatesInfo(u *core.Unit, cs []core.Candidater) (hostsAggregatesMap, []*models.Aggregate) { - ret := make(map[string]hostAggregates, 0) - allAggs := make([]*models.Aggregate, 0) - for _, c := range cs { - hostAggs := c.GetHostAggregates() - dynamicMatchedAggs, err := getHostDynamicSchedtags(u, c) - if err != nil { - log.Errorf("Get host %q dynamic schedtag error: %v", c.IndexKey(), err) - } else { - hostAggs = mergeHostSchedtags(c, hostAggs, dynamicMatchedAggs) - } - ret[c.IndexKey()] = hostAggs - } - if len(cs) > 0 { - allAggs = cs[0].GetAggregates() - } - return ret, allAggs + return &AggregatePredicate{} } func (p *AggregatePredicate) PreExecute(u *core.Unit, cs []core.Candidater) (bool, error) { @@ -130,72 +41,16 @@ func (p *AggregatePredicate) PreExecute(u *core.Unit, cs []core.Candidater) (boo return false, nil } - hsMap, allAggs := hostsAggregatesInfo(u, cs) - p.AggregateHosts = hsMap - appendedAggIds := make(map[string]int, len(data.Aggregates)) - - for _, aggregate := range data.Aggregates { - switch aggregate.Strategy { - case api.AggregateStrategyRequire: - p.RequireAggregates = append(p.RequireAggregates, aggregate) - case api.AggregateStrategyExclude: - p.ExcludeAggregates = append(p.ExcludeAggregates, aggregate) - - case api.AggregateStrategyPrefer: - p.PreferAggregates = append(p.PreferAggregates, aggregate) - - case api.AggregateStrategyAvoid: - p.AvoidAggregates = append(p.AvoidAggregates, aggregate) - } - - p.AggregateMap[aggregate.Idx] = aggregate - appendedAggIds[aggregate.Idx] = 1 - } - - for _, aggregate := range allAggs { - _, nameOk := appendedAggIds[aggregate.Name] - _, idOk := appendedAggIds[aggregate.ID] - if !(nameOk || idOk) { - agg := api.Aggregate{Idx: aggregate.ID, Strategy: aggregate.DefaultStrategy} - switch agg.Strategy { - case api.AggregateStrategyRequire: - p.RequireAggregates = append(p.RequireAggregates, agg) - case api.AggregateStrategyExclude: - p.ExcludeAggregates = append(p.ExcludeAggregates, agg) - - case api.AggregateStrategyPrefer: - p.PreferAggregates = append(p.PreferAggregates, agg) - - case api.AggregateStrategyAvoid: - p.AvoidAggregates = append(p.AvoidAggregates, agg) - } - } + allAggs, err := GetAllSchedtags(computemodels.HostManager.KeywordPlural()) + if err != nil { + return false, err } + p.SchedtagPredicate = NewSchedtagPredicate(data.Schedtags, allAggs) u.AppendSelectPlugin(p) return true, nil } -func getHostAggregateCount(inAggs []api.Aggregate, hAggs []*models.Aggregate, strategy string) (countMap map[string]int) { - countMap = make(map[string]int) - - in := func(hAgg *models.Aggregate, inAggs []api.Aggregate) bool { - for _, agg := range inAggs { - if agg.Idx == hAgg.ID || agg.Idx == hAgg.Name { - return true - } - } - return false - } - - for _, hAgg := range hAggs { - if in(hAgg, inAggs) { - countMap[fmt.Sprintf("%s:%s:%s", hAgg.ID, hAgg.Name, strategy)]++ - } - } - return -} - func (p *AggregatePredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason, error) { h := NewPredicateHelper(p, u, c) @@ -206,66 +61,46 @@ func (p *AggregatePredicate) Execute(u *core.Unit, c core.Candidater) (bool, []c return h.GetResult() } +type schedtagCandidateW struct { + core.Candidater + schedData *api.SchedData +} + +func (w schedtagCandidateW) GetDynamicSchedDesc() *jsonutils.JSONDict { + ret := jsonutils.NewDict() + hostSchedDesc := w.GetSchedDesc() + srvSchedDesc := jsonutils.Marshal(w.schedData) + ret.Add(hostSchedDesc, "host") + ret.Add(srvSchedDesc, "server") + return ret +} + +func (w schedtagCandidateW) GetSchedtags() []computemodels.SSchedtag { + return w.Getter().HostSchedtags() +} + +func (w schedtagCandidateW) ResourceType() string { + return computemodels.HostManager.KeywordPlural() +} + func (p *AggregatePredicate) exec(h *PredicateHelper) string { - ahs := p.AggregateHosts - candidateID := h.Candidate.IndexKey() - - log.V(10).Debugf(">>>> ExcludeAggregates: %#v, RequireAggregates: %#v, AvoidAggregates: %#v, PreferAggregates: %#v, candidateID: %v", p.ExcludeAggregates, p.RequireAggregates, p.AvoidAggregates, p.PreferAggregates, candidateID) - - if len(p.ExcludeAggregates) > 0 { - inExclude := func(a *models.Aggregate) bool { - for _, agg := range p.ExcludeAggregates { - if agg.Idx == a.ID || agg.Idx == a.Name { - return true - } - } - - return false - } - - if ah, ok := ahs[candidateID]; ok { - for _, a := range ah { - if inExclude(a) { - return fmt.Sprintf("exclude by aggregate: '%s:%s'", a.Name, a.ID) - } - } - } - } - - if len(p.RequireAggregates) > 0 { - var as []*models.Aggregate = nil - if ah, ok := ahs[candidateID]; ok { - as = ah - } - - inRequire := func(agg api.Aggregate) bool { - for _, a := range as { - if a.ID == agg.Idx || a.Name == agg.Idx { - return true - } - } - - return false - } - - for _, agg := range p.RequireAggregates { - if !inRequire(agg) { - return fmt.Sprintf("need aggregate: '%s'", agg.Idx) - } - } + if err := p.SchedtagPredicate.Check( + schedtagCandidateW{ + Candidater: h.Candidate, + schedData: h.Unit.SchedData(), + }, + ); err != nil { + return err.Error() } return "" } func (p *AggregatePredicate) OnPriorityEnd(u *core.Unit, c core.Candidater) { - hostAggs, ok := p.AggregateHosts[c.IndexKey()] - if !ok { - return - } + hostAggs := c.Getter().HostSchedtags() - avoidCountMap := getHostAggregateCount(p.AvoidAggregates, hostAggs, api.AggregateStrategyAvoid) - preferCountMap := getHostAggregateCount(p.PreferAggregates, hostAggs, api.AggregateStrategyPrefer) + avoidCountMap := GetSchedtagCount(p.SchedtagPredicate.GetAvoidTags(), hostAggs, api.AggregateStrategyAvoid) + preferCountMap := GetSchedtagCount(p.SchedtagPredicate.GetPreferTags(), hostAggs, api.AggregateStrategyPrefer) setScore := func(aggCountMap map[string]int, postiveScore bool) { stepScore := core.PriorityStep diff --git a/pkg/scheduler/algorithm/predicates/baremetal/base.go b/pkg/scheduler/algorithm/predicates/baremetal/base.go index 2e1e55ebb7..b5aef10654 100644 --- a/pkg/scheduler/algorithm/predicates/baremetal/base.go +++ b/pkg/scheduler/algorithm/predicates/baremetal/base.go @@ -1,9 +1,9 @@ package baremetal import ( - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/algorithm/predicates" "yunion.io/x/onecloud/pkg/scheduler/core" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type BasePredicate struct { diff --git a/pkg/scheduler/algorithm/predicates/baremetal/network_predicate.go b/pkg/scheduler/algorithm/predicates/baremetal/network_predicate.go index 973244cc0b..62191c39ae 100644 --- a/pkg/scheduler/algorithm/predicates/baremetal/network_predicate.go +++ b/pkg/scheduler/algorithm/predicates/baremetal/network_predicate.go @@ -61,29 +61,29 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor var errMsgs []string for _, network := range candidate.Networks { appendError := func(errMsg string) { - errMsgs = append(errMsgs, fmt.Sprintf("%s: %s", network.ID, errMsg)) + errMsgs = append(errMsgs, fmt.Sprintf("%s: %s", network.Id, errMsg)) } - if !((network.Ports > 0 || isMigrate()) && network.IsExit == exit) { + if !((network.GetPorts() > 0 || isMigrate()) && network.IsExitNetwork() == exit) { appendError(predicates.ErrNoPorts) } - if wire != "" && !utils.HasPrefix(wire, network.Wire) && !utils.HasPrefix(wire, network.WireID) { // re + if wire != "" && !utils.HasPrefix(wire, network.WireId) && !utils.HasPrefix(wire, network.GetWire().GetName()) { // re appendError(predicates.ErrWireIsNotMatch) } - if (!private && network.IsPublic) || (private && !network.IsPublic && network.TenantID == schedData.OwnerTenantID) { + if (!private && network.IsPublic) || (private && !network.IsPublic && network.ProjectId == schedData.OwnerTenantID) { // TODO: support reservedNetworks reservedNetworks := 0 - restPort := int64(network.Ports - reservedNetworks) + restPort := int64(network.GetPorts() - reservedNetworks) if restPort == 0 { appendError("not enough network port") continue } - counter := u.CounterManager.GetOrCreate("net:"+network.ID, func() core.Counter { + counter := u.CounterManager.GetOrCreate("net:"+network.Id, func() core.Counter { return core.NewNormalCounter(restPort) }) - u.SharedResourceManager.Add(network.ID, counter) + u.SharedResourceManager.Add(network.Id, counter) counters.Add(counter) - p.SelectedNetworks.Store(network.ID, counter.GetCount()) + p.SelectedNetworks.Store(network.Id, counter.GetCount()) return "" } else { appendError(predicates.ErrNotOwner) @@ -105,7 +105,7 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor return isRandomNetworkAvailable(network.Private, network.Exit, network.Wire) } for _, net := range candidate.Networks { - if (network.Idx == net.ID || network.Idx == net.Name) && (net.IsPublic || net.TenantID == schedData.OwnerTenantID) && (net.Ports > 0 || isMigrate()) { + if (network.Idx == net.Id || network.Idx == net.Name) && (net.IsPublic || net.ProjectId == schedData.OwnerTenantID) && (net.GetPorts() > 0 || isMigrate()) { h.SetCapacity(1) return "" } diff --git a/pkg/scheduler/algorithm/predicates/disk_schedtag_predicate.go b/pkg/scheduler/algorithm/predicates/disk_schedtag_predicate.go new file mode 100644 index 0000000000..e6ba918a9e --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/disk_schedtag_predicate.go @@ -0,0 +1,168 @@ +package predicates + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/errors" + + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/scheduler/algorithm/plugin" + "yunion.io/x/onecloud/pkg/scheduler/api" + "yunion.io/x/onecloud/pkg/scheduler/core" +) + +type DiskStoragesMap map[int][]*api.CandidateStorage + +type CandidateDiskStoragesMap map[string]DiskStoragesMap + +type DiskSchedtagPredicate struct { + BasePredicate + plugin.BasePlugin + + SchedtagPredicate *SchedtagPredicate + + CandidateDiskStoragesMap CandidateDiskStoragesMap +} + +func (p *DiskSchedtagPredicate) Name() string { + return "disk_schedtag" +} + +func (p *DiskSchedtagPredicate) Clone() core.FitPredicate { + return &DiskSchedtagPredicate{ + CandidateDiskStoragesMap: make(map[string]DiskStoragesMap), + } +} + +func (p *DiskSchedtagPredicate) getSchedtagDisks(disks []*api.Disk) ([]*api.Disk, []*api.Disk) { + noTagDisk := make([]*api.Disk, 0) + tagDisk := make([]*api.Disk, 0) + for _, d := range disks { + if len(d.Schedtags) != 0 { + tagDisk = append(tagDisk, d) + } else { + noTagDisk = append(noTagDisk, d) + } + } + return noTagDisk, tagDisk +} + +func (p *DiskSchedtagPredicate) PreExecute(u *core.Unit, cs []core.Candidater) (bool, error) { + disks := u.SchedData().Disks + if len(disks) == 0 { + return false, nil + } + + // always select each storages to disks + u.AppendSelectPlugin(p) + + return true, nil +} + +type schedtagStorageW struct { + candidater *api.CandidateStorage + disk *api.Disk +} + +func (w schedtagStorageW) IndexKey() string { + return fmt.Sprintf("%d:%s", w.disk.Size, w.disk.Backend) +} + +func (w schedtagStorageW) GetDynamicSchedDesc() *jsonutils.JSONDict { + return nil +} + +func (w schedtagStorageW) GetSchedtags() []models.SSchedtag { + return w.candidater.Schedtags +} + +func (w schedtagStorageW) ResourceType() string { + return models.StorageManager.KeywordPlural() +} + +func (p *DiskSchedtagPredicate) check(d *api.Disk, s *api.CandidateStorage) (bool, error) { + allTags, err := GetAllSchedtags(models.StorageManager.KeywordPlural()) + if err != nil { + return false, err + } + tagPredicate := NewSchedtagPredicate(d.Schedtags, allTags) + if err := tagPredicate.Check( + schedtagStorageW{ + candidater: s, + disk: d, + }, + ); err != nil { + return false, err + } + return true, nil +} + +func (p *DiskSchedtagPredicate) checkStorages(d *api.Disk, storages []*api.CandidateStorage) ([]*api.CandidateStorage, error) { + errs := make([]error, 0) + ret := make([]*api.CandidateStorage, 0) + for _, s := range storages { + _, err := p.check(d, s) + if err != nil { + // append err, storage not suit disk + errs = append(errs, err) + continue + } + ret = append(ret, s) + } + if len(ret) == 0 { + return nil, errors.NewAggregate(errs) + } + return ret, nil +} + +func (p *DiskSchedtagPredicate) GetDiskStoragesMap(candidateId string) DiskStoragesMap { + ret, ok := p.CandidateDiskStoragesMap[candidateId] + if !ok { + ret = make(map[int][]*api.CandidateStorage) + p.CandidateDiskStoragesMap[candidateId] = ret + } + return ret +} + +func (p *DiskSchedtagPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason, error) { + h := NewPredicateHelper(p, u, c) + + //noTagDisks, tagDisks := p.getSchedtagDisks(u.SchedData().Disks) + storages := c.Getter().Storages() + ds := p.GetDiskStoragesMap(c.IndexKey()) + disks := u.SchedData().Disks + for _, d := range disks { + matchedStorages, err := p.checkStorages(d, storages) + if err != nil { + h.Exclude(err.Error()) + } + ds[d.Index] = matchedStorages + } + + return h.GetResult() +} + +func (p *DiskSchedtagPredicate) OnSelectEnd(u *core.Unit, c core.Candidater, count int64) { + res := u.GetAllocatedResource(c.IndexKey()) + diskStorages := p.GetDiskStoragesMap(c.IndexKey()) + res.Disks = make([]*core.DiskAllocatedResource, len(diskStorages)) + disks := u.SchedData().Disks + for idx, ds := range diskStorages { + res.Disks[idx] = p.allocatedDiskResource(c, disks[idx], ds) + } + log.Errorf("============OnSelectEnd %s called: %#v", c.Getter().Name(), jsonutils.Marshal(res.Disks).String()) +} + +func (p *DiskSchedtagPredicate) allocatedDiskResource(c core.Candidater, disk *api.Disk, storages []*api.CandidateStorage) *core.DiskAllocatedResource { + storage := p.selectStorage(disk, storages) + return &core.DiskAllocatedResource{ + Index: disk.Index, + StorageId: storage.Id, + } +} + +func (p *DiskSchedtagPredicate) selectStorage(d *api.Disk, storages []*api.CandidateStorage) *api.CandidateStorage { + return storages[0] +} diff --git a/pkg/scheduler/algorithm/predicates/guest/hypervisor_predicate.go b/pkg/scheduler/algorithm/predicates/guest/hypervisor_predicate.go index 788d0f0ca2..eb0cfe9c22 100644 --- a/pkg/scheduler/algorithm/predicates/guest/hypervisor_predicate.go +++ b/pkg/scheduler/algorithm/predicates/guest/hypervisor_predicate.go @@ -27,7 +27,7 @@ func (f *HypervisorPredicate) Clone() core.FitPredicate { } func hostHasContainerTag(c core.Candidater) bool { - aggs := c.GetHostAggregates() + aggs := c.Getter().HostSchedtags() for _, agg := range aggs { if agg.Name == CONTAINER_ALLOWED_TAG { return true diff --git a/pkg/scheduler/algorithm/predicates/guest/network_predicate.go b/pkg/scheduler/algorithm/predicates/guest/network_predicate.go index c90f700f4e..cd42c648f1 100644 --- a/pkg/scheduler/algorithm/predicates/guest/network_predicate.go +++ b/pkg/scheduler/algorithm/predicates/guest/network_predicate.go @@ -8,11 +8,12 @@ import ( "yunion.io/x/pkg/util/sets" "yunion.io/x/pkg/utils" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/scheduler/algorithm/plugin" "yunion.io/x/onecloud/pkg/scheduler/algorithm/predicates" "yunion.io/x/onecloud/pkg/scheduler/api" "yunion.io/x/onecloud/pkg/scheduler/core" - networks "yunion.io/x/onecloud/pkg/scheduler/db/models" ) // NetworkPredicate will filter the current network information with @@ -56,16 +57,16 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor } // ServerType's value is 'guest', 'container' or ''(support all type) will return true. - isMatchServerType := func(network *networks.NetworkSchedResult) bool { + isMatchServerType := func(network *models.SNetwork) bool { return sets.NewString("guest", "", "container").Has(network.ServerType) } - counterOfNetwork := func(u *core.Unit, n *networks.NetworkSchedResult, r int) core.Counter { - counter := u.CounterManager.GetOrCreate("net:"+n.ID, func() core.Counter { - return core.NewNormalCounter(int64(n.Ports - r)) + counterOfNetwork := func(u *core.Unit, n *models.SNetwork, r int) core.Counter { + counter := u.CounterManager.GetOrCreate("net:"+n.Id, func() core.Counter { + return core.NewNormalCounter(int64(n.GetPorts() - r)) }) - u.SharedResourceManager.Add(n.ID, counter) + u.SharedResourceManager.Add(n.GetId(), counter) return counter } @@ -81,36 +82,36 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor errMsgs = append(errMsgs, errMsg) } - if !isMatchServerType(n) { + if !isMatchServerType(&n) { appendError(predicates.ErrServerTypeIsNotMatch) } - if n.IsExit != exit { + if n.IsExitNetwork() != exit { appendError(predicates.ErrExitIsNotMatch) } - if !(n.Ports > 0 || isMigrate()) { + if !(n.GetPorts() > 0 || isMigrate()) { appendError(predicates.ErrNoPorts) } - if wire != "" && !utils.HasPrefix(wire, n.Wire) && !utils.HasPrefix(wire, n.WireID) { // re + if wire != "" && !utils.HasPrefix(wire, n.WireId) && !utils.HasPrefix(wire, n.GetWire().GetName()) { // re appendError(predicates.ErrWireIsNotMatch) } - if !((!private && n.IsPublic) || (private && !n.IsPublic && n.TenantID == d.OwnerTenantID)) { + if !((!private && n.IsPublic) || (private && !n.IsPublic && n.ProjectId == d.OwnerTenantID)) { appendError(predicates.ErrNotOwner) } if len(errMsgs) == 0 { // add resource reservedNetworks := 0 - counter := counterOfNetwork(u, n, reservedNetworks) - p.SelectedNetworks.Store(n.ID, counter.GetCount()) + counter := counterOfNetwork(u, &n, reservedNetworks) + p.SelectedNetworks.Store(n.GetId(), counter.GetCount()) counters.Add(counter) found = true } else { fullErrMsgs = append(fullErrMsgs, - fmt.Sprintf("%s: %s", n.ID, strings.Join(errMsgs, ",")), + fmt.Sprintf("%s: %s", n.Id, strings.Join(errMsgs, ",")), ) } } @@ -131,7 +132,7 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor } isNetworkAvaliable := func(n *api.Network, counters *core.MinCounters, - networks []*networks.NetworkSchedResult) string { + networks []models.SNetwork) string { if n.Idx == "" { counters0 := core.NewCounters() ret_msg := isRandomNetworkAvailable(n.Private, n.Exit, n.Wire, counters0) @@ -149,21 +150,21 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): server type not matched", net.Name, net.ID)) continue }*/ - if !(n.Idx == net.ID || n.Idx == net.Name) { - errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): id/name not matched", net.Name, net.ID)) - } else if !(net.IsPublic || net.TenantID == d.OwnerTenantID) { - errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): not owner (%v != %v)", net.Name, net.ID, net.TenantID, d.OwnerTenantID)) - } else if !(net.Ports > 0 || isMigrate()) { - errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): ports use up", net.Name, net.ID)) + if !(n.Idx == net.GetId() || n.Idx == net.GetName()) { + errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): id/name not matched", net.Name, net.Id)) + } else if !(net.IsPublic || net.ProjectId == d.OwnerTenantID) { + errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): not owner (%v != %v)", net.Name, net.Id, net.ProjectId, d.OwnerTenantID)) + } else if !(net.GetPorts() > 0 || isMigrate()) { + errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): ports use up", net.Name, net.Id)) } else { // add resource reservedNetworks := 0 - counter := counterOfNetwork(u, net, reservedNetworks) + counter := counterOfNetwork(u, &net, reservedNetworks) if counter.GetCount() < d.Count { errMsgs = append(errMsgs, fmt.Sprintf("%s: ports not enough, free: %d, required: %d", net.Name, counter.GetCount(), d.Count)) continue } - p.SelectedNetworks.Store(net.ID, counter.GetCount()) + p.SelectedNetworks.Store(net.Id, counter.GetCount()) counters.Add(counter) return "" } diff --git a/pkg/scheduler/algorithm/predicates/guest/storage_predicate.go b/pkg/scheduler/algorithm/predicates/guest/storage_predicate.go index 0a57a74285..30a84ea995 100644 --- a/pkg/scheduler/algorithm/predicates/guest/storage_predicate.go +++ b/pkg/scheduler/algorithm/predicates/guest/storage_predicate.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/scheduler/algorithm/predicates" @@ -52,7 +53,7 @@ func (p *StoragePredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor isStorageAccessible := func(storage string) bool { for _, s := range hc.Storages { - if storage == s.ID || storage == s.Name { + if storage == s.Id || storage == s.Name { return true } } @@ -82,9 +83,11 @@ func (p *StoragePredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor ss := []string{} for _, s := range hc.Storages { if s.StorageType == backend { - total := int64(float64(s.Capacity) * s.Cmtbound) - free := total - s.UsedCapacity - s.WasteCapacity - ss = append(ss, fmt.Sprintf("(%v-%v-%v=%v)", total, s.UsedCapacity, s.WasteCapacity, free)) + total := int64(float32(s.Capacity) * s.Cmtbound) + used := s.GetUsedCapacity(tristate.True) + waste := s.GetUsedCapacity(tristate.False) + free := total - int64(used) - int64(waste) + ss = append(ss, fmt.Sprintf("(%v-%v-%v=%v)", total, used, waste, free)) } } return strings.Join(ss, " + ") diff --git a/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go b/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go index 835ff0a74a..3eb5980417 100644 --- a/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go +++ b/pkg/scheduler/algorithm/predicates/k8s/network_predicate.go @@ -7,9 +7,11 @@ import ( "k8s.io/client-go/kubernetes" "yunion.io/x/pkg/util/errors" + "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/scheduler/cache/candidate" - "yunion.io/x/onecloud/pkg/scheduler/db/models" ) const ( @@ -56,13 +58,13 @@ func (p *NetworkPredicate) Execute(cli *kubernetes.Clientset, pod *v1.Pod, node return true, nil } -func (p NetworkPredicate) checkByNetworks(nets []*models.NetworkSchedResult) error { +func (p NetworkPredicate) checkByNetworks(nets []models.SNetwork) error { if len(nets) == 0 { return fmt.Errorf("Network is empty") } errs := make([]error, 0) for _, net := range nets { - err := p.checkByNetwork(net) + err := p.checkByNetwork(&net) if err == nil { return nil } @@ -71,23 +73,23 @@ func (p NetworkPredicate) checkByNetworks(nets []*models.NetworkSchedResult) err return errors.NewAggregate(errs) } -func (p NetworkPredicate) checkByNetwork(net *models.NetworkSchedResult) error { - if net.Ports <= 0 { +func (p NetworkPredicate) checkByNetwork(net *models.SNetwork) error { + if net.GetPorts() <= 0 { return fmt.Errorf("Network %s no free IPs", net.Name) } - if !(p.network == net.Name || p.network == net.ID) { - return fmt.Errorf("Network %s:%s or id not match %s", net.Name, net.ID, p.network) + if !(p.network == net.Name || p.network == net.Id) { + return fmt.Errorf("Network %s:%s or id not match %s", net.Name, net.Id, p.network) } return nil } -func (p NetworkPredicate) checkNetworksIP(ip string, nets []*models.NetworkSchedResult) error { +func (p NetworkPredicate) checkNetworksIP(ip string, nets []models.SNetwork) error { if len(nets) == 0 { return fmt.Errorf("Network is empty") } errs := make([]error, 0) for _, net := range nets { - err := p.checkNetworkIP(ip, net) + err := p.checkNetworkIP(ip, &net) if err == nil { return nil } @@ -96,10 +98,12 @@ func (p NetworkPredicate) checkNetworksIP(ip string, nets []*models.NetworkSched return errors.NewAggregate(errs) } -func (p NetworkPredicate) checkNetworkIP(ip string, net *models.NetworkSchedResult) error { - if ok, err := net.ContainsIp(ip); err != nil { +func (p NetworkPredicate) checkNetworkIP(ip string, net *models.SNetwork) error { + ipAddr, err := netutils.NewIPV4Addr(ip) + if err != nil { return err - } else if !ok { + } + if ok := net.GetIPRange().Contains(ipAddr); !ok { return fmt.Errorf("Network %s not contains ip %s", net.Name, ip) } return nil diff --git a/pkg/scheduler/algorithm/predicates/schedtag_helper.go b/pkg/scheduler/algorithm/predicates/schedtag_helper.go new file mode 100644 index 0000000000..868677205d --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/schedtag_helper.go @@ -0,0 +1,263 @@ +package predicates + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/scheduler/api" + "yunion.io/x/onecloud/pkg/util/conditionparser" +) + +type ISchedtagPredicate interface { + GetExcludeTags() []api.Schedtag + GetRequireTags() []api.Schedtag + GetAvoidTags() []api.Schedtag + GetPreferTags() []api.Schedtag +} + +type ISchedtagCandidate interface { + IndexKey() string + ResourceType() string + // GetSchedtags return schedtags bind to this candidate + GetSchedtags() []models.SSchedtag + // GetDynamicSchedDesc return schedule description used by dynamic schedtags condition eval + GetDynamicSchedDesc() *jsonutils.JSONDict +} + +type SchedtagPredicate struct { + requireTags []api.Schedtag + execludeTags []api.Schedtag + preferTags []api.Schedtag + avoidTags []api.Schedtag + checker *SchedtagChecker +} + +func NewSchedtagPredicate(reqTags []api.Schedtag, allTags []models.SSchedtag) *SchedtagPredicate { + p := new(SchedtagPredicate) + requireTags, execludeTags, preferTags, avoidTags := GetRequestSchedtags(reqTags, allTags) + p.requireTags = requireTags + p.execludeTags = execludeTags + p.preferTags = preferTags + p.avoidTags = avoidTags + p.checker = new(SchedtagChecker) + return p +} + +func (p *SchedtagPredicate) GetExcludeTags() []api.Schedtag { + return p.execludeTags +} + +func (p *SchedtagPredicate) GetRequireTags() []api.Schedtag { + return p.requireTags +} + +func (p *SchedtagPredicate) GetAvoidTags() []api.Schedtag { + return p.avoidTags +} + +func (p *SchedtagPredicate) GetPreferTags() []api.Schedtag { + return p.preferTags +} + +func (p *SchedtagPredicate) Check(candidate ISchedtagCandidate) error { + return p.checker.Check(p, candidate) +} + +func GetSchedtagCount(inTags []api.Schedtag, objTags []models.SSchedtag, strategy string) (countMap map[string]int) { + countMap = make(map[string]int) + + in := func(objTag models.SSchedtag, inTags []api.Schedtag) bool { + for _, tag := range inTags { + if tag.Idx == objTag.Id || tag.Idx == objTag.Name { + return true + } + } + return false + } + + for _, objTag := range objTags { + if in(objTag, inTags) { + countMap[fmt.Sprintf("%s:%s:%s", objTag.Id, objTag.Name, strategy)]++ + } + } + return +} + +func GetAllSchedtags(resType string) ([]models.SSchedtag, error) { + tags, err := models.SchedtagManager.GetResourceSchedtags(resType) + if err != nil { + return nil, err + } + return tags, nil +} + +func GetRequestSchedtags(reqTags []api.Schedtag, allTags []models.SSchedtag) (requireTags, execludeTags, preferTags, avoidTags []api.Schedtag) { + requireTags = make([]api.Schedtag, 0) + execludeTags = make([]api.Schedtag, 0) + preferTags = make([]api.Schedtag, 0) + avoidTags = make([]api.Schedtag, 0) + + appendedTagIds := make(map[string]int) + + appendTagByStrategy := func(tag api.Schedtag) { + switch tag.Strategy { + case models.STRATEGY_REQUIRE: + requireTags = append(requireTags, tag) + case models.STRATEGY_EXCLUDE: + execludeTags = append(execludeTags, tag) + case models.STRATEGY_PREFER: + preferTags = append(preferTags, tag) + case models.STRATEGY_AVOID: + avoidTags = append(avoidTags, tag) + } + } + + for _, tag := range reqTags { + appendTagByStrategy(tag) + + appendedTagIds[tag.Idx] = 1 + } + + for _, tag := range allTags { + _, nameOk := appendedTagIds[tag.Name] + _, idOk := appendedTagIds[tag.Id] + + if !(nameOk || idOk) { + apiTag := api.Schedtag{Idx: tag.Id, Strategy: tag.DefaultStrategy} + appendTagByStrategy(apiTag) + } + } + + return +} + +type SchedtagChecker struct{} + +type apiTags []api.Schedtag + +func (t apiTags) contains(objTag models.SSchedtag) bool { + for _, tag := range t { + if tag.Idx == objTag.Id || tag.Idx == objTag.Name { + return true + } + } + return false +} + +type objTags []models.SSchedtag + +func (t objTags) contains(atag api.Schedtag) bool { + for _, tag := range t { + if tag.Id == atag.Idx || tag.Name == atag.Idx { + return true + } + } + return false +} + +func (c *SchedtagChecker) contains(tags []api.Schedtag, objTag models.SSchedtag) bool { + for _, tag := range tags { + if tag.Idx == objTag.Id || tag.Idx == objTag.Name { + return true + } + } + return false +} + +func (c *SchedtagChecker) HasIntersection(tags []api.Schedtag, objTags []models.SSchedtag) (bool, *models.SSchedtag) { + var atags apiTags = tags + for _, objTag := range objTags { + if atags.contains(objTag) { + return true, &objTag + } + } + return false, nil +} + +func (c *SchedtagChecker) Contains(objectTags []models.SSchedtag, tags []api.Schedtag) (bool, *api.Schedtag) { + var otags objTags = objectTags + for _, tag := range tags { + if !otags.contains(tag) { + return false, &tag + } + } + return true, nil +} + +func (p *SchedtagChecker) getDynamicSchedtags(schedDesc *jsonutils.JSONDict) ([]models.SSchedtag, error) { + if schedDesc == nil { + return []models.SSchedtag{}, nil + } + dynamicTags := models.DynamicschedtagManager.GetAllEnabledDynamicSchedtags() + + tags := []models.SSchedtag{} + for _, tag := range dynamicTags { + matched, err := conditionparser.Eval(tag.Condition, schedDesc) + if err != nil { + log.Errorf("Condition parse eval: condition: %q, desc: %s, error: %v", tag.Condition, schedDesc, err) + continue + } + if !matched { + continue + } + objTag := tag.GetSchedtag() + if objTag != nil { + tags = append(tags, *objTag) + } + } + return tags, nil +} + +func (c *SchedtagChecker) mergeSchedtags(candiate ISchedtagCandidate, staticTags, dynamicTags []models.SSchedtag) []models.SSchedtag { + isIn := func(tags []models.SSchedtag, dt models.SSchedtag) bool { + for _, t := range tags { + if t.Id == dt.Id { + return true + } + } + return false + } + ret := []models.SSchedtag{} + ret = append(ret, staticTags...) + for _, dt := range dynamicTags { + if !isIn(staticTags, dt) { + ret = append(ret, dt) + log.Debugf("Append dynamic schedtag %s to %s %q", dt, candiate.ResourceType(), candiate.IndexKey()) + } + } + return ret +} + +func (c *SchedtagChecker) GetCandidateSchedtags(candidate ISchedtagCandidate) ([]models.SSchedtag, error) { + staticTags := candidate.GetSchedtags() + dynamicTags, err := c.getDynamicSchedtags(candidate.GetDynamicSchedDesc()) + if err != nil { + return nil, err + } + return c.mergeSchedtags(candidate, staticTags, dynamicTags), nil +} + +func (c *SchedtagChecker) Check(p ISchedtagPredicate, candidate ISchedtagCandidate) error { + candidateTags, err := c.GetCandidateSchedtags(candidate) + if err != nil { + return err + } + execludeTags := p.GetExcludeTags() + if len(execludeTags) > 0 { + if ok, tag := c.HasIntersection(execludeTags, candidateTags); ok { + return fmt.Errorf("Execlude by schedtag: '%s:%s'", tag.Name, tag.Id) + } + } + + requireTags := p.GetRequireTags() + if len(requireTags) > 0 { + if ok, tag := c.Contains(candidateTags, requireTags); !ok { + return fmt.Errorf("Need schedtag: '%s'", tag.Idx) + } + } + + return nil +} diff --git a/pkg/scheduler/algorithm/priorities/guest/avoid_same_cluster.go b/pkg/scheduler/algorithm/priorities/guest/avoid_same_cluster.go deleted file mode 100644 index dfa0d0e6ac..0000000000 --- a/pkg/scheduler/algorithm/priorities/guest/avoid_same_cluster.go +++ /dev/null @@ -1,55 +0,0 @@ -package guest - -import ( - "yunion.io/x/onecloud/pkg/scheduler/algorithm/priorities" - "yunion.io/x/onecloud/pkg/scheduler/core" -) - -type AvoidSameClusterPriority struct { - priorities.BasePriority - ClusterTbl map[string]int -} - -func (p *AvoidSameClusterPriority) Name() string { - return "guest_avoid_same_cluster" -} - -func (p *AvoidSameClusterPriority) Clone() core.Priority { - return &AvoidSameClusterPriority{ClusterTbl: make(map[string]int)} -} - -func (p *AvoidSameClusterPriority) PreExecute(u *core.Unit, cs []core.Candidater) (bool, []core.PredicateFailureReason, error) { - d := u.SchedData() - clusterTbl := make(map[string]int, 0) - ownerTenantID := d.OwnerTenantID - - for _, c := range cs { - hc, err := p.HostCandidate(c) - if err != nil { - return false, nil, err - } - - if count, ok := hc.Tenants[ownerTenantID]; ok && count > 0 { - clusterId := hc.ClusterID - - if count0, ok := clusterTbl[clusterId]; ok { - clusterTbl[clusterId] = count0 + int(count) - } else { - clusterTbl[clusterId] = int(count) - } - } - } - - p.ClusterTbl = clusterTbl - return true, nil, nil -} - -func (p *AvoidSameClusterPriority) Map(u *core.Unit, c core.Candidater) (core.HostPriority, error) { - h := priorities.NewPriorityHelper(p, u, c) - - if count, ok := p.ClusterTbl[c.Get("ClusterID").(string)]; ok { - h.SetScore(-20 * count) - } - - return h.GetResult() -} diff --git a/pkg/scheduler/algorithmprovider/defaults.go b/pkg/scheduler/algorithmprovider/defaults.go index 54f6670b63..7a1b8ec849 100644 --- a/pkg/scheduler/algorithmprovider/defaults.go +++ b/pkg/scheduler/algorithmprovider/defaults.go @@ -27,6 +27,7 @@ func defaultPredicates() sets.String { factory.RegisterFitPredicate("j-GuestNetworkFilter", &predicateguest.NetworkPredicate{}), factory.RegisterFitPredicate("k-GuestIsolatedDeviceFilter", &predicateguest.IsolatedDevicePredicate{}), factory.RegisterFitPredicate("l-GuestResourceTypeFilter", &predicates.ResourceTypePredicate{}), + factory.RegisterFitPredicate("m-GuestDiskschedtagFilter", &predicates.DiskSchedtagPredicate{}), ) } diff --git a/pkg/scheduler/api/candidate.go b/pkg/scheduler/api/candidate.go index 07ba0aca02..467fd3f39c 100644 --- a/pkg/scheduler/api/candidate.go +++ b/pkg/scheduler/api/candidate.go @@ -1,22 +1,23 @@ package api import ( + "encoding/json" + "io" "strconv" simplejson "github.com/bitly/go-simplejson" - - "yunion.io/x/log" ) // CandidateListArgs is a struct just for parsing candidate // resource list parameters. type CandidateListArgs struct { - Type string - Zone string - Pool string - Limit int64 - Offset int64 - Avaliable bool + Type string `json:"type"` + Region string `json:"region"` + Zone string `json:"zone"` + //Pool string + Limit int64 `json:"limit"` + Offset int64 `json:"offset"` + Avaliable bool `json:"available"` } type ResultResource struct { @@ -75,44 +76,20 @@ const ( // NewCandidateListArgs provides a function that // will parse candidate's list args from a json data. -func NewCandidateListArgs(sjson *simplejson.Json) (*CandidateListArgs, error) { - args := &CandidateListArgs{ - Limit: DefaultCandidateListArgsLimit, +func NewCandidateListArgs(r io.Reader) (*CandidateListArgs, error) { + args := CandidateListArgs{} + err := json.NewDecoder(r).Decode(&args) + if err != nil { + return nil, err } - if argsType, ok := sjson.CheckGet("type"); ok { - args.Type = argsType.MustString() - } else { + if args.Limit == 0 { + args.Limit = DefaultCandidateListArgsLimit + } + if args.Type == "" { args.Type = "all" } - if zone, ok := sjson.CheckGet("zone"); ok { - args.Zone = zone.MustString() - } - - if pool, ok := sjson.CheckGet("pool"); ok { - args.Pool = pool.MustString() - } - - if limit, ok := sjson.CheckGet("limit"); ok { - limitv, err := limit.Int64() - if err != nil { - limitv, err = strconv.ParseInt(limit.MustString(), 10, 64) - if err != nil { - log.Errorln(err) - } - } - args.Limit = limitv - } - - if offset, ok := sjson.CheckGet("offset"); ok { - args.Offset = offset.MustInt64() - } - - if avaliable, ok := sjson.CheckGet("avaliable"); ok { - args.Avaliable = avaliable.MustBool() - } - - return args, nil + return &args, nil } // CandidateDetailArgs is a struct just for parsing candidate diff --git a/pkg/scheduler/api/sched.go b/pkg/scheduler/api/sched.go index 6cde908351..eca23da386 100644 --- a/pkg/scheduler/api/sched.go +++ b/pkg/scheduler/api/sched.go @@ -11,27 +11,29 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/utils" - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/compute/baremetal" "yunion.io/x/onecloud/pkg/compute/models" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type Meta map[string]string type Disk struct { - Backend string `json:"backend"` - ImageID string `json:"image_id"` - Fs *string `json:"fs"` - Os string `json:"os"` - OSDistribution string `json:"os_distribution"` - OsVersion string `json:"os_version"` - Format string `json:"format"` - MountPoint *string `json:"mountpoint"` - Driver *string `json:"driver"` - Cache *string `json:"cache"` - ImageDiskFormat string `json:"image_disk_format"` - Size int64 `json:"size"` - Storage *string `json:"storage"` + Backend string `json:"backend"` + ImageID string `json:"image_id"` + Fs *string `json:"fs"` + Os string `json:"os"` + OSDistribution string `json:"os_distribution"` + OsVersion string `json:"os_version"` + Format string `json:"format"` + MountPoint *string `json:"mountpoint"` + Driver *string `json:"driver"` + Cache *string `json:"cache"` + ImageDiskFormat string `json:"image_disk_format"` + Size int64 `json:"size"` + Storage *string `json:"storage"` + Schedtags []Schedtag `json:"schedtags"` + Index int `json:"index"` } type Network struct { @@ -62,7 +64,7 @@ type ForGuest struct { Name string `json:"name"` } -type Aggregate struct { +type Schedtag struct { Idx string `json:"idx"` Strategy string `json:"strategy"` } @@ -78,8 +80,8 @@ type SchedData struct { Type string `json:"type"` IsContainer bool `json:"is_container"` Count int64 `json:"count"` + RegionID string `json:"region_id"` ZoneID string `json:"zone_id"` - PoolID string `json:"pool_id"` HostID string `json:"host_id"` Candidates []string `json:"candidates"` OwnerTenantID string `json:"owner_tenant_id"` @@ -90,7 +92,7 @@ type SchedData struct { Name string `json:"name"` Networks []*Network `json:"networks"` IsolatedDevices []*IsolatedDevice `json:"isolated_devices"` - Aggregates []Aggregate `json:"aggregate_stategy"` + Schedtags []Schedtag `json:"aggregate_stategy"` Meta Meta `json:"__meta__"` ForGuests []*ForGuest `json:"for_guests"` GuestStatus string `json:"guest_status"` @@ -132,9 +134,9 @@ func NewSchedData(sjson *simplejson.Json, count int64, byTest bool) (*SchedData, } } - if poolID, ok := sjson.CheckGet("prefer_pool_id"); ok { - if str, err := poolID.String(); err == nil { - data.PoolID = str + if regionID, ok := sjson.CheckGet("prefer_region_id"); ok { + if str, err := regionID.String(); err == nil { + data.RegionID = str } } @@ -231,7 +233,7 @@ func NewSchedData(sjson *simplejson.Json, count int64, byTest bool) (*SchedData, data.fillForGuests(sjson) - if err := data.fillAggregates(sjson, byTest); err != nil { + if err := data.fillSchedtags(sjson, byTest); err != nil { return nil, err } @@ -652,11 +654,12 @@ func (d *SchedData) fillDisksInfo(sjson *simplejson.Json, byTest bool) error { if !ok { break } - index++ disk, err := newDiskFromSimpleJson(d, byTest) if err != nil { return err } + disk.Index = index + index++ disks = append(disks, disk) } if index == 0 && d.Hypervisor != SchedTypeContainer { @@ -703,7 +706,7 @@ func (d *SchedData) fillForGuests(sjson *simplejson.Json) { } } -func NewSchedTagFromCmdline(str string) (agg Aggregate, err error) { +func NewSchedTagFromCmdline(str string) (agg Schedtag, err error) { rs := strings.Split(str, ":") if len(rs) == 1 || rs[1] == "" { err = fmt.Errorf("SchedTag %q no strategy.", str) @@ -711,22 +714,22 @@ func NewSchedTagFromCmdline(str string) (agg Aggregate, err error) { } name, strategy := rs[0], rs[1] - err = AggregateStrategyCheck(strategy) + err = SchedtagStrategyCheck(strategy) if err != nil { return } - agg = Aggregate{name, strategy} + agg = Schedtag{name, strategy} return } -func (d *SchedData) fillAggregates(sjson *simplejson.Json, byTest bool) error { - d.Aggregates = []Aggregate{} +func (d *SchedData) fillSchedtags(sjson *simplejson.Json, byTest bool) error { + d.Schedtags = []Schedtag{} if !byTest { if aggNode, ok := sjson.CheckGet("aggregate_strategy"); ok { for name, strategy := range aggNode.MustMap() { - d.Aggregates = append(d.Aggregates, Aggregate{ + d.Schedtags = append(d.Schedtags, Schedtag{ Idx: fmt.Sprintf("%v", name), Strategy: fmt.Sprintf("%v", strategy), }) } @@ -743,7 +746,7 @@ func (d *SchedData) fillAggregates(sjson *simplejson.Json, byTest bool) error { if err != nil { return err } - d.Aggregates = append(d.Aggregates, agg) + d.Schedtags = append(d.Schedtags, agg) index++ } } diff --git a/pkg/scheduler/api/types.go b/pkg/scheduler/api/types.go index 2a2cca01ce..2862157484 100644 --- a/pkg/scheduler/api/types.go +++ b/pkg/scheduler/api/types.go @@ -74,9 +74,14 @@ func init() { } } -func AggregateStrategyCheck(strategy string) (err error) { +func SchedtagStrategyCheck(strategy string) (err error) { if !AggregateStrategySets.Has(strategy) { err = fmt.Errorf("Strategy %q must in set %v", strategy, AggregateStrategySets.List()) } return } + +type CandidateStorage struct { + *models.SStorage + Schedtags []models.SSchedtag `json:"schedtags"` +} diff --git a/pkg/scheduler/cache/candidate/baremetals.go b/pkg/scheduler/cache/candidate/baremetals.go index 8109ee0589..51dfc2b831 100644 --- a/pkg/scheduler/cache/candidate/baremetals.go +++ b/pkg/scheduler/cache/candidate/baremetals.go @@ -14,10 +14,12 @@ import ( "yunion.io/x/onecloud/pkg/scheduler/cache/db" "yunion.io/x/onecloud/pkg/scheduler/core" "yunion.io/x/onecloud/pkg/scheduler/db/models" + + computemodels "yunion.io/x/onecloud/pkg/compute/models" ) type BaremetalDesc struct { - *baseHostDesc + *BaseHostDesc Storages []*baremetal.BaremetalStorage `json:"storages"` StorageType string `json:"storage_type"` @@ -47,7 +49,7 @@ func (bd *BaremetalDesc) Type() int { func (bd *BaremetalDesc) Get(key string) interface{} { switch key { case "ID": - return bd.ID + return bd.Id case "Name": return bd.Name @@ -55,17 +57,14 @@ func (bd *BaremetalDesc) Get(key string) interface{} { case "Status": return bd.Status - case "PoolID": - return bd.PoolID - case "ZoneID": - return bd.ZoneID + return bd.ZoneId case "ServerID": return bd.ServerID case "CPUCount": - return int64(bd.CPUCount) + return int64(bd.CpuCount) case "FreeCPUCount": return bd.FreeCPUCount() @@ -117,19 +116,19 @@ func (bd *BaremetalDesc) XGet(key string, kind core.Kind) interface{} { } func (bd *BaremetalDesc) IndexKey() string { - return bd.ID + return bd.Id } func (bd *BaremetalDesc) FreeCPUCount() int64 { if bd.ServerID == "" { - return bd.CPUCount + return int64(bd.CpuCount) } return 0 } func (bd *BaremetalDesc) FreeMemSize() int64 { if bd.ServerID == "" { - return bd.MemSize + return int64(bd.MemSize) } return 0 } @@ -190,6 +189,7 @@ func (bb *BaremetalBuilder) Type() string { } func (bb *BaremetalBuilder) AllIDs() ([]string, error) { + log.Errorf("=======get baremetal host ids") return models.AllBaremetalIDs() } @@ -220,12 +220,13 @@ func (bb *BaremetalBuilder) build() ([]interface{}, error) { } func (bb *BaremetalBuilder) buildOne(bm *models.Host) (interface{}, error) { - baseDesc, err := newBaseHostDesc(bm) + hostObj := computemodels.HostManager.FetchHostById(bm.ID) + baseDesc, err := newBaseHostDesc(hostObj) if err != nil { return nil, err } desc := &BaremetalDesc{ - baseHostDesc: baseDesc, + BaseHostDesc: baseDesc, } desc.StorageDriver = bm.StorageDriver diff --git a/pkg/scheduler/cache/candidate/base.go b/pkg/scheduler/cache/candidate/base.go new file mode 100644 index 0000000000..fc7fc7aab2 --- /dev/null +++ b/pkg/scheduler/cache/candidate/base.go @@ -0,0 +1,246 @@ +package candidate + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/scheduler/api" + "yunion.io/x/onecloud/pkg/scheduler/core" + "yunion.io/x/onecloud/pkg/scheduler/db/models" + + computedb "yunion.io/x/onecloud/pkg/cloudcommon/db" + computemodels "yunion.io/x/onecloud/pkg/compute/models" +) + +type BaseHostDesc struct { + *computemodels.SHost + Region *computemodels.SCloudregion `json:"region"` + Zone *computemodels.SZone `json:"zone"` + Cloudprovider *computemodels.SCloudprovider `json:"cloudprovider"` + Networks []computemodels.SNetwork `json:"networks"` + Storages []*api.CandidateStorage `json:"storages"` + + Tenants map[string]int64 `json:"tenants"` + HostSchedtags []computemodels.SSchedtag `json:"schedtags"` +} + +type baseHostGetter struct { + h *BaseHostDesc +} + +func newBaseHostGetter(h *BaseHostDesc) *baseHostGetter { + return &baseHostGetter{h} +} + +func (b *BaseHostDesc) Getter() core.CandidatePropertyGetter { + return newBaseHostGetter(b) +} + +func (b baseHostGetter) Id() string { + return b.h.GetId() +} + +func (b baseHostGetter) Name() string { + return b.h.GetName() +} + +func (b baseHostGetter) Zone() *computemodels.SZone { + return b.h.Zone +} + +func (b baseHostGetter) Region() *computemodels.SCloudregion { + return b.h.Region +} + +func (b baseHostGetter) HostType() string { + return b.h.HostType +} + +func (b baseHostGetter) HostSchedtags() []computemodels.SSchedtag { + return b.h.HostSchedtags +} + +func (b baseHostGetter) Storages() []*api.CandidateStorage { + return b.h.Storages +} + +func reviseResourceType(resType string) string { + if resType == "" { + return computemodels.HostResourceTypeDefault + } + return resType +} + +func newBaseHostDesc(host *computemodels.SHost) (*BaseHostDesc, error) { + host.ResourceType = reviseResourceType(host.ResourceType) + desc := &BaseHostDesc{ + SHost: host, + } + + if err := desc.fillCloudProvider(host); err != nil { + return nil, fmt.Errorf("Fill cloudprovider info error: %v", err) + } + + if err := desc.fillNetworks(desc.Id); err != nil { + return nil, fmt.Errorf("Fill networks error: %v", err) + } + + if err := desc.fillZone(host); err != nil { + return nil, fmt.Errorf("Fill zone error: %v", err) + } + + if err := desc.fillRegion(host); err != nil { + return nil, fmt.Errorf("Fill region error: %v", err) + } + + if err := desc.fillResidentTenants(host); err != nil { + return nil, fmt.Errorf("Fill resident tenants error: %v", err) + } + + return desc, nil +} + +func (b BaseHostDesc) GetSchedDesc() *jsonutils.JSONDict { + desc := jsonutils.Marshal(b.SHost).(*jsonutils.JSONDict) + + if b.Cloudprovider != nil { + p := b.Cloudprovider + cloudproviderDesc := jsonutils.NewDict() + cloudproviderDesc.Add(jsonutils.NewString(p.ProjectId), "tenant_id") + cloudproviderDesc.Add(jsonutils.NewString(p.Provider), "provider") + desc.Add(cloudproviderDesc, "cloudprovider") + } + + return desc +} + +func (b BaseHostDesc) GetResourceType() string { + return b.ResourceType +} + +func (b *BaseHostDesc) fillCloudProvider(host *computemodels.SHost) error { + b.Cloudprovider = host.GetCloudprovider() + return nil +} + +func (b *BaseHostDesc) fillRegion(host *computemodels.SHost) error { + b.Region = host.GetRegion() + return nil +} + +func (b *BaseHostDesc) fillZone(host *computemodels.SHost) error { + zone := host.GetZone() + b.Zone = zone + b.ZoneId = host.ZoneId + return nil +} + +func (b *BaseHostDesc) fillResidentTenants(host *computemodels.SHost) error { + rets, err := HostResidentTenantCount(host.Id) + if err != nil { + return err + } + + b.Tenants = rets + + return nil +} + +func (b *BaseHostDesc) fillSchedtags(allSchedtags []computemodels.SSchedtag) error { + //b.Schedtags = allSchedtags + b.HostSchedtags = b.SHost.GetSchedtags() + return nil +} + +func (b *BaseHostDesc) fillNetworks(hostID string) error { + hostwires := computemodels.HostwireManager.Query().SubQuery() + sq := hostwires.Query(sqlchemy.DISTINCT("wire_id", hostwires.Field("wire_id"))).Equals("host_id", hostID) + networks := computemodels.NetworkManager.Query().SubQuery() + q := networks.Query().In("wire_id", sq) + + nets := make([]computemodels.SNetwork, 0) + err := computedb.FetchModelObjects(computemodels.NetworkManager, q, &nets) + + if err != nil { + return err + } + b.Networks = nets + return nil +} + +func (b *BaseHostDesc) fillStorages(storages []*computemodels.SStorage) error { + ss := make([]*api.CandidateStorage, 0) + for _, s := range storages { + ss = append(ss, &api.CandidateStorage{ + SStorage: s, + Schedtags: s.GetSchedtags(), + }) + } + b.Storages = ss + return nil +} + +func (h *BaseHostDesc) GetEnableStatus() string { + if h.Enabled { + return "enable" + } + return "disable" +} + +func (h *BaseHostDesc) GetHostType() string { + if h.HostType == api.HostTypeBaremetal && h.IsBaremetal { + return api.HostTypeBaremetal + } + return h.HostType +} + +func HostsResidentTenantStats(hostIDs []string) (map[string]map[string]interface{}, error) { + residentTenantStats, err := models.ResidentTenantsInHosts(hostIDs) + if err != nil { + return nil, err + } + stat3 := make([]utils.StatItem3, len(residentTenantStats)) + for i, item := range residentTenantStats { + stat3[i] = item + } + return utils.ToStatDict3(stat3) +} + +func HostResidentTenantCount(id string) (map[string]int64, error) { + residentTenantDict, err := HostsResidentTenantStats([]string{id}) + if err != nil { + return nil, err + } + tenantMap, ok := residentTenantDict[id] + if !ok { + log.V(10).Infof("Not found host ID: %s when fill resident tenants, may be no guests on it.", id) + return nil, nil + } + rets := make(map[string]int64, len(tenantMap)) + for tenantID, countObj := range tenantMap { + rets[tenantID] = countObj.(int64) + } + return rets, nil +} + +type DescBuilder struct { + dbGroupCache DBGroupCacher + syncGroupCache SyncGroupCacher + actor BuildActor +} + +func NewDescBuilder(db DBGroupCacher, sync SyncGroupCacher, act BuildActor) *DescBuilder { + return &DescBuilder{ + dbGroupCache: db, + syncGroupCache: sync, + actor: act, + } +} + +func (d *DescBuilder) Build(ids []string) ([]interface{}, error) { + return d.actor.Do(ids, d.dbGroupCache, d.syncGroupCache) +} diff --git a/pkg/scheduler/cache/candidate/common.go b/pkg/scheduler/cache/candidate/common.go new file mode 100644 index 0000000000..1a9f8b54bc --- /dev/null +++ b/pkg/scheduler/cache/candidate/common.go @@ -0,0 +1,50 @@ +package candidate + +import ( + //"yunion.io/x/log" + + "yunion.io/x/pkg/util/sets" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/compute/models" +) + +var ( + VMRunningStatus = sets.NewString( + models.VM_START_START, + models.VM_STARTING, + models.VM_RUNNING, + models.VM_STOP_FAILED, + models.VM_BLOCK_STREAM, + models.VM_UNKNOWN, + models.VM_BACKUP_STARTING, + ) + + VMCreatingStatus = sets.NewString( + models.VM_CREATE_NETWORK, + models.VM_CREATE_DISK, + models.VM_START_DEPLOY, + models.VM_DEPLOYING, + models.VM_BACKUP_CREATING, + models.VM_DEPLOYING_BACKUP, + ) +) + +func FetchGuestByHostIDs(ids []string) ([]models.SGuest, error) { + gs := make([]models.SGuest, 0) + q := models.GuestManager.Query().In("host_id", ids) + err := db.FetchModelObjects(models.GuestManager, q, &gs) + return gs, err +} + +func IsGuestRunning(g models.SGuest) bool { + return VMRunningStatus.Has(g.Status) +} + +func IsGuestCreating(g models.SGuest) bool { + return VMCreatingStatus.Has(g.Status) +} + +func IsGuestPendingDelete(g models.SGuest) bool { + return g.PendingDeleted +} diff --git a/pkg/scheduler/cache/candidate/default.go b/pkg/scheduler/cache/candidate/default.go index 7c9dc7e967..5ad5f1191e 100644 --- a/pkg/scheduler/cache/candidate/default.go +++ b/pkg/scheduler/cache/candidate/default.go @@ -9,9 +9,9 @@ import ( "yunion.io/x/log" u "yunion.io/x/pkg/utils" - "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/cache" "yunion.io/x/onecloud/pkg/scheduler/db/models" + "yunion.io/x/onecloud/pkg/scheduler/options" ) const ( @@ -32,7 +32,7 @@ func defaultCadidateItems(db DBGroupCacher, sync SyncGroupCacher) []cache.Cached } func uuidKey(obj interface{}) (string, error) { - return obj.(descer).UUID(), nil + return obj.(descer).GetId(), nil } func generalUpdateFunc(db DBGroupCacher, sync SyncGroupCacher, act BuildActor, mutex *gosync.Mutex) cache.UpdateFunc { @@ -92,7 +92,10 @@ func generalGetUpdateFunc(isBaremetal bool) cache.GetUpdateFunc { for _, item := range d { r := reflect.ValueOf(item) f := reflect.Indirect(r) - key := f.FieldByName("ID") + key := f.FieldByName("Id") + if !key.IsValid() { + key = f.FieldByName("ID") + } value := f.FieldByName("UpdatedAt") if key.IsValid() && value.IsValid() { allStatus[key.String()] = value.Interface().(time.Time) diff --git a/pkg/scheduler/cache/candidate/desc.go b/pkg/scheduler/cache/candidate/desc.go deleted file mode 100644 index 2bc41db960..0000000000 --- a/pkg/scheduler/cache/candidate/desc.go +++ /dev/null @@ -1,260 +0,0 @@ -package candidate - -import ( - "fmt" - "time" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/utils" - - cloudmodels "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/scheduler/api" - "yunion.io/x/onecloud/pkg/scheduler/db/models" -) - -type baseDesc struct { - ID string `json:"id"` - Name string `json:"name"` - UpdatedAt time.Time `json:"updated_at"` -} - -func (b *baseDesc) UUID() string { - return b.ID -} - -type baseHostDesc struct { - baseDesc - models.BillingResourceBase - - ManagerID *string `json:"manager_id"` - Status string `json:"status"` - CPUCount int64 `json:"cpu_count"` - MemSize int64 `json:"mem_size"` - Networks []*models.NetworkSchedResult `json:"networks"` - HostStatus string `json:"host_status"` - Enabled bool `json:"enabled"` - HostType string `json:"host_type"` - IsBaremetal bool `json:"is_baremetal"` - IsMaintenance bool `json:"is_maintenance"` - NodeCount int64 `json:"node_count"` - Tenants map[string]int64 `json:"tenants"` - ZoneID string `json:"zone_id"` - Zone *models.Zone `json:"zone"` - PoolID string `json:"pool_id"` - ClusterID string `json:"cluster_id"` - Aggregates []*models.Aggregate `json:"aggregates"` - HostAggregates []*models.Aggregate `json:"host_aggregates"` - Cloudprovider *models.Cloudprovider `json:"cloudprovider"` - ResourceType string `json:"resource_type"` - RealExternalId string `json:"real_external_id"` -} - -func reviseResourceType(resType string) string { - if resType == "" { - return cloudmodels.HostResourceTypeDefault - } - return resType -} - -func newBaseHostDesc(host *models.Host) (*baseHostDesc, error) { - desc := &baseHostDesc{ - baseDesc: baseDesc{ - ID: host.ID, - Name: host.Name, - UpdatedAt: host.UpdatedAt, - }, - BillingResourceBase: host.BillingResourceBase, - ManagerID: host.ManagerID, - Status: host.Status, - CPUCount: host.CPUCount, - MemSize: host.MemSize, - HostStatus: host.HostStatus, - Enabled: host.Enabled, - HostType: host.HostType, - IsBaremetal: host.IsBaremetal, - NodeCount: host.NodeCount, - IsMaintenance: host.IsMaintenance, - ResourceType: reviseResourceType(host.ResourceType), - RealExternalId: host.RealExternalId, - } - - if err := desc.fillCloudProvider(host); err != nil { - return nil, fmt.Errorf("Fill cloudprovider info error: %v", err) - } - - if err := desc.fillNetworks(desc.ID); err != nil { - return nil, fmt.Errorf("Fill networks error: %v", err) - } - - if err := desc.fillZone(host); err != nil { - return nil, fmt.Errorf("Fill zone error: %v", err) - } - - if err := desc.fillResidentTenants(host); err != nil { - return nil, fmt.Errorf("Fill resident tenants error: %v", err) - } - - if err := desc.fillAggregates(); err != nil { - return nil, fmt.Errorf("Fill schetags error: %v", err) - } - return desc, nil -} - -func (b baseHostDesc) GetSchedDesc() *jsonutils.JSONDict { - desc := jsonutils.NewDict() - - desc.Add(jsonutils.NewString(b.ID), "id") - desc.Add(jsonutils.NewString(b.Name), "name") - desc.Add(jsonutils.NewInt(b.CPUCount), "cpu_count") - desc.Add(jsonutils.NewInt(b.MemSize), "mem_size") - desc.Add(jsonutils.NewString(b.HostType), "host_type") - desc.Add(jsonutils.NewString(b.ZoneID), "zone_id") - desc.Add(jsonutils.NewString(b.Zone.Name), "zone") - desc.Add(jsonutils.NewString(b.ResourceType), "resource_type") - - if b.Cloudprovider != nil { - p := b.Cloudprovider - cloudproviderDesc := jsonutils.NewDict() - cloudproviderDesc.Add(jsonutils.NewString(p.ProjectId), "tenant_id") - cloudproviderDesc.Add(jsonutils.NewString(p.Provider), "provider") - desc.Add(cloudproviderDesc, "cloudprovider") - } - - return desc -} - -func (b baseHostDesc) GetResourceType() string { - return b.ResourceType -} - -func (b *baseHostDesc) fillCloudProvider(host *models.Host) error { - if host.ManagerID == nil { - log.Debugf("Host %q manager id is empty, no cloud provider", host.Name) - return nil - } - provider, err := models.FetchCloudproviderById(*(host.ManagerID)) - if err != nil { - return err - } - b.Cloudprovider = provider - return nil -} - -func (b *baseHostDesc) fillZone(host *models.Host) error { - zone, err := models.FetchZoneByID(host.ZoneID) - if err != nil { - return err - } - b.Zone = zone - b.ZoneID = host.ZoneID - return nil -} - -func (b *baseHostDesc) fillResidentTenants(host *models.Host) error { - rets, err := HostResidentTenantCount(host.ID) - if err != nil { - return err - } - - b.Tenants = rets - - return nil -} - -func (b *baseHostDesc) fillAggregates() error { - b.Aggregates = make([]*models.Aggregate, 0) - objs, err := models.All(models.Aggregates) - if err != nil { - return err - } - for _, obj := range objs { - agg := obj.(*models.Aggregate) - b.Aggregates = append(b.Aggregates, agg) - } - - aggs, err := models.HostAggregates(b.ID) - if err != nil { - return err - } - b.HostAggregates = aggs - return nil -} - -func (b *baseHostDesc) GetAggregates() []*models.Aggregate { - return b.Aggregates -} - -func (b *baseHostDesc) GetHostAggregates() []*models.Aggregate { - return b.HostAggregates -} - -func (b *baseHostDesc) fillNetworks(hostID string) error { - net, err := models.HostNetworkSchedResults(hostID) - if err != nil { - return err - } - b.Networks = net - return nil -} - -func (h *baseHostDesc) GetEnableStatus() string { - if h.Enabled { - return "enable" - } - return "disable" -} - -func (h *baseHostDesc) GetHostType() string { - if h.HostType == api.HostTypeBaremetal && h.IsBaremetal { - return api.HostTypeBaremetal - } - return h.HostType -} - -func HostsResidentTenantStats(hostIDs []string) (map[string]map[string]interface{}, error) { - residentTenantStats, err := models.ResidentTenantsInHosts(hostIDs) - if err != nil { - return nil, err - } - stat3 := make([]utils.StatItem3, len(residentTenantStats)) - for i, item := range residentTenantStats { - stat3[i] = item - } - return utils.ToStatDict3(stat3) -} - -func HostResidentTenantCount(id string) (map[string]int64, error) { - residentTenantDict, err := HostsResidentTenantStats([]string{id}) - if err != nil { - return nil, err - } - tenantMap, ok := residentTenantDict[id] - if !ok { - log.V(10).Infof("Not found host ID: %s when fill resident tenants, may be no guests on it.", id) - return nil, nil - } - rets := make(map[string]int64, len(tenantMap)) - for tenantID, countObj := range tenantMap { - rets[tenantID] = countObj.(int64) - } - return rets, nil -} - -type DescBuilder struct { - dbGroupCache DBGroupCacher - syncGroupCache SyncGroupCacher - actor BuildActor -} - -func NewDescBuilder(db DBGroupCacher, sync SyncGroupCacher, act BuildActor) *DescBuilder { - return &DescBuilder{ - dbGroupCache: db, - syncGroupCache: sync, - actor: act, - } -} - -func (d *DescBuilder) Build(ids []string) ([]interface{}, error) { - return d.actor.Do(ids, d.dbGroupCache, d.syncGroupCache) -} diff --git a/pkg/scheduler/cache/candidate/hosts.go b/pkg/scheduler/cache/candidate/hosts.go index 7b3b331698..32b9399530 100644 --- a/pkg/scheduler/cache/candidate/hosts.go +++ b/pkg/scheduler/cache/candidate/hosts.go @@ -13,23 +13,20 @@ import ( "yunion.io/x/pkg/util/sets" "yunion.io/x/pkg/util/workqueue" "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" - o "yunion.io/x/onecloud/cmd/scheduler/options" - "yunion.io/x/onecloud/pkg/scheduler/cache" - "yunion.io/x/onecloud/pkg/scheduler/cache/db" + computedb "yunion.io/x/onecloud/pkg/cloudcommon/db" + computemodels "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/scheduler/core" "yunion.io/x/onecloud/pkg/scheduler/db/models" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type HostDesc struct { - *baseHostDesc + *BaseHostDesc // cpu - CPUMHZ int64 `json:"cpu_mhz"` - CPUCmtbound float64 `json:"cpu_cmtbound"` - CPUDesc string `json:"cpu_desc"` - CPUCache int64 `json:"cpu_cache"` - CPUReserved int64 `json:"cpu_reserved"` + CPUCmtbound float32 `json:"cpu_cmtbound"` CPUBoundCount int64 `json:"cpu_bound_count"` CPULoad *float64 `json:"cpu_load"` TotalCPUCount int64 `json:"total_cpu_count"` @@ -40,8 +37,7 @@ type HostDesc struct { FreeCPUCount int64 `json:"free_cpu_count"` // memory - MemCmtbound float64 `json:"mem_cmtbound"` - MemReserved int64 `json:"mem_reserved"` + MemCmtbound float32 `json:"mem_cmtbound"` TotalMemSize int64 `json:"total_mem_size"` FreeMemSize int64 `json:"free_mem_size"` RunningMemSize int64 `json:"running_mem_size"` @@ -50,8 +46,7 @@ type HostDesc struct { FakeDeletedMemSize int64 `json:"fake_deleted_mem_size"` // storage - Storages []*Storage `json:"storages"` - StorageTypes []string `json:"storage_types"` + StorageTypes []string `json:"storage_types"` // IO IOBoundCount int64 `json:"io_bound_count"` @@ -96,10 +91,10 @@ func StorageIsolatedDevReservedSize() int64 { return o.GetOptions().StorageReservedPerIsolatedDevice } -func NewGuestReservedResourceByBuilder(b *HostBuilder, host *models.Host) (ret *ReservedResource) { +func NewGuestReservedResourceByBuilder(b *HostBuilder, host *computemodels.SHost) (ret *ReservedResource) { ret = NewReservedResource(0, 0, 0) //isoDevs := b.getUnusedIsolatedDevices(host.ID) - isoDevs := b.getIsolatedDevices(host.ID) + isoDevs := b.getIsolatedDevices(host.Id) hostDevsCount := int64(len(isoDevs)) if hostDevsCount == 0 { return @@ -115,9 +110,9 @@ func NewGuestReservedResourceByBuilder(b *HostBuilder, host *models.Host) (ret * return } -func NewGuestReservedResourceUsedByBuilder(b *HostBuilder, host *models.Host) (ret *ReservedResource, err error) { +func NewGuestReservedResourceUsedByBuilder(b *HostBuilder, host *computemodels.SHost) (ret *ReservedResource, err error) { ret = NewReservedResource(0, 0, 0) - gst := b.getIsolatedDeviceGuests(host.ID) + gst := b.getIsolatedDeviceGuests(host.Id) if len(gst) == 0 { return } @@ -133,7 +128,7 @@ func NewGuestReservedResourceUsedByBuilder(b *HostBuilder, host *models.Host) (r return } disk += dSize - if o.GetOptions().IgnoreNonRunningGuests && !g.IsRunning() { + if o.GetOptions().IgnoreNonrunningGuests && !g.IsRunning() { continue } cpu += g.VCPUCount @@ -145,54 +140,51 @@ func NewGuestReservedResourceUsedByBuilder(b *HostBuilder, host *models.Host) (r return } -type Storage struct { - ID string `json:"id"` - Name string `json:"name"` - Capacity int64 `json:"capacity"` - StorageType string `json:"type"` - UsedCapacity int64 `json:"used"` - WasteCapacity int64 `json:"waste"` - FreeCapacity int64 `json:"free"` - VCapacity int64 `json:"vcapacity"` - Cmtbound float64 `json:"cmtbound"` - StorageDriver string `json:"driver"` - Adapter string `json:"adapter"` - Splits []string `json:"splits"` - Range string `json:"range"` - Conf string `json:"conf"` - MinStripSize int `json:"min_strip_size"` - MaxStripSize int `json:"max_strip_size"` - Size int `json:"size"` -} +//type Storage struct { +//ID string `json:"id"` +//Name string `json:"name"` +//Capacity int64 `json:"capacity"` +//StorageType string `json:"type"` +//UsedCapacity int64 `json:"used"` +//WasteCapacity int64 `json:"waste"` +//FreeCapacity int64 `json:"free"` +//VCapacity int64 `json:"vcapacity"` +//Cmtbound float64 `json:"cmtbound"` +//StorageDriver string `json:"driver"` +//Adapter string `json:"adapter"` +//Splits []string `json:"splits"` +//Range string `json:"range"` +//Conf string `json:"conf"` +//MinStripSize int `json:"min_strip_size"` +//MaxStripSize int `json:"max_strip_size"` +//Size int `json:"size"` +//} -func (storage *Storage) GetFreeSize() int64 { - return storage.GetTotalSize() - storage.UsedCapacity - storage.WasteCapacity -} +//func (storage *Storage) GetFreeSize() int64 { +//return storage.GetTotalSize() - storage.UsedCapacity - storage.WasteCapacity +//} -func (storage *Storage) GetTotalSize() int64 { - return int64(float64(storage.Capacity) * storage.Cmtbound) -} +//func (storage *Storage) GetTotalSize() int64 { +//return int64(float64(storage.Capacity) * storage.Cmtbound) +//} -func (storage *Storage) IsLocal() bool { - return utils.IsLocalStorage(storage.StorageType) -} +//func (storage *Storage) IsLocal() bool { +//return utils.IsLocalStorage(storage.StorageType) +//} type HostBuilder struct { - clusters cache.Cache - residentTenantDict map[string]map[string]interface{} - hosts []interface{} + hosts []computemodels.SHost hostDict map[string]interface{} - guests []interface{} + guests []computemodels.SGuest guestDict map[string]interface{} guestIDs []string - hostStorages []interface{} - hostStoragesDict map[string][]interface{} + hostStorages []computemodels.SHoststorage + hostStoragesDict map[string][]*computemodels.SStorage storages []interface{} - storageDict map[string]interface{} storageStatesSizeDict map[string]map[string]interface{} hostGuests map[string][]interface{} @@ -212,6 +204,8 @@ type HostBuilder struct { isolatedDevicesDict map[string][]interface{} cpuIOLoads map[string]map[string]float64 + + schedtags []computemodels.SSchedtag } func (h *HostDesc) String() string { @@ -231,25 +225,19 @@ func (h *HostDesc) GetGuestCount() int64 { func (h *HostDesc) Get(key string) interface{} { switch key { case "ID": - return h.ID + return h.Id case "Name": return h.Name case "CPUCount": - return h.CPUCount + return h.CpuCount case "MemSize": return h.MemSize - case "PoolID": - return h.PoolID - case "ZoneID": - return h.ZoneID - - case "ClusterID": - return h.ClusterID + return h.ZoneId case "TotalCPUCount": return h.GetTotalCPUCount(true) @@ -332,7 +320,7 @@ func (h *HostDesc) totalStorageSize(onlyLocal, useRsvd bool) int64 { total := int64(0) for _, storage := range h.Storages { if !onlyLocal || storage.IsLocal() { - total += storage.GetTotalSize() + total += int64(storage.GetCapacity()) } } @@ -346,7 +334,7 @@ func (h *HostDesc) freeStorageSize(onlyLocal, useRsvd bool) int64 { total := int64(0) for _, storage := range h.Storages { if !onlyLocal || storage.IsLocal() { - total += storage.GetFreeSize() + total += int64(storage.GetFreeCapacity()) } } @@ -370,7 +358,7 @@ func (h *HostDesc) freeStorageSizeOfType(storageType string, useRsvd bool) int64 total := int64(0) for _, storage := range h.Storages { if storage.StorageType == storageType { - total += storage.GetFreeSize() + total += int64(storage.GetFreeCapacity()) } } if utils.IsLocalStorage(storageType) { @@ -431,11 +419,11 @@ func (h *HostDesc) GuestReservedStorageSizeFree() int64 { } func (h *HostDesc) GetReservedMemSize() int64 { - return h.GuestReservedResource.MemorySize + h.MemReserved + return h.GuestReservedResource.MemorySize + int64(h.MemReserved) } func (h *HostDesc) GetReservedCPUCount() int64 { - return h.GuestReservedResource.CPUCount + h.CPUReserved + return h.GuestReservedResource.CPUCount + int64(h.CpuReserved) } func (h *HostDesc) GetReservedStorageSize() int64 { @@ -451,7 +439,7 @@ func (h *HostDesc) GetFreeCPUCount(useRsvd bool) int64 { } func (h *HostDesc) IndexKey() string { - return h.ID + return h.Id } func (h *HostDesc) UnusedIsolatedDevices() []*IsolatedDeviceDesc { @@ -544,8 +532,8 @@ func (b *HostBuilder) init(ids []string, dbCache DBGroupCacher, syncCache SyncGr errMessageChannel := make(chan error, 12) defer close(errMessageChannel) setFuncs := []func(){ - func() { b.setClusters(dbCache, errMessageChannel) }, func() { b.setHosts(ids, errMessageChannel) }, + func() { b.setSchedtags(ids, errMessageChannel) }, func() { b.setGuests(ids, errMessageChannel) b.setGroupInfo(errMessageChannel) @@ -582,88 +570,70 @@ func (b *HostBuilder) init(ids []string, dbCache DBGroupCacher, syncCache SyncGr return nil } -func (b *HostBuilder) setClusters(dbCache DBGroupCacher, errMessageChannel chan error) { - clusters, err := dbCache.Get(db.ClusterDBCache) - if err != nil { - errMessageChannel <- err - return - } - b.clusters = clusters - return -} - func (b *HostBuilder) setHosts(ids []string, errMessageChannel chan error) { - hosts, err := models.FetchHypervisorHostByIDs(ids) + hosts := computemodels.HostManager.Query() + q := hosts.In("id", ids).NotEquals("host_type", computemodels.HOST_TYPE_BAREMETAL) + hostObjs := make([]computemodels.SHost, 0) + err := computedb.FetchModelObjects(computemodels.HostManager, q, &hostObjs) if err != nil { errMessageChannel <- err return } - hostDict, err := utils.ToDict(hosts, func(obj interface{}) (string, error) { - host, ok := obj.(*models.Host) + + hostDict, err := utils.ToDict(hostObjs, func(obj interface{}) (string, error) { + host, ok := obj.(computemodels.SHost) if !ok { - return "", utils.ConvertError(obj, "*models.Host") + return "", utils.ConvertError(obj, "computemodels.Host") } - return host.ID, nil + return host.Id, nil }) if err != nil { errMessageChannel <- err return } - b.hosts = hosts + b.hosts = hostObjs b.hostDict = hostDict return } +func (b *HostBuilder) setSchedtags(ids []string, errMessageChannel chan error) { + tags := make([]computemodels.SSchedtag, 0) + if err := computemodels.SchedtagManager.Query().All(&tags); err != nil { + errMessageChannel <- err + return + } + b.schedtags = tags +} + func (b *HostBuilder) setStorages(ids []string, errMessageChannel chan error) { - hostStorages, err := models.FetchByHostIDs(models.HostStorages, ids) - if err != nil { - errMessageChannel <- err - return - } - storageIDs := make([]string, len(hostStorages)) - func() { - for i, s := range hostStorages { - storageIDs[i] = s.(*models.HostStorage).StorageID - } - }() - storages, err := models.FetchByIDs(models.Storages, storageIDs) + q := computemodels.HoststorageManager.Query().In("host_id", ids) + hostStorages := make([]computemodels.SHoststorage, 0) + err := computedb.FetchModelObjects(computemodels.HoststorageManager, q, &hostStorages) if err != nil { errMessageChannel <- err return } - hostStoragesDict, err := utils.GroupBy(hostStorages, func(obj interface{}) (string, error) { - storage, ok := obj.(*models.HostStorage) - if !ok { - return "", utils.ConvertError(obj, "*models.HostStorage") - } - return storage.HostID, nil - }) - if err != nil { - errMessageChannel <- err - return - } - storageDict, err := utils.ToDict(storages, func(obj interface{}) (string, error) { - storage, ok := obj.(*models.Storage) - if !ok { - return "", utils.ConvertError(obj, "*models.Storage") - } - return storage.ID, nil - }) + hostStoragesDict := make(map[string][]*computemodels.SStorage) - if err != nil { - errMessageChannel <- err - return + for _, s := range hostStorages { + if ss, ok := hostStoragesDict[s.HostId]; !ok { + storage := s.GetStorage() + ss = make([]*computemodels.SStorage, 0) + ss = append(ss, storage) + hostStoragesDict[s.HostId] = ss + } else { + ss = append(ss, s.GetStorage()) + } } + b.hostStorages = hostStorages b.hostStoragesDict = hostStoragesDict - b.storages = storages - b.storageDict = storageDict return } func (b *HostBuilder) setGuests(ids []string, errMessageChannel chan error) { - guests, err := models.FetchGuestByHostIDs(ids) + guests, err := FetchGuestByHostIDs(ids) if err != nil { errMessageChannel <- err return @@ -671,16 +641,16 @@ func (b *HostBuilder) setGuests(ids []string, errMessageChannel chan error) { guestIDs := make([]string, len(guests)) func() { for i, gst := range guests { - guestIDs[i] = gst.(*models.Guest).ID + guestIDs[i] = gst.GetId() } }() hostGuests, err := utils.GroupBy(guests, func(obj interface{}) (string, error) { - gst, ok := obj.(*models.Guest) + gst, ok := obj.(computemodels.SGuest) if !ok { - return "", utils.ConvertError(obj, "*models.Guest") + return "", utils.ConvertError(obj, "computemodels.SGuest") } - return gst.HostID, nil + return gst.HostId, nil }) if err != nil { errMessageChannel <- err @@ -688,11 +658,11 @@ func (b *HostBuilder) setGuests(ids []string, errMessageChannel chan error) { } hostBackupGuests, err := utils.GroupBy(guests, func(obj interface{}) (string, error) { - gst, ok := obj.(*models.Guest) + gst, ok := obj.(computemodels.SGuest) if !ok { - return "", utils.ConvertError(obj, "*models.Guest") + return "", utils.ConvertError(obj, "computemodels.SGuest") } - return gst.BackupHostID, nil + return gst.BackupHostId, nil }) if err != nil { errMessageChannel <- err @@ -700,11 +670,11 @@ func (b *HostBuilder) setGuests(ids []string, errMessageChannel chan error) { } guestDict, err := utils.ToDict(guests, func(obj interface{}) (string, error) { - gst, ok := obj.(*models.Guest) + gst, ok := obj.(computemodels.SGuest) if !ok { - return "", utils.ConvertError(obj, "*models.Guest") + return "", utils.ConvertError(obj, "computemodels.SGuest") } - return gst.ID, nil + return gst.GetId(), nil }) if err != nil { errMessageChannel <- err @@ -913,7 +883,23 @@ func (b *HostBuilder) Type() string { } func (b *HostBuilder) AllIDs() ([]string, error) { - return models.AllHostIDs() + q := computemodels.HostManager.Query("id") + q = q.Filter(sqlchemy.NotEquals(q.Field("host_type"), computemodels.HOST_TYPE_BAREMETAL)) + rs, err := q.Rows() + if err != nil { + return nil, err + } + ret := []string{} + defer rs.Close() + for rs.Next() { + var id string + if err := rs.Scan(&id); err != nil { + return nil, err + } + ret = append(ret, id) + } + log.Errorf("==============================\n ============================get hostsIDs: %v", ret) + return ret, nil } func (b *HostBuilder) Do(ids []string, dbCache DBGroupCacher, syncCache SyncGroupCacher) ([]interface{}, error) { @@ -940,7 +926,7 @@ func (b *HostBuilder) build() ([]interface{}, error) { return } host := b.hosts[i] - desc, err := b.buildOne(host.(*models.Host)) + desc, err := b.buildOne(&host) if err != nil { descResultLock.Lock() errs = append(errs, err) @@ -963,27 +949,19 @@ func (b *HostBuilder) build() ([]interface{}, error) { return schedDescs, nil } -func (b *HostBuilder) buildOne(host *models.Host) (interface{}, error) { +func (b *HostBuilder) buildOne(host *computemodels.SHost) (interface{}, error) { baseDesc, err := newBaseHostDesc(host) if err != nil { return nil, err } desc := &HostDesc{ - baseHostDesc: baseDesc, + BaseHostDesc: baseDesc, } desc.Metadata = make(map[string]string) - desc.ManagerID = host.ManagerID - desc.CPUCmtbound = host.CPUOverCommitBound() - desc.CPUDesc = host.CPUDesc - desc.CPUCache = host.CPUCache - desc.CPUReserved = host.CPUReserved - desc.NodeCount = host.NodeCount - desc.CPUMHZ = host.CPUMHZ - - desc.MemCmtbound = host.MemOverCommitBound() - desc.MemReserved = host.MemReserved + desc.CPUCmtbound = host.GetCPUOvercommitBound() + desc.MemCmtbound = host.GetMemoryOvercommitBound() desc.GuestReservedResource = NewGuestReservedResourceByBuilder(b, host) guestRsvdUsed, err := NewGuestReservedResourceUsedByBuilder(b, host) @@ -992,9 +970,10 @@ func (b *HostBuilder) buildOne(host *models.Host) (interface{}, error) { } desc.GuestReservedResourceUsed = guestRsvdUsed - fillFuncs := []func(*HostDesc, *models.Host) error{ + fillFuncs := []func(*HostDesc, *computemodels.SHost) error{ b.fillGuestsResourceInfo, b.fillStorages, + b.fillSchedtags, b.fillResidentGroups, b.fillMetadata, b.fillIsolatedDevices, @@ -1020,7 +999,7 @@ func _in(s string, ss []string) bool { return false } -func (b *HostBuilder) fillGuestsResourceInfo(desc *HostDesc, host *models.Host) error { +func (b *HostBuilder) fillGuestsResourceInfo(desc *HostDesc, host *computemodels.SHost) error { var ( guestCount int64 runningCount int64 @@ -1036,37 +1015,37 @@ func (b *HostBuilder) fillGuestsResourceInfo(desc *HostDesc, host *models.Host) creatingCPUCount int64 creatingGuestCount int64 ) - guestsOnHost, ok := b.hostGuests[host.ID] + guestsOnHost, ok := b.hostGuests[host.Id] if !ok { guestsOnHost = []interface{}{} } - backupGuestsOnHost, ok := b.hostBackupGuests[host.ID] + backupGuestsOnHost, ok := b.hostBackupGuests[host.Id] if ok { guestsOnHost = append(guestsOnHost, backupGuestsOnHost...) } for _, gst := range guestsOnHost { - guest := gst.(*models.Guest) - if guest.IsRunning() { + guest := gst.(computemodels.SGuest) + if IsGuestRunning(guest) { runningCount++ - memSize += guest.VMemSize - cpuCount += guest.VCPUCount - } else if guest.IsCreating() { + memSize += int64(guest.VmemSize) + cpuCount += int64(guest.VcpuCount) + } else if IsGuestCreating(guest) { creatingGuestCount++ - creatingMemSize += guest.VMemSize - creatingCPUCount += guest.VCPUCount - } else if guest.IsGuestFakeDeleted() && _in(guest.Status, []string{models.VmReady}) { - memFakeDeletedSize += guest.VMemSize - cpuFakeDeletedCount += guest.VCPUCount + creatingMemSize += int64(guest.VmemSize) + creatingCPUCount += int64(guest.VcpuCount) + } else if IsGuestPendingDelete(guest) { + memFakeDeletedSize += int64(guest.VmemSize) + cpuFakeDeletedCount += int64(guest.VcpuCount) } guestCount++ - cpuReqCount += guest.VCPUCount - memReqSize += guest.VMemSize + cpuReqCount += int64(guest.VcpuCount) + memReqSize += int64(guest.VmemSize) appTags := b.guestAppTags(guest) for _, tag := range appTags { if tag == "cpu_bound" { - cpuBoundCount += guest.VCPUCount + cpuBoundCount += int64(guest.VcpuCount) } else if tag == "io_bound" { ioBoundCount++ } @@ -1084,12 +1063,12 @@ func (b *HostBuilder) fillGuestsResourceInfo(desc *HostDesc, host *models.Host) desc.CreatingCPUCount = creatingCPUCount desc.FakeDeletedCPUCount = cpuFakeDeletedCount - desc.TotalMemSize = int64(float64(desc.MemSize) * desc.MemCmtbound) - desc.TotalCPUCount = int64(float64(desc.CPUCount) * desc.CPUCmtbound) + desc.TotalMemSize = int64(float32(desc.MemSize) * desc.MemCmtbound) + desc.TotalCPUCount = int64(float32(desc.CpuCount) * desc.CPUCmtbound) var memFreeSize int64 var cpuFreeCount int64 - if o.GetOptions().IgnoreNonRunningGuests { + if o.GetOptions().IgnoreNonrunningGuests { memFreeSize = desc.TotalMemSize - desc.RunningMemSize - desc.CreatingMemSize cpuFreeCount = desc.TotalCPUCount - desc.RunningCPUCount - desc.CreatingCPUCount } else { @@ -1125,8 +1104,8 @@ func (b *HostBuilder) fillGuestsResourceInfo(desc *HostDesc, host *models.Host) return nil } -func (b *HostBuilder) guestAppTags(guest *models.Guest) []string { - metadatas, ok := b.guestMetadatasDict[guest.ID] +func (b *HostBuilder) guestAppTags(guest computemodels.SGuest) []string { + metadatas, ok := b.guestMetadatasDict[guest.GetId()] if !ok { return []string{} } @@ -1146,50 +1125,12 @@ func (b *HostBuilder) guestAppTags(guest *models.Guest) []string { return []string{} } -func (b *HostBuilder) fillStorages(desc *HostDesc, host *models.Host) error { - objs, ok := b.hostStoragesDict[host.ID] - if !ok { - return nil - } +func (b *HostBuilder) fillStorages(desc *HostDesc, host *computemodels.SHost) error { + return desc.fillStorages(b.hostStoragesDict[host.GetId()]) +} - var ( - rets = make([]*Storage, 0) - storageTypeMap = make(map[string]int, 0) - ) - for _, obj := range objs { - hostStorage, ok := obj.(*models.HostStorage) - if !ok { - return utils.ConvertError(obj, "*models.HostStorage") - } - storageID := hostStorage.StorageID - storageObj, ok := b.storageDict[storageID] - if !ok { - log.Warningf("Storage ID: %q not found when fill it", storageID) - return nil - } - storageModel := storageObj.(*models.Storage) - storage := new(Storage) - storage.ID = storageModel.ID - storage.Name = storageModel.Name - storage.Capacity = storageModel.Capacity - storage.StorageType = storageModel.StorageType - storage.UsedCapacity = b.storageUsedCapacity(storageModel, true) - storage.WasteCapacity = b.storageUsedCapacity(storageModel, false) - storage.Cmtbound = storageModel.OverCommitBound() - storage.VCapacity = storage.GetTotalSize() - storage.FreeCapacity = storage.GetFreeSize() - rets = append(rets, storage) - - storageTypeMap[storage.StorageType] = 0 - } - - desc.Storages = rets - - for storageType := range storageTypeMap { - desc.StorageTypes = append(desc.StorageTypes, storageType) - } - - return nil +func (b *HostBuilder) fillSchedtags(desc *HostDesc, host *computemodels.SHost) error { + return desc.fillSchedtags(b.schedtags) } func (b *HostBuilder) storageUsedCapacity(storage *models.Storage, ready bool) int64 { @@ -1213,8 +1154,8 @@ func (b *HostBuilder) storageUsedCapacity(storage *models.Storage, ready bool) i return total } -func (b *HostBuilder) fillResidentGroups(desc *HostDesc, host *models.Host) error { - groups, ok := b.hostGroupCountDict[host.ID] +func (b *HostBuilder) fillResidentGroups(desc *HostDesc, host *computemodels.SHost) error { + groups, ok := b.hostGroupCountDict[host.Id] if !ok { desc.Groups = nil return nil @@ -1223,8 +1164,8 @@ func (b *HostBuilder) fillResidentGroups(desc *HostDesc, host *models.Host) erro return nil } -func (b *HostBuilder) fillMetadata(desc *HostDesc, host *models.Host) error { - metadataObjs, ok := b.hostMetadatasDict[host.ID] +func (b *HostBuilder) fillMetadata(desc *HostDesc, host *computemodels.SHost) error { + metadataObjs, ok := b.hostMetadatasDict[host.Id] if !ok { return nil } @@ -1310,9 +1251,9 @@ func (b *HostBuilder) getUnusedIsolatedDevices(hostID string) (devs []*models.Is return } -func (b *HostBuilder) fillIsolatedDevices(desc *HostDesc, host *models.Host) error { +func (b *HostBuilder) fillIsolatedDevices(desc *HostDesc, host *computemodels.SHost) error { - allDevs := b.getIsolatedDevices(host.ID) + allDevs := b.getIsolatedDevices(host.Id) if len(allDevs) == 0 { return nil } @@ -1335,9 +1276,9 @@ func (b *HostBuilder) fillIsolatedDevices(desc *HostDesc, host *models.Host) err return nil } -func (b *HostBuilder) fillCPUIOLoads(desc *HostDesc, host *models.Host) error { - desc.CPULoad = b.loadByName(host.ID, "cpu_load") - desc.IOLoad = b.loadByName(host.ID, "io_load") +func (b *HostBuilder) fillCPUIOLoads(desc *HostDesc, host *computemodels.SHost) error { + desc.CPULoad = b.loadByName(host.Id, "cpu_load") + desc.IOLoad = b.loadByName(host.Id, "io_load") return nil } diff --git a/pkg/scheduler/cache/candidate/interface.go b/pkg/scheduler/cache/candidate/interface.go index f8fde13670..0335fe7a90 100644 --- a/pkg/scheduler/cache/candidate/interface.go +++ b/pkg/scheduler/cache/candidate/interface.go @@ -17,7 +17,7 @@ type SyncGroupCacher interface { } type descer interface { - UUID() string + GetId() string } type BuildActor interface { diff --git a/pkg/scheduler/cache/db/default.go b/pkg/scheduler/cache/db/default.go index bf0e208ebb..e50c16016d 100644 --- a/pkg/scheduler/cache/db/default.go +++ b/pkg/scheduler/cache/db/default.go @@ -5,9 +5,9 @@ import ( u "yunion.io/x/pkg/utils" - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/cache" "yunion.io/x/onecloud/pkg/scheduler/db/models" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) const ( diff --git a/pkg/scheduler/cache/sync/default.go b/pkg/scheduler/cache/sync/default.go index 49627d8fd7..490c8c8141 100644 --- a/pkg/scheduler/cache/sync/default.go +++ b/pkg/scheduler/cache/sync/default.go @@ -3,9 +3,9 @@ package sync import ( u "yunion.io/x/pkg/utils" - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/cache" networks_db "yunion.io/x/onecloud/pkg/scheduler/cache/sync/networks/db" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) const ( diff --git a/pkg/scheduler/core/context.go b/pkg/scheduler/core/context.go index 0ec66661f8..053d0e4852 100644 --- a/pkg/scheduler/core/context.go +++ b/pkg/scheduler/core/context.go @@ -364,6 +364,8 @@ type Unit struct { selectPlugins []SelectPlugin LogManager *SchedLogManager + + AllocatedResources map[string]*AllocatedResource } func NewScheduleUnit(info *api.SchedInfo, schedManager interface{}) *Unit { @@ -382,6 +384,7 @@ func NewScheduleUnit(info *api.SchedInfo, schedManager interface{}) *Unit { CounterManager: NewCounterManager(), LogManager: NewSchedLogManager(), SchedulerManager: schedManager, + AllocatedResources: make(map[string]*AllocatedResource), } return unit } @@ -630,3 +633,12 @@ func (u *Unit) GetFiltedData(id string, count int64) map[string]interface{} { return nil } + +func (u *Unit) GetAllocatedResource(candidateId string) *AllocatedResource { + ret, ok := u.AllocatedResources[candidateId] + if !ok { + ret = NewAllocatedResource() + u.AllocatedResources[candidateId] = ret + } + return ret +} diff --git a/pkg/scheduler/core/generic_scheduler.go b/pkg/scheduler/core/generic_scheduler.go index 97709c5d73..0dc59410c3 100644 --- a/pkg/scheduler/core/generic_scheduler.go +++ b/pkg/scheduler/core/generic_scheduler.go @@ -15,7 +15,7 @@ import ( utiltrace "yunion.io/x/pkg/util/trace" "yunion.io/x/pkg/util/workqueue" - o "yunion.io/x/onecloud/cmd/scheduler/options" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) const ( @@ -166,13 +166,14 @@ func newSchedResultByCtx(u *Unit, count int64, c Candidater) *SchedResultItem { showDetails := u.SchedInfo.ShowSuggestionDetails id := c.IndexKey() r := &SchedResultItem{ - ID: id, - Count: count, - Capacity: u.GetCapacity(id), - Name: fmt.Sprintf("%v", c.Get("Name")), - Score: u.GetScore(id).DigitString(), - Data: u.GetFiltedData(id, count), - Candidater: c, + ID: id, + Count: count, + Capacity: u.GetCapacity(id), + Name: fmt.Sprintf("%v", c.Get("Name")), + Score: u.GetScore(id).DigitString(), + Data: u.GetFiltedData(id, count), + Candidater: c, + AllocatedResource: u.GetAllocatedResource(id), } if showDetails { @@ -237,6 +238,8 @@ type SchedResultItem struct { ScoreDetails string `json:"score_details"` Candidater Candidater `json:"-"` + + *AllocatedResource } func GetCapacities(u *Unit, id string) (res map[string]int64) { diff --git a/pkg/scheduler/core/types.go b/pkg/scheduler/core/types.go index 7deb8724b4..ab420bcaef 100644 --- a/pkg/scheduler/core/types.go +++ b/pkg/scheduler/core/types.go @@ -3,8 +3,9 @@ package core import ( "yunion.io/x/jsonutils" + computemodels "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/scheduler/api" "yunion.io/x/onecloud/pkg/scheduler/core/score" - "yunion.io/x/onecloud/pkg/scheduler/db/models" ) const ( @@ -35,8 +36,19 @@ const ( KindReserved ) +type CandidatePropertyGetter interface { + Id() string + Name() string + Zone() *computemodels.SZone + Region() *computemodels.SCloudregion + HostType() string + HostSchedtags() []computemodels.SSchedtag + Storages() []*api.CandidateStorage +} + // Candidater replace host Candidate resource info type Candidater interface { + Getter() CandidatePropertyGetter // IndexKey return candidate cache item's ident, usually host ID IndexKey() string // Get return candidate cache item's value by key @@ -45,9 +57,6 @@ type Candidater interface { XGet(key string, kind Kind) interface{} Type() int - GetAggregates() []*models.Aggregate - GetHostAggregates() []*models.Aggregate - GetSchedDesc() *jsonutils.JSONDict GetGuestCount() int64 GetResourceType() string @@ -119,3 +128,18 @@ type Priority interface { // Score intervals ScoreIntervals() score.Intervals } + +type DiskAllocatedResource struct { + Index int `json:"index"` + StorageId string `json:"storage_id"` +} + +type AllocatedResource struct { + Disks []*DiskAllocatedResource `json:"disks"` +} + +func NewAllocatedResource() *AllocatedResource { + return &AllocatedResource{ + Disks: make([]*DiskAllocatedResource, 0), + } +} diff --git a/pkg/scheduler/data_manager/candidate_manager.go b/pkg/scheduler/data_manager/candidate_manager.go index 57e8afaaf2..36dd18637d 100644 --- a/pkg/scheduler/data_manager/candidate_manager.go +++ b/pkg/scheduler/data_manager/candidate_manager.go @@ -14,10 +14,10 @@ import ( ) type CandidateGetArgs struct { - ResType string - ZoneID string - PoolID string - IgnorePool bool + // ResType is candidate host_type + ResType string + RegionID string + ZoneID string } type DataManager struct { @@ -200,10 +200,28 @@ func (cm *CandidateManager) GetCandidates(args CandidateGetArgs) ([]core.Candida return nil, err2 } - hasZone := len(args.ZoneID) > 0 - result := []core.Candidater{} + matchZone := func(r core.Candidater, zoneId string) bool { + if args.ZoneID != "" { + if r.Getter().Zone().GetId() == zoneId { + return true + } + return false + } + return true + } + + matchRegion := func(r core.Candidater, regionId string) bool { + if args.RegionID != "" { + if r.Getter().Region().GetId() == regionId { + return true + } + return false + } + return true + } + for _, c := range candidates { r := c.(core.Candidater) @@ -211,11 +229,15 @@ func (cm *CandidateManager) GetCandidates(args CandidateGetArgs) ([]core.Candida continue } - if args.IgnorePool { - result = append(result, r) - } else if (!hasZone || r.Get("ZoneID") == args.ZoneID) && r.Get("PoolID") == args.PoolID { - result = append(result, r) + if !matchRegion(r, args.RegionID) { + continue } + + if !matchZone(r, args.ZoneID) { + continue + } + + result = append(result, r) } return result, nil diff --git a/pkg/scheduler/db/models/host.go b/pkg/scheduler/db/models/host.go index e8d2e13083..a618795e50 100644 --- a/pkg/scheduler/db/models/host.go +++ b/pkg/scheduler/db/models/host.go @@ -6,8 +6,8 @@ import ( "github.com/jinzhu/gorm" - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/api" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) const ( @@ -98,7 +98,7 @@ func (h Host) CPUOverCommitBound() float64 { if h.CPUCmtbound != nil { return *h.CPUCmtbound } - return float64(o.GetOptions().DefaultCpuOvercommitBound) + return float64(o.GetOptions().DefaultCPUOvercommitBound) } func (h Host) MemOverCommitBound() float64 { diff --git a/pkg/scheduler/db/models/storage.go b/pkg/scheduler/db/models/storage.go index d21235eeb3..eacc467860 100644 --- a/pkg/scheduler/db/models/storage.go +++ b/pkg/scheduler/db/models/storage.go @@ -8,7 +8,7 @@ import ( "yunion.io/x/log" - o "yunion.io/x/onecloud/cmd/scheduler/options" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type Storage struct { diff --git a/pkg/scheduler/handler/handler.go b/pkg/scheduler/handler/handler.go index cefeb042d0..4c6e8b6394 100644 --- a/pkg/scheduler/handler/handler.go +++ b/pkg/scheduler/handler/handler.go @@ -15,6 +15,8 @@ import ( "yunion.io/x/onecloud/pkg/scheduler/core" "yunion.io/x/onecloud/pkg/scheduler/db/models" schedman "yunion.io/x/onecloud/pkg/scheduler/manager" + + computemodels "yunion.io/x/onecloud/pkg/compute/models" ) // InstallHandler is an interface that registes route and @@ -149,13 +151,7 @@ func doSchedulerForecast(c *gin.Context) { } func doCandidateList(c *gin.Context) { - sjson, err := simplejson.NewFromReader(c.Request.Body) - if err != nil { - c.AbortWithError(http.StatusBadRequest, err) - return - } - - args, err := api.NewCandidateListArgs(sjson) + args, err := api.NewCandidateListArgs(c.Request.Body) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return @@ -171,22 +167,22 @@ func doCandidateList(c *gin.Context) { } func doCandidateDetail(c *gin.Context, id string) { - hs, err := models.FetchHostByIDs([]string{id}) + hs, err := computemodels.HostManager.FetchById(id) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } - if len(hs) == 0 { + if hs == nil { c.AbortWithError(http.StatusNotFound, fmt.Errorf("Candidate %s not found.", id)) return } - host := hs[0].(*models.Host) + host := hs.(*computemodels.SHost) args := new(api.CandidateDetailArgs) args.ID = id - if !host.IsHypervisor() { + if host.HostType == computemodels.HOST_TYPE_BAREMETAL { args.Type = api.HostTypeBaremetal } else { args.Type = api.HostTypeHost @@ -198,7 +194,7 @@ func doCandidateDetail(c *gin.Context, id string) { return } - c.JSON(http.StatusOK, result) + SendJSON(c, http.StatusOK, result) } func doCleanup(c *gin.Context) { diff --git a/pkg/scheduler/handler/helper.go b/pkg/scheduler/handler/helper.go new file mode 100644 index 0000000000..6d3499b123 --- /dev/null +++ b/pkg/scheduler/handler/helper.go @@ -0,0 +1,27 @@ +package handler + +import ( + "net/http" + + "gopkg.in/gin-gonic/gin.v1" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/appsrv" +) + +func SendJSON(c *gin.Context, code int, obj interface{}) { + c.Render(http.StatusOK, JSON{Data: obj}) +} + +type JSON struct { + Data interface{} +} + +func (r JSON) Render(w http.ResponseWriter) error { + appsrv.SendJSON(w, jsonutils.Marshal(r.Data)) + return nil +} + +func (r JSON) WriteContentType(w http.ResponseWriter) { +} diff --git a/pkg/scheduler/handler/ping.go b/pkg/scheduler/handler/ping.go index cbe25bc93b..dce80136eb 100644 --- a/pkg/scheduler/handler/ping.go +++ b/pkg/scheduler/handler/ping.go @@ -8,8 +8,8 @@ import ( "yunion.io/x/log" - o "yunion.io/x/onecloud/cmd/scheduler/options" schedman "yunion.io/x/onecloud/pkg/scheduler/manager" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) var counter = 0 diff --git a/pkg/scheduler/manager/completed_queue.go b/pkg/scheduler/manager/completed_queue.go index b65a203a94..55263b9f04 100644 --- a/pkg/scheduler/manager/completed_queue.go +++ b/pkg/scheduler/manager/completed_queue.go @@ -6,8 +6,8 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/utils" - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/api" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type CompletedManager struct { diff --git a/pkg/scheduler/manager/expire_queue.go b/pkg/scheduler/manager/expire_queue.go index 2069f0a27e..ca74a90492 100644 --- a/pkg/scheduler/manager/expire_queue.go +++ b/pkg/scheduler/manager/expire_queue.go @@ -7,8 +7,8 @@ import ( "yunion.io/x/log" u "yunion.io/x/pkg/utils" - o "yunion.io/x/onecloud/cmd/scheduler/options" "yunion.io/x/onecloud/pkg/scheduler/api" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type ExpireManager struct { diff --git a/pkg/scheduler/manager/manager.go b/pkg/scheduler/manager/manager.go index b4f09f7155..0b43409cf3 100644 --- a/pkg/scheduler/manager/manager.go +++ b/pkg/scheduler/manager/manager.go @@ -11,12 +11,12 @@ import ( "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" + o "yunion.io/x/onecloud/pkg/scheduler/options" "yunion.io/x/onecloud/pkg/util/k8s" ) @@ -142,10 +142,9 @@ func getHostCandidatesList(args *api.CandidateListArgs) (*api.CandidateListResul r.Limit = args.Limit r.Offset = args.Offset cs, err := GetCandidateManager().GetCandidates(data_manager.CandidateGetArgs{ - ResType: "host", - ZoneID: args.Zone, - PoolID: args.Pool, - IgnorePool: defaultIgnorePool, + ResType: "host", + ZoneID: args.Zone, + RegionID: args.Region, }) if err != nil { return nil, fmt.Errorf("Get host candidates err: %v", err) @@ -158,10 +157,9 @@ func getBaremetalCandidatesList(args *api.CandidateListArgs) (*api.CandidateList r.Limit = args.Limit r.Offset = args.Offset cs, err := GetCandidateManager().GetCandidates(data_manager.CandidateGetArgs{ - ResType: "baremetal", - ZoneID: args.Zone, - PoolID: args.Pool, - IgnorePool: defaultIgnorePool, + ResType: "baremetal", + ZoneID: args.Zone, + RegionID: args.Region, }) if err != nil { return nil, fmt.Errorf("Get baremetal candidates err: %v", err) @@ -283,12 +281,12 @@ func GetCandidateBaremetalList( mem := api.NewResultResourceInt64( c.FreeMemSize(), 0, - c.MemSize) + int64(c.MemSize)) cpu := api.NewResultResourceInt64( c.FreeCPUCount(), 0, - c.CPUCount) + int64(c.CpuCount)) storage := api.NewResultResourceInt64( c.FreeStorageSize(), diff --git a/pkg/scheduler/manager/scheduler.go b/pkg/scheduler/manager/scheduler.go index 37b855f264..35ac8903a6 100644 --- a/pkg/scheduler/manager/scheduler.go +++ b/pkg/scheduler/manager/scheduler.go @@ -25,7 +25,6 @@ func candidatesByProvider(provider CandidatesProvider, schedData *api.SchedData) args := data_manager.CandidateGetArgs{ ResType: provider.CandidateType(), ZoneID: schedData.ZoneID, - PoolID: schedData.PoolID, } hosts, err = candidateManager.GetCandidates(args) } diff --git a/pkg/scheduler/manager/task_history.go b/pkg/scheduler/manager/task_history.go index 9c234ee711..2847d083ab 100644 --- a/pkg/scheduler/manager/task_history.go +++ b/pkg/scheduler/manager/task_history.go @@ -8,7 +8,7 @@ import ( "yunion.io/x/pkg/util/wait" u "yunion.io/x/pkg/utils" - o "yunion.io/x/onecloud/cmd/scheduler/options" + o "yunion.io/x/onecloud/pkg/scheduler/options" ) type HistoryItem struct { diff --git a/pkg/scheduler/manager/task_queue.go b/pkg/scheduler/manager/task_queue.go index 06d1edd956..6fa0e167d0 100644 --- a/pkg/scheduler/manager/task_queue.go +++ b/pkg/scheduler/manager/task_queue.go @@ -188,7 +188,7 @@ func (teqm *TaskExecutorQueueManager) AddTaskExecutor( scheduler Scheduler, callback TaskExecuteCallback) *TaskExecutor { schedData := scheduler.SchedData() log.V(10).Infof("AddTaskExecutor schedData: %#v", schedData) - taskQueue := teqm.GetQueue(schedData.Type, schedData.PoolID) + taskQueue := teqm.GetQueue(schedData.Type, "") return taskQueue.AddTaskExecutor(scheduler, callback) } diff --git a/cmd/scheduler/options/options.go b/pkg/scheduler/options/options.go similarity index 59% rename from cmd/scheduler/options/options.go rename to pkg/scheduler/options/options.go index 4576913468..e8b335a274 100644 --- a/cmd/scheduler/options/options.go +++ b/pkg/scheduler/options/options.go @@ -1,19 +1,25 @@ package options import ( - "fmt" "os" - gin "gopkg.in/gin-gonic/gin.v1" - - "yunion.io/x/log" - "yunion.io/x/pkg/util/version" - "yunion.io/x/structarg" + "yunion.io/x/onecloud/pkg/cloudcommon" + "yunion.io/x/onecloud/pkg/compute/options" ) type SchedulerOptions struct { - IgnoreNonRunningGuests bool `help:"Ignore non running guests when build host memory and cpu size" default:"false" alias:"ignore-nonrunning-guests"` - IgnoreFakeDeletedGuests bool `help:"Ignore fake deleted guests when build host memory and cpu size" default:"false"` + options.ComputeOptions + + SchedOptions + + // gin http framework mode + GinMode string `help:"gin http framework work mode" default:"debug" choices:"debug|release"` +} + +type SchedOptions struct { + SchedulerPort int `help:"The port that the scheduler's http service runs on" default:"8897"` + IgnoreFakeDeletedGuests bool `help:"Ignore fake deleted guests when build host memory and cpu size" default:"false"` + AlwaysCheckAllPredicates bool `help:"Excute all predicates when scheduling" default:"false"` DisableBaremetalPredicates bool `help:"Switch to trigger baremetal related predicates" default:"false"` SchedulerTestLimit int `help:"Scheduler test items' limitations" default:"100"` @@ -30,11 +36,6 @@ type SchedulerOptions struct { PredicateParallelizeSize int `help:"Number of execute predicates parallelization" default:"14"` PriorityParallelizeSize int `help:"Number of execute priority parallelization" default:"14"` - // overcommit bound options - DefaultStorageOvercommitBound int `help:"Default storage overcommit bound" default:"1"` - DefaultCpuOvercommitBound int `help:"Default cpu overcommit bound" default:"8"` - DefaultMemoryOvercommitBound int `help:"Default memory overcommit bound" default:"1"` - // expire queue options ExpireQueueConsumptionPeriod string `help:"Expire queue consumption period" default:"3s"` ExpireQueueConsumptionTimeout string `help:"Expire queue consumption timeout" default:"10s"` @@ -81,83 +82,15 @@ type SchedulerOptions struct { WireDBCachePeriod string `help:"Wire database cache period" default:"5m"` } -type Options struct { - // common options - structarg.BaseOptions - Port int `help:"The port that the scheduler's http service runs on" default:"8897" alias:"scheduler-port"` - Address string `help:"The IP address to serve on (set to 0.0.0.0 for all interfaces)" default:"0.0.0.0"` +var ( + opt SchedulerOptions +) - // mysql options - SqlConnection string `help:"SQL connection string" default:"root:root@tcp(127.0.0.1:3306)/mclouds?charset=utf8&parseTime=True"` - - // log options - LogLevel string `help:"log level" default:"info" choices:"debug|info|warn|error"` - LogVerboseLevel int `help:"log verbosity level" default:"0"` - - // gin http framework mode - GinMode string `help:"gin http framework work mode" default:"debug" choices:"debug|release"` - - // cloud auth options - Region string `help:"Region name" default:"Beijing"` - AuthURL string `help:"Keystone auth URL" default:"http://10.168.26.241:35357/v2.0" alias:"auth-uri"` - AdminUser string `help:"Admin username" default:"regionadmin"` - AdminPasswd string `help:"Admin password" default:"eBVVSNaMeyzDnD8F" alias:"admin-password"` - AdminTenant string `help:"Admin tenant" default:"system" alias:"admin-tenant-name"` - - EnableSsl bool `help:"Enable https"` - SslCertfile string `help:"ssl certification file"` - SslKeyfile string `help:"ssl certification key file"` - - // scheduler options - SchedulerOptions +func GetOptions() *SchedulerOptions { + return &opt } -var options Options - -func GetOptions() *Options { - return &options -} - -func Parse() { - parser, e := structarg.NewArgumentParser(&options, - "scheduler", - `Yunion cloud scheduler`, - `Yunion Technology @ 2018`) - if e != nil { - log.Fatalf("Error define argument parser: %v", e) - } - - e = parser.ParseArgs(os.Args[1:], false) - if e != nil { - log.Fatalf("Parse arguments error: %v", e) - } - - if len(options.Config) > 0 { - e := parser.ParseFile(options.Config) - if e != nil { - log.Fatalf("Parse configuration file: %v", e) - } - } - - if options.Help { - fmt.Println(parser.HelpString()) - os.Exit(0) - } - - if options.Version { - fmt.Printf("Yunion cloud version:\n%s", version.GetJsonString()) - os.Exit(0) - } - - // log configuration - log.SetVerboseLevel(int32(options.LogVerboseLevel)) - e = log.SetLogLevelByString(log.Logger(), options.LogLevel) - if e != nil { - log.Fatalf("Set log level %q: %v", options.LogLevel, e) - } - - log.V(10).Debugf("Parsed options: %#v", options) - - // gin http framework mode configuration - gin.SetMode(options.GinMode) +func Init() { + cloudcommon.ParseOptions(&opt, os.Args, "region.conf", "scheduler") + options.Options = opt.ComputeOptions } diff --git a/pkg/scheduler/service/service.go b/pkg/scheduler/service/service.go new file mode 100644 index 0000000000..6e744e0ae6 --- /dev/null +++ b/pkg/scheduler/service/service.go @@ -0,0 +1,95 @@ +package service + +import ( + "io/ioutil" + "net" + "net/http" + "strconv" + + "gopkg.in/gin-gonic/gin.v1" + + "yunion.io/x/log" + "yunion.io/x/pkg/util/prometheus" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudcommon" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + computemodels "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/scheduler/db/models" + schedhandler "yunion.io/x/onecloud/pkg/scheduler/handler" + schedman "yunion.io/x/onecloud/pkg/scheduler/manager" + o "yunion.io/x/onecloud/pkg/scheduler/options" + "yunion.io/x/onecloud/pkg/util/gin/middleware" + + _ "yunion.io/x/onecloud/pkg/scheduler/algorithmprovider" +) + +func StartService() error { + o.Init() + opts := o.GetOptions() + dbOpts := &opts.DBOptions + + // gin http framework mode configuration + gin.SetMode(opts.GinMode) + + startSched := func() { + sqlDialect, sqlConn, err := utils.TransSQLAchemyURL(opts.SqlConnection) + if err != nil { + log.Fatalf("Invalid SqlConnection: %v", err) + } + if err := models.Init(sqlDialect, sqlConn); err != nil { + log.Fatalf("DB init error: %v, dialect: %s, url: %s", err, sqlDialect, sqlConn) + } + + stopEverything := make(chan struct{}) + schedman.InitAndStart(stopEverything) + } + + opts.Port = opts.SchedulerPort + // init region compute models + cloudcommon.InitDB(dbOpts) + defer cloudcommon.CloseDB() + + db.InitAllManagers() + + if err := computemodels.InitDB(); err != nil { + log.Fatalf("InitDB fail: %s", err) + } + + commonOpts := &opts.CommonOptions + cloudcommon.InitAuth(commonOpts, func() { + log.Infof("Auth complete!!") + startSched() + }) + + //app := cloudcommon.InitApp(commonOpts, true) + + //InitHandlers(app) + return startHTTP(opts) +} + +func startHTTP(opt *o.SchedulerOptions) error { + gin.DefaultWriter = ioutil.Discard + + router := gin.Default() + router.Use(middleware.Logger()) + router.Use(middleware.ErrorHandler) + router.Use(middleware.KeystoneTokenVerifyMiddleware()) + + prometheus.InstallHandler(router) + schedhandler.InstallHandler(router) + + server := &http.Server{ + Addr: net.JoinHostPort(opt.Address, strconv.Itoa(int(opt.Port))), + Handler: router, + } + + log.Infof("Start server on: %s:%d", opt.Address, opt.Port) + + if o.GetOptions().EnableSsl { + return server.ListenAndServeTLS(o.GetOptions().SslCertfile, + o.GetOptions().SslKeyfile) + } else { + return server.ListenAndServe() + } +} diff --git a/vendor/yunion.io/x/pkg/utils/misc.go b/vendor/yunion.io/x/pkg/utils/misc.go index b79264d6ea..f2eee46389 100644 --- a/vendor/yunion.io/x/pkg/utils/misc.go +++ b/vendor/yunion.io/x/pkg/utils/misc.go @@ -13,22 +13,32 @@ import ( type selectFunc func(obj interface{}) (string, error) -func ToDict(items []interface{}, ks selectFunc) (map[string]interface{}, error) { +func ToDict(objs interface{}, ks selectFunc) (map[string]interface{}, error) { + s := reflect.ValueOf(objs) + if s.Kind() != reflect.Slice { + return nil, fmt.Errorf("Not slice") + } res := map[string]interface{}{} - for _, item := range items { - key, err := ks(item) + for i := 0; i < s.Len(); i++ { + obj := s.Index(i).Interface() + key, err := ks(obj) if err != nil { return nil, err } - res[key] = item + res[key] = obj } return res, nil } -func GroupBy(items []interface{}, ks selectFunc) (map[string][]interface{}, error) { +func GroupBy(items interface{}, ks selectFunc) (map[string][]interface{}, error) { + s := reflect.ValueOf(items) + if s.Kind() != reflect.Slice { + return nil, fmt.Errorf("Not slice") + } res := map[string][]interface{}{} - for _, item := range items { - key, err := ks(item) + for i := 0; i < s.Len(); i++ { + obj := s.Index(i).Interface() + key, err := ks(obj) if err != nil { return nil, err } @@ -36,7 +46,7 @@ func GroupBy(items []interface{}, ks selectFunc) (map[string][]interface{}, erro if !ok { values = []interface{}{} } - values = append(values, item) + values = append(values, obj) res[key] = values } return res, nil