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