mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 04:35:16 +01:00
Merge pull request #8758 from heww/issue-8681
refactor(quota,middleware): skip overflow error when subtract resources
This commit is contained in:
commit
6198ed2634
@ -110,7 +110,8 @@ func (m *Manager) getUsageForUpdate(o orm.Ormer) (*models.QuotaUsage, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) updateUsage(o orm.Ormer, resources types.ResourceList,
|
func (m *Manager) updateUsage(o orm.Ormer, resources types.ResourceList,
|
||||||
calculate func(types.ResourceList, types.ResourceList) types.ResourceList) error {
|
calculate func(types.ResourceList, types.ResourceList) types.ResourceList,
|
||||||
|
skipOverflow bool) error {
|
||||||
|
|
||||||
quota, err := m.getQuotaForUpdate(o)
|
quota, err := m.getQuotaForUpdate(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -137,7 +138,7 @@ func (m *Manager) updateUsage(o orm.Ormer, resources types.ResourceList,
|
|||||||
return fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsed))
|
return fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := isSafe(hardLimits, used, newUsed); err != nil {
|
if err := isSafe(hardLimits, used, newUsed, skipOverflow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,14 +256,14 @@ func (m *Manager) EnsureQuota(usages types.ResourceList) error {
|
|||||||
// AddResources add resources to usage
|
// AddResources add resources to usage
|
||||||
func (m *Manager) AddResources(resources types.ResourceList) error {
|
func (m *Manager) AddResources(resources types.ResourceList) error {
|
||||||
return dao.WithTransaction(func(o orm.Ormer) error {
|
return dao.WithTransaction(func(o orm.Ormer) error {
|
||||||
return m.updateUsage(o, resources, types.Add)
|
return m.updateUsage(o, resources, types.Add, false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubtractResources subtract resources from usage
|
// SubtractResources subtract resources from usage
|
||||||
func (m *Manager) SubtractResources(resources types.ResourceList) error {
|
func (m *Manager) SubtractResources(resources types.ResourceList) error {
|
||||||
return dao.WithTransaction(func(o orm.Ormer) error {
|
return dao.WithTransaction(func(o orm.Ormer) error {
|
||||||
return m.updateUsage(o, resources, types.Subtract)
|
return m.updateUsage(o, resources, types.Subtract, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/types"
|
"github.com/goharbor/harbor/src/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isSafe(hardLimits types.ResourceList, currentUsed types.ResourceList, newUsed types.ResourceList) error {
|
func isSafe(hardLimits types.ResourceList, currentUsed types.ResourceList, newUsed types.ResourceList, skipOverflow bool) error {
|
||||||
var errs Errors
|
var errs Errors
|
||||||
|
|
||||||
for resource, value := range newUsed {
|
for resource, value := range newUsed {
|
||||||
@ -35,7 +35,7 @@ func isSafe(hardLimits types.ResourceList, currentUsed types.ResourceList, newUs
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if value > hardLimit {
|
if value > hardLimit && !skipOverflow {
|
||||||
errs = errs.Add(NewResourceOverflowError(resource, hardLimit, currentUsed[resource], value))
|
errs = errs.Add(NewResourceOverflowError(resource, hardLimit, currentUsed[resource], value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,10 @@ import (
|
|||||||
|
|
||||||
func Test_isSafe(t *testing.T) {
|
func Test_isSafe(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
hardLimits types.ResourceList
|
hardLimits types.ResourceList
|
||||||
currentUsed types.ResourceList
|
currentUsed types.ResourceList
|
||||||
newUsed types.ResourceList
|
newUsed types.ResourceList
|
||||||
|
skipOverflow bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -37,6 +38,7 @@ func Test_isSafe(t *testing.T) {
|
|||||||
types.ResourceList{types.ResourceStorage: types.UNLIMITED},
|
types.ResourceList{types.ResourceStorage: types.UNLIMITED},
|
||||||
types.ResourceList{types.ResourceStorage: 1000},
|
types.ResourceList{types.ResourceStorage: 1000},
|
||||||
types.ResourceList{types.ResourceStorage: 1000},
|
types.ResourceList{types.ResourceStorage: 1000},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
@ -46,6 +48,7 @@ func Test_isSafe(t *testing.T) {
|
|||||||
types.ResourceList{types.ResourceStorage: 100},
|
types.ResourceList{types.ResourceStorage: 100},
|
||||||
types.ResourceList{types.ResourceStorage: 10},
|
types.ResourceList{types.ResourceStorage: 10},
|
||||||
types.ResourceList{types.ResourceStorage: 1},
|
types.ResourceList{types.ResourceStorage: 1},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
@ -55,22 +58,34 @@ func Test_isSafe(t *testing.T) {
|
|||||||
types.ResourceList{types.ResourceStorage: 100},
|
types.ResourceList{types.ResourceStorage: 100},
|
||||||
types.ResourceList{types.ResourceStorage: 0},
|
types.ResourceList{types.ResourceStorage: 0},
|
||||||
types.ResourceList{types.ResourceStorage: 200},
|
types.ResourceList{types.ResourceStorage: 200},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"skip overflow",
|
||||||
|
args{
|
||||||
|
types.ResourceList{types.ResourceStorage: 100},
|
||||||
|
types.ResourceList{types.ResourceStorage: 0},
|
||||||
|
types.ResourceList{types.ResourceStorage: 200},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"hard limit not found",
|
"hard limit not found",
|
||||||
args{
|
args{
|
||||||
types.ResourceList{types.ResourceStorage: 100},
|
types.ResourceList{types.ResourceStorage: 100},
|
||||||
types.ResourceList{types.ResourceCount: 0},
|
types.ResourceList{types.ResourceCount: 0},
|
||||||
types.ResourceList{types.ResourceCount: 1},
|
types.ResourceList{types.ResourceCount: 1},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := isSafe(tt.args.hardLimits, tt.args.currentUsed, tt.args.newUsed); (err != nil) != tt.wantErr {
|
if err := isSafe(tt.args.hardLimits, tt.args.currentUsed, tt.args.newUsed, tt.args.skipOverflow); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("isSafe() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("isSafe() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -16,7 +16,9 @@ package quota
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/common/utils/redis"
|
"github.com/goharbor/harbor/src/common/utils/redis"
|
||||||
@ -24,6 +26,10 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/types"
|
"github.com/goharbor/harbor/src/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
// New ....
|
// New ....
|
||||||
func New(opts ...Option) interceptor.Interceptor {
|
func New(opts ...Option) interceptor.Interceptor {
|
||||||
options := newOptions(opts...)
|
options := newOptions(opts...)
|
||||||
@ -59,7 +65,7 @@ func (qi *quotaInterceptor) HandleRequest(req *http.Request) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = qi.reserve()
|
err = qi.doTry()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to %s resources, error: %v", qi.opts.Action, err)
|
log.Errorf("Failed to %s resources, error: %v", qi.opts.Action, err)
|
||||||
}
|
}
|
||||||
@ -80,14 +86,18 @@ func (qi *quotaInterceptor) HandleResponse(w http.ResponseWriter, req *http.Requ
|
|||||||
|
|
||||||
switch sr.Status() {
|
switch sr.Status() {
|
||||||
case opts.StatusCode:
|
case opts.StatusCode:
|
||||||
|
if err := qi.doConfirm(); err != nil {
|
||||||
|
log.Errorf("Failed to confirm for resource, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if opts.OnFulfilled != nil {
|
if opts.OnFulfilled != nil {
|
||||||
if err := opts.OnFulfilled(w, req); err != nil {
|
if err := opts.OnFulfilled(w, req); err != nil {
|
||||||
log.Errorf("Failed to handle on fulfilled, error: %v", err)
|
log.Errorf("Failed to handle on fulfilled, error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if err := qi.unreserve(); err != nil {
|
if err := qi.doCancel(); err != nil {
|
||||||
log.Errorf("Failed to %s resources, error: %v", opts.Action, err)
|
log.Errorf("Failed to cancel for resource, error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.OnRejected != nil {
|
if opts.OnRejected != nil {
|
||||||
@ -148,42 +158,65 @@ func (qi *quotaInterceptor) computeResources(req *http.Request) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qi *quotaInterceptor) reserve() error {
|
func (qi *quotaInterceptor) doTry() error {
|
||||||
if !qi.opts.EnforceResources() {
|
if !qi.opts.EnforceResources() {
|
||||||
// Do nothing in reserve resources when quota interceptor not enforce resources
|
// Do nothing in try stage when quota interceptor not enforce resources
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(qi.resources) == 0 {
|
// Add resources in try stage when it is add action
|
||||||
return nil
|
// And do nothing in confirm stage for add action
|
||||||
}
|
if len(qi.resources) != 0 && qi.opts.Action == AddAction {
|
||||||
|
|
||||||
switch qi.opts.Action {
|
|
||||||
case AddAction:
|
|
||||||
return qi.opts.Manager.AddResources(qi.resources)
|
|
||||||
case SubtractAction:
|
|
||||||
return qi.opts.Manager.SubtractResources(qi.resources)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qi *quotaInterceptor) unreserve() error {
|
|
||||||
if !qi.opts.EnforceResources() {
|
|
||||||
// Do nothing in unreserve resources when quota interceptor not enforce resources
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(qi.resources) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch qi.opts.Action {
|
|
||||||
case AddAction:
|
|
||||||
return qi.opts.Manager.SubtractResources(qi.resources)
|
|
||||||
case SubtractAction:
|
|
||||||
return qi.opts.Manager.AddResources(qi.resources)
|
return qi.opts.Manager.AddResources(qi.resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qi *quotaInterceptor) doConfirm() error {
|
||||||
|
if !qi.opts.EnforceResources() {
|
||||||
|
// Do nothing in confirm stage when quota interceptor not enforce resources
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract resources in confirm stage when it is subtract action
|
||||||
|
// And do nothing in try stage for subtract action
|
||||||
|
if len(qi.resources) != 0 && qi.opts.Action == SubtractAction {
|
||||||
|
return retry(3, 100*time.Millisecond, func() error {
|
||||||
|
return qi.opts.Manager.SubtractResources(qi.resources)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qi *quotaInterceptor) doCancel() error {
|
||||||
|
if !qi.opts.EnforceResources() {
|
||||||
|
// Do nothing in cancel stage when quota interceptor not enforce resources
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract resources back when process failed for add action
|
||||||
|
if len(qi.resources) != 0 && qi.opts.Action == AddAction {
|
||||||
|
return retry(3, 100*time.Millisecond, func() error {
|
||||||
|
return qi.opts.Manager.SubtractResources(qi.resources)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func retry(attempts int, sleep time.Duration, f func() error) error {
|
||||||
|
if err := f(); err != nil {
|
||||||
|
if attempts--; attempts > 0 {
|
||||||
|
r := time.Duration(rand.Int63n(int64(sleep)))
|
||||||
|
sleep = sleep + r/2
|
||||||
|
|
||||||
|
time.Sleep(sleep)
|
||||||
|
return retry(attempts, 2*sleep, f)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user