From b59a14518411608e8d0ef3996cb8ae51a3fac82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E9=9B=A8?= Date: Wed, 6 Nov 2019 15:06:53 +0800 Subject: [PATCH] fix: Fix that fail to create vm with unforced instance group 1. This is a bug because of latest modify about scheduler's instance_group_predicate. Should set minFree as zero when founding that host can't be used to create vm with requested forced instance group. 2. Fix some little error of instance group. 3. Add PerformEnable and PerformDisable for instance group. 4. Clean scheduler desc cache after having attached or detached guests and groups and enabled group. Cleaning was necessary when disable group because disabled group is prohibited. 5. Add package 'golang.org/x/sync/errgroup'. --- go.mod | 1 + pkg/compute/models/groups.go | 138 +++++++++++++++--- pkg/compute/models/guest_actions.go | 12 +- .../predicates/instance_group_predicate.go | 5 +- vendor/golang.org/x/sync/AUTHORS | 3 + vendor/golang.org/x/sync/CONTRIBUTORS | 3 + vendor/golang.org/x/sync/LICENSE | 27 ++++ vendor/golang.org/x/sync/PATENTS | 22 +++ vendor/golang.org/x/sync/errgroup/errgroup.go | 66 +++++++++ vendor/modules.txt | 2 + 10 files changed, 250 insertions(+), 29 deletions(-) create mode 100644 vendor/golang.org/x/sync/AUTHORS create mode 100644 vendor/golang.org/x/sync/CONTRIBUTORS create mode 100644 vendor/golang.org/x/sync/LICENSE create mode 100644 vendor/golang.org/x/sync/PATENTS create mode 100644 vendor/golang.org/x/sync/errgroup/errgroup.go diff --git a/go.mod b/go.mod index 4a54023c99..ae244c11f5 100644 --- a/go.mod +++ b/go.mod @@ -134,6 +134,7 @@ require ( go.uber.org/zap v1.10.0 // indirect golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc golang.org/x/net v0.0.0-20191007182048-72f939374954 + golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 golang.org/x/sys v0.0.0-20191008105621-543471e840be golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191008142428-8d021180e987 google.golang.org/grpc v1.19.0 diff --git a/pkg/compute/models/groups.go b/pkg/compute/models/groups.go index d5e0a9778b..d600439d6a 100644 --- a/pkg/compute/models/groups.go +++ b/pkg/compute/models/groups.go @@ -18,7 +18,9 @@ import ( "context" "database/sql" + "golang.org/x/sync/errgroup" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/sets" @@ -72,7 +74,7 @@ type SGroup struct { func (sm *SGroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - guestFilter := jsonutils.GetAnyString(query, []string{"guest", "guest_id"}) + guestFilter := jsonutils.GetAnyString(query, []string{"server", "guest"}) if len(guestFilter) != 0 { guestObj, err := GuestManager.FetchByIdOrName(userCred, guestFilter) if err != nil { @@ -84,37 +86,36 @@ func (sm *SGroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, return q, nil } -func (sp *SGroup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, +func (group *SGroup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { - extra := sp.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) - ret, _ := sp.getMoreDetails(ctx, userCred, extra) + extra := group.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + ret, _ := group.getMoreDetails(ctx, userCred, extra) return ret } -func (sp *SGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, +func (group *SGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { - extra, err := sp.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query) + extra, err := group.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query) if err != nil { return nil, err } - return sp.getMoreDetails(ctx, userCred, extra) + return group.getMoreDetails(ctx, userCred, extra) } -func (sp *SGroup) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, +func (group *SGroup) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { ret := query.(*jsonutils.JSONDict) - ret.Add(jsonutils.JSONTrue, "enabled") - q := GroupguestManager.Query().Equals("group_id", sp.Id) + q := GroupguestManager.Query().Equals("group_id", group.Id) count, _ := q.CountWithError() ret.Add(jsonutils.NewInt(int64(count)), "guest_count") return ret, nil } -func (s *SGroup) ValidateDeleteCondition(ctx context.Context) error { - q := GroupguestManager.Query().Equals("group_id", s.Id) +func (group *SGroup) ValidateDeleteCondition(ctx context.Context) error { + q := GroupguestManager.Query().Equals("group_id", group.Id) count, err := q.CountWithError() if err != nil { - return errors.Wrapf(err, "fail to check that if there are any guest in this group %s", s.Name) + return errors.Wrapf(err, "fail to check that if there are any guest in this group %s", group.Name) } if count > 0 { return httperrors.NewUnsupportOperationError("请在解绑所有主机后重试") @@ -140,7 +141,7 @@ func (group *SGroup) AllowPerformBindGuests(ctx context.Context, userCred mcclie func (group *SGroup) PerformBindGuests(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - guestIdSet, err := group.checkGuests(ctx, userCred, query, data) + guestIdSet, hostIds, err := group.checkGuests(ctx, userCred, query, data) if err != nil { return nil, err } @@ -165,6 +166,10 @@ func (group *SGroup) PerformBindGuests(ctx context.Context, userCred mcclient.To } } + err = group.clearSchedDescCache(hostIds) + if err != nil { + log.Errorf("fail to clear scheduler desc cache after binding guests successfully: %s", err.Error()) + } logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_ASSOCIATE, nil, userCred, true) return nil, nil } @@ -177,7 +182,7 @@ func (group *SGroup) AllowPerformUnbindGuests(ctx context.Context, userCred mccl func (group *SGroup) PerformUnbindGuests(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - guestIdSet, err := group.checkGuests(ctx, userCred, query, data) + guestIdSet, hostIds, err := group.checkGuests(ctx, userCred, query, data) if err != nil { return nil, err } @@ -200,30 +205,117 @@ func (group *SGroup) PerformUnbindGuests(ctx context.Context, userCred mcclient. } } + err = group.clearSchedDescCache(hostIds) + if err != nil { + log.Errorf("fail to clear scheduler desc cache after unbinding guests successfully: %s", err.Error()) + } logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_DISSOCIATE, nil, userCred, true) return nil, nil } func (group *SGroup) checkGuests(ctx context.Context, userCred mcclient.TokenCredential, - query jsonutils.JSONObject, data jsonutils.JSONObject) (sets.String, error) { + query jsonutils.JSONObject, data jsonutils.JSONObject) (guestIdSet sets.String, hostIds []string, err error) { guestIdArr := jsonutils.GetArrayOfPrefix(data, "guest") if len(guestIdArr) == 0 { - return nil, httperrors.NewMissingParameterError("guest.0 guest.1 ... ") + return nil, nil, httperrors.NewMissingParameterError("guest.0 guest.1 ... ") } - guestIdSet := sets.NewString() + guestIdSet = sets.NewString() + hostIdSet := sets.NewString() for i := range guestIdArr { guestIdStr, _ := guestIdArr[i].GetString() - guest, err := GuestManager.FetchByIdOrName(userCred, guestIdStr) + model, err := GuestManager.FetchByIdOrName(userCred, guestIdStr) if err == sql.ErrNoRows { - return nil, httperrors.NewInputParameterError("no such guest %s", guestIdStr) + return nil, nil, httperrors.NewInputParameterError("no such model %s", guestIdStr) } if err != nil { - return nil, errors.Wrapf(err, "fail to fetch guest by id or name %s", guestIdStr) + return nil, nil, errors.Wrapf(err, "fail to fetch model by id or name %s", guestIdStr) } - guestIdSet.Insert(guest.GetId()) + guestIdSet.Insert(model.GetId()) + guest := model.(*SGuest) + hostIdSet.Insert(guest.HostId) + } + hostIds = hostIdSet.List() + return +} + +func (group *SGroup) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return group.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, group, "enable") +} + +func (group *SGroup) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !group.Enabled.IsTrue() { + _, err := db.Update(group, func() error { + group.Enabled = tristate.True + return nil + }) + if err != nil { + logclient.AddSimpleActionLog(group, logclient.ACT_ENABLE, nil, userCred, false) + return nil, err + } + err = group.ClearAllScheDescCache() + if err != nil { + log.Errorf("fail to clean all sche desc cache: %s", err.Error()) + } + db.OpsLog.LogEvent(group, db.ACT_ENABLE, "", userCred) + logclient.AddSimpleActionLog(group, logclient.ACT_ENABLE, nil, userCred, true) + } + return nil, nil +} + +func (group *SGroup) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return group.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, group, "disable") +} + +func (group *SGroup) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if group.Enabled.IsTrue() { + _, err := db.Update(group, func() error { + group.Enabled = tristate.False + return nil + }) + if err != nil { + logclient.AddSimpleActionLog(group, logclient.ACT_DISABLE, nil, userCred, false) + return nil, err + } + db.OpsLog.LogEvent(group, db.ACT_DISABLE, "", userCred) + logclient.AddSimpleActionLog(group, logclient.ACT_DISABLE, nil, userCred, true) + } + return nil, nil +} + +func (group *SGroup) ClearAllScheDescCache() error { + guests, err := group.fetchAllGuests() + if err != nil { + return errors.Wrapf(err, "fail to fetch all guest of group %s", group.Id) } - return guestIdSet, nil + hostIdSet := sets.NewString() + for i := range guests { + hostIdSet.Insert(guests[i].HostId) + } + + return group.clearSchedDescCache(hostIdSet.List()) +} + +func (group *SGroup) clearSchedDescCache(hostIds []string) error { + var g errgroup.Group + for _, hostId := range hostIds { + g.Go(func() error { + return HostManager.ClearSchedDescCache(hostId) + }) + } + return g.Wait() +} + +func (group *SGroup) fetchAllGuests() ([]SGuest, error) { + ggSub := GroupguestManager.Query("guest_id").Equals("group_id", group.GetId()).SubQuery() + guestSub := GuestManager.Query().SubQuery() + q := guestSub.Query().Join(ggSub, sqlchemy.Equals(ggSub.Field("guest_id"), guestSub.Field("id"))) + guests := make([]SGuest, 0, 2) + err := db.FetchModelObjects(GuestManager, q, &guests) + if err != nil { + return nil, err + } + return guests, nil } diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 97d938092d..d70f177989 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -4190,7 +4190,11 @@ func (self *SGuest) PerformBindGroups(ctx context.Context, userCred mcclient.Tok return nil, errors.Wrapf(err, "fail to attch group %s to guest %s", groupId, self.Id) } } - + // ignore error + err = self.ClearSchedDescCache() + if err != nil { + log.Errorf("fail to clear scheduler desc cache after unbinding groups successfully") + } logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, true) return nil, nil } @@ -4224,7 +4228,11 @@ func (self *SGuest) PerformUnbindGroups(ctx context.Context, userCred mcclient.T return nil, errors.Wrapf(err, "fail to detach group %s to guest %s", joint.GroupId, self.Id) } } - + // ignore error + err = self.ClearSchedDescCache() + if err != nil { + log.Errorf("fail to clear scheduler desc cache after binding groups successfully") + } logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, true) return nil, nil } diff --git a/pkg/scheduler/algorithm/predicates/instance_group_predicate.go b/pkg/scheduler/algorithm/predicates/instance_group_predicate.go index af4d195463..dbb5494421 100644 --- a/pkg/scheduler/algorithm/predicates/instance_group_predicate.go +++ b/pkg/scheduler/algorithm/predicates/instance_group_predicate.go @@ -78,6 +78,7 @@ func (p *SForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool, h.AppendPredicateFailMsg(fmt.Sprintf( "the number of guests with same instance group '%s' in this host has reached the upper limit", instanceGroups[id].GetName())) + minFree = 0 break } } else { @@ -87,10 +88,6 @@ func (p *SForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool, minFree = free } } - // show that minFree shoule be zero - if minFree == math.MaxInt32 { - minFree = 0 - } // chose the min capacity of groups h.SetCapacity(int64(minFree)) return h.GetResult() diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS new file mode 100644 index 0000000000..15167cd746 --- /dev/null +++ b/vendor/golang.org/x/sync/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS new file mode 100644 index 0000000000..1c4577e968 --- /dev/null +++ b/vendor/golang.org/x/sync/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 0000000000..9857fe53d3 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,66 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +package errgroup + +import ( + "context" + "sync" +) + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid and does not cancel on error. +type Group struct { + cancel func() + + wg sync.WaitGroup + + errOnce sync.Once + err error +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := context.WithCancel(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel() + } + return g.err +} + +// Go calls the given function in a new goroutine. +// +// The first call to return a non-nil error cancels the group; its error will be +// returned by Wait. +func (g *Group) Go(f func() error) { + g.wg.Add(1) + + go func() { + defer g.wg.Done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel() + } + }) + } + }() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index dcf2e2e6a7..843897a4d0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -562,6 +562,8 @@ golang.org/x/net/trace # golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 golang.org/x/oauth2 golang.org/x/oauth2/internal +# golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 +golang.org/x/sync/errgroup # golang.org/x/sys v0.0.0-20191008105621-543471e840be golang.org/x/sys/cpu golang.org/x/sys/unix