mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 02:05:41 +01:00
deprecate quota count on artifact
Fixes #11241 1, remove count quota from quota manager 2, remove count in DB scheme 3, remove UI relates on quota 4, update UT, API test and UI UT. Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
9ca87b85a5
commit
44825e819e
@ -205,3 +205,7 @@ ALTER TABLE notification_policy ADD UNIQUE (name);
|
||||
|
||||
ALTER TABLE replication_task ALTER COLUMN src_resource TYPE varchar(512);
|
||||
ALTER TABLE replication_task ALTER COLUMN dst_resource TYPE varchar(512);
|
||||
|
||||
/*remove count from quota hard and quota_usage used json*/
|
||||
UPDATE quota SET hard = hard - 'count';
|
||||
UPDATE quota_usage SET used = used - 'count';
|
||||
|
@ -159,10 +159,8 @@ func Test_quotaOrderBy(t *testing.T) {
|
||||
}{
|
||||
{"no query", args{nil}, "b.creation_time DESC"},
|
||||
{"order by unsupport field", args{query("unknow")}, "b.creation_time DESC"},
|
||||
{"order by count of hard", args{query("hard.count")}, "(CAST( (CASE WHEN (hard->>'count') IS NULL THEN '0' WHEN (hard->>'count') = '-1' THEN '9223372036854775807' ELSE (hard->>'count') END) AS BIGINT )) ASC"},
|
||||
{"order by storage of hard", args{query("hard.storage")}, "(CAST( (CASE WHEN (hard->>'storage') IS NULL THEN '0' WHEN (hard->>'storage') = '-1' THEN '9223372036854775807' ELSE (hard->>'storage') END) AS BIGINT )) ASC"},
|
||||
{"order by unsupport hard resource", args{query("hard.unknow")}, "b.creation_time DESC"},
|
||||
{"order by count of used", args{query("used.count")}, "(CAST( (CASE WHEN (used->>'count') IS NULL THEN '0' WHEN (used->>'count') = '-1' THEN '9223372036854775807' ELSE (used->>'count') END) AS BIGINT )) ASC"},
|
||||
{"order by storage of used", args{query("used.storage")}, "(CAST( (CASE WHEN (used->>'storage') IS NULL THEN '0' WHEN (used->>'storage') = '-1' THEN '9223372036854775807' ELSE (used->>'storage') END) AS BIGINT )) ASC"},
|
||||
{"order by unsupport used resource", args{query("used.unknow")}, "b.creation_time DESC"},
|
||||
}
|
||||
|
@ -151,7 +151,6 @@ func Test_quotaUsageOrderBy(t *testing.T) {
|
||||
}{
|
||||
{"no query", args{nil}, ""},
|
||||
{"order by unsupport field", args{query("unknow")}, ""},
|
||||
{"order by count of used", args{query("used.count")}, "(CAST( (CASE WHEN (used->>'count') IS NULL THEN '0' WHEN (used->>'count') = '-1' THEN '9223372036854775807' ELSE (used->>'count') END) AS BIGINT )) ASC"},
|
||||
{"order by storage of used", args{query("used.storage")}, "(CAST( (CASE WHEN (used->>'storage') IS NULL THEN '0' WHEN (used->>'storage') = '-1' THEN '9223372036854775807' ELSE (used->>'storage') END) AS BIGINT )) ASC"},
|
||||
{"order by unsupport used resource", args{query("used.unknow")}, ""},
|
||||
}
|
||||
|
@ -90,7 +90,6 @@ type OIDCSetting struct {
|
||||
|
||||
// QuotaSetting wraps the settings for Quota
|
||||
type QuotaSetting struct {
|
||||
CountPerProject int64 `json:"count_per_project"`
|
||||
StoragePerProject int64 `json:"storage_per_project"`
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,6 @@ type ProjectRequest struct {
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
CVEWhitelist CVEWhitelist `json:"cve_whitelist"`
|
||||
|
||||
CountLimit *int64 `json:"count_limit,omitempty"`
|
||||
StorageLimit *int64 `json:"storage_limit,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,6 @@ type driver struct {
|
||||
|
||||
func (d *driver) HardLimits() types.ResourceList {
|
||||
return types.ResourceList{
|
||||
types.ResourceCount: d.cfg.Get(common.CountPerProject).GetInt64(),
|
||||
types.ResourceStorage: d.cfg.Get(common.StoragePerProject).GetInt64(),
|
||||
}
|
||||
}
|
||||
@ -128,7 +127,6 @@ func (d *driver) Load(key string) (dr.RefObject, error) {
|
||||
|
||||
func (d *driver) Validate(hardLimits types.ResourceList) error {
|
||||
resources := map[types.ResourceName]bool{
|
||||
types.ResourceCount: true,
|
||||
types.ResourceStorage: true,
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ type DriverSuite struct {
|
||||
func (suite *DriverSuite) TestHardLimits() {
|
||||
driver := newDriver()
|
||||
|
||||
suite.Equal(types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: -1}, driver.HardLimits())
|
||||
suite.Equal(types.ResourceList{types.ResourceStorage: -1}, driver.HardLimits())
|
||||
}
|
||||
|
||||
func (suite *DriverSuite) TestLoad() {
|
||||
@ -59,11 +59,9 @@ func (suite *DriverSuite) TestLoad() {
|
||||
func (suite *DriverSuite) TestValidate() {
|
||||
driver := newDriver()
|
||||
|
||||
suite.Nil(driver.Validate(types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 1024}))
|
||||
suite.Error(driver.Validate(types.ResourceList{}))
|
||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceCount: 1}))
|
||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 0}))
|
||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceCount: 1, types.ResourceName("foo"): 1}))
|
||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceStorage: 0}))
|
||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceName("foo"): 1}))
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
hardLimits = types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: 1000}
|
||||
hardLimits = types.ResourceList{types.ResourceStorage: 1000}
|
||||
reference = "mock"
|
||||
)
|
||||
|
||||
@ -39,7 +39,6 @@ func init() {
|
||||
|
||||
mockHardLimitsFn := func() types.ResourceList {
|
||||
return types.ResourceList{
|
||||
types.ResourceCount: -1,
|
||||
types.ResourceStorage: -1,
|
||||
}
|
||||
}
|
||||
@ -124,7 +123,7 @@ func (suite *ManagerSuite) TestUpdateQuota() {
|
||||
mgr := suite.quotaManager()
|
||||
|
||||
id, _ := mgr.NewQuota(hardLimits)
|
||||
largeHardLimits := types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: 1000000}
|
||||
largeHardLimits := types.ResourceList{types.ResourceStorage: 1000000}
|
||||
|
||||
if err := mgr.UpdateQuota(largeHardLimits); suite.Nil(err) {
|
||||
quota, _ := dao.GetQuota(id)
|
||||
@ -136,17 +135,17 @@ func (suite *ManagerSuite) TestSetResourceUsage() {
|
||||
mgr := suite.quotaManager()
|
||||
id, _ := mgr.NewQuota(hardLimits)
|
||||
|
||||
if err := mgr.SetResourceUsage(types.ResourceCount, 999999999999999999); suite.Nil(err) {
|
||||
if err := mgr.SetResourceUsage(types.ResourceStorage, 999999999999999999); suite.Nil(err) {
|
||||
quota, _ := dao.GetQuota(id)
|
||||
suite.Equal(hardLimits, mustResourceList(quota.Hard))
|
||||
|
||||
usage, _ := dao.GetQuotaUsage(id)
|
||||
suite.Equal(types.ResourceList{types.ResourceCount: 999999999999999999, types.ResourceStorage: 0}, mustResourceList(usage.Used))
|
||||
suite.Equal(types.ResourceList{types.ResourceStorage: 999999999999999999}, mustResourceList(usage.Used))
|
||||
}
|
||||
|
||||
if err := mgr.SetResourceUsage(types.ResourceStorage, 234); suite.Nil(err) {
|
||||
usage, _ := dao.GetQuotaUsage(id)
|
||||
suite.Equal(types.ResourceList{types.ResourceCount: 999999999999999999, types.ResourceStorage: 234}, mustResourceList(usage.Used))
|
||||
suite.Equal(types.ResourceList{types.ResourceStorage: 234}, mustResourceList(usage.Used))
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,8 +153,8 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
||||
// non-existent
|
||||
nonExistRefID := "3"
|
||||
mgr := suite.quotaManager(nonExistRefID)
|
||||
infinite := types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: -1}
|
||||
usage := types.ResourceList{types.ResourceCount: 10, types.ResourceStorage: 10}
|
||||
infinite := types.ResourceList{types.ResourceStorage: -1}
|
||||
usage := types.ResourceList{types.ResourceStorage: 10}
|
||||
err := mgr.EnsureQuota(usage)
|
||||
suite.Nil(err)
|
||||
query := &models.QuotaQuery{
|
||||
@ -170,7 +169,7 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
||||
// existent
|
||||
existRefID := "4"
|
||||
mgr = suite.quotaManager(existRefID)
|
||||
used := types.ResourceList{types.ResourceCount: 11, types.ResourceStorage: 11}
|
||||
used := types.ResourceList{types.ResourceStorage: 11}
|
||||
if id, err := mgr.NewQuota(hardLimits, used); suite.Nil(err) {
|
||||
quota, _ := dao.GetQuota(id)
|
||||
suite.Equal(hardLimits, mustResourceList(quota.Hard))
|
||||
@ -179,7 +178,7 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
||||
suite.Equal(used, mustResourceList(usage.Used))
|
||||
}
|
||||
|
||||
usage2 := types.ResourceList{types.ResourceCount: 12, types.ResourceStorage: 12}
|
||||
usage2 := types.ResourceList{types.ResourceStorage: 12}
|
||||
err = mgr.EnsureQuota(usage2)
|
||||
suite.Nil(err)
|
||||
query2 := &models.QuotaQuery{
|
||||
@ -195,7 +194,7 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
||||
func (suite *ManagerSuite) TestQuotaAutoCreation() {
|
||||
for i := 0; i < 10; i++ {
|
||||
mgr := suite.quotaManager(fmt.Sprintf("%d", i))
|
||||
resource := types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 100}
|
||||
resource := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
suite.Nil(mgr.AddResources(resource))
|
||||
}
|
||||
@ -205,7 +204,7 @@ func (suite *ManagerSuite) TestAddResources() {
|
||||
mgr := suite.quotaManager()
|
||||
id, _ := mgr.NewQuota(hardLimits)
|
||||
|
||||
resource := types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 100}
|
||||
resource := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
if suite.Nil(mgr.AddResources(resource)) {
|
||||
usage, _ := dao.GetQuotaUsage(id)
|
||||
@ -214,7 +213,7 @@ func (suite *ManagerSuite) TestAddResources() {
|
||||
|
||||
if suite.Nil(mgr.AddResources(resource)) {
|
||||
usage, _ := dao.GetQuotaUsage(id)
|
||||
suite.Equal(types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 200}, mustResourceList(usage.Used))
|
||||
suite.Equal(types.ResourceList{types.ResourceStorage: 200}, mustResourceList(usage.Used))
|
||||
}
|
||||
|
||||
if err := mgr.AddResources(types.ResourceList{types.ResourceStorage: 10000}); suite.Error(err) {
|
||||
@ -230,7 +229,7 @@ func (suite *ManagerSuite) TestSubtractResources() {
|
||||
mgr := suite.quotaManager()
|
||||
id, _ := mgr.NewQuota(hardLimits)
|
||||
|
||||
resource := types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 100}
|
||||
resource := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
if suite.Nil(mgr.AddResources(resource)) {
|
||||
usage, _ := dao.GetQuotaUsage(id)
|
||||
@ -239,7 +238,7 @@ func (suite *ManagerSuite) TestSubtractResources() {
|
||||
|
||||
if suite.Nil(mgr.SubtractResources(resource)) {
|
||||
usage, _ := dao.GetQuotaUsage(id)
|
||||
suite.Equal(types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 0}, mustResourceList(usage.Used))
|
||||
suite.Equal(types.ResourceList{types.ResourceStorage: 0}, mustResourceList(usage.Used))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,9 @@ func TestValidate(t *testing.T) {
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid", args{"project", types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 1}}, false},
|
||||
{"invalid", args{"project", types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 0}}, true},
|
||||
{"not support", args{"not support", types.ResourceList{types.ResourceCount: 1}}, true},
|
||||
{"valid", args{"project", types.ResourceList{types.ResourceStorage: 1}}, false},
|
||||
{"invalid", args{"project", types.ResourceList{types.ResourceStorage: 0}}, true},
|
||||
{"not support", args{"not support", types.ResourceList{types.ResourceStorage: 1}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -19,8 +19,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ResourceCount alias types.ResourceCount
|
||||
ResourceCount = types.ResourceCount
|
||||
// ResourceStorage alias types.ResourceStorage
|
||||
ResourceStorage = types.ResourceStorage
|
||||
)
|
||||
|
@ -72,16 +72,6 @@ func Test_isSafe(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"hard limit not found",
|
||||
args{
|
||||
types.ResourceList{types.ResourceStorage: 100},
|
||||
types.ResourceList{types.ResourceCount: 0},
|
||||
types.ResourceList{types.ResourceCount: 1},
|
||||
false,
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -48,7 +48,7 @@ func (suite *ControllerTestSuite) TestGetReservedResources() {
|
||||
suite.Len(resources, 0)
|
||||
}
|
||||
|
||||
suite.Nil(ctl.setReservedResources(context.TODO(), reference, referenceID, types.ResourceList{types.ResourceCount: 1}))
|
||||
suite.Nil(ctl.setReservedResources(context.TODO(), reference, referenceID, types.ResourceList{types.ResourceStorage: 100}))
|
||||
|
||||
{
|
||||
resources, err := ctl.getReservedResources(context.TODO(), reference, referenceID)
|
||||
@ -68,7 +68,7 @@ func (suite *ControllerTestSuite) TestGetReservedResources() {
|
||||
func (suite *ControllerTestSuite) TestReserveResources() {
|
||||
quotaMgr := "atesting.Manager{}
|
||||
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
mock.OnAnything(quotaMgr, "GetByRefForUpdate").Return("a.Quota{Hard: hardLimits.String(), Used: types.Zero(hardLimits).String()}, nil)
|
||||
|
||||
@ -76,7 +76,7 @@ func (suite *ControllerTestSuite) TestReserveResources() {
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||
reference, referenceID := "reference", uuid.New().String()
|
||||
resources := types.ResourceList{types.ResourceCount: 1}
|
||||
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
suite.Nil(ctl.reserveResources(ctx, reference, referenceID, resources))
|
||||
|
||||
@ -86,7 +86,7 @@ func (suite *ControllerTestSuite) TestReserveResources() {
|
||||
func (suite *ControllerTestSuite) TestUnreserveResources() {
|
||||
quotaMgr := "atesting.Manager{}
|
||||
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
mock.OnAnything(quotaMgr, "GetByRefForUpdate").Return("a.Quota{Hard: hardLimits.String(), Used: types.Zero(hardLimits).String()}, nil)
|
||||
|
||||
@ -94,7 +94,7 @@ func (suite *ControllerTestSuite) TestUnreserveResources() {
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||
reference, referenceID := "reference", uuid.New().String()
|
||||
resources := types.ResourceList{types.ResourceCount: 1}
|
||||
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
suite.Nil(ctl.reserveResources(ctx, reference, referenceID, resources))
|
||||
|
||||
@ -108,10 +108,10 @@ func (suite *ControllerTestSuite) TestUnreserveResources() {
|
||||
func (suite *ControllerTestSuite) TestRequest() {
|
||||
quotaMgr := "atesting.Manager{}
|
||||
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
q := "a.Quota{Hard: hardLimits.String(), Used: types.Zero(hardLimits).String()}
|
||||
used := types.ResourceList{types.ResourceCount: 0}
|
||||
used := types.ResourceList{types.ResourceStorage: 0}
|
||||
|
||||
mock.OnAnything(quotaMgr, "GetByRefForUpdate").Return(q, nil)
|
||||
|
||||
@ -122,7 +122,7 @@ func (suite *ControllerTestSuite) TestRequest() {
|
||||
d := &drivertesting.Driver{}
|
||||
|
||||
mock.OnAnything(d, "CalculateUsage").Return(used, nil).Run(func(args mock.Arguments) {
|
||||
used[types.ResourceCount]++
|
||||
used[types.ResourceStorage]++
|
||||
})
|
||||
|
||||
driver.Register("mock", d)
|
||||
@ -131,7 +131,7 @@ func (suite *ControllerTestSuite) TestRequest() {
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||
reference, referenceID := "mock", "1"
|
||||
resources := types.ResourceList{types.ResourceCount: 1}
|
||||
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||
|
||||
{
|
||||
suite.Nil(ctl.Request(ctx, reference, referenceID, resources, func() error { return nil }))
|
||||
@ -151,7 +151,7 @@ func BenchmarkGetReservedResources(b *testing.B) {
|
||||
|
||||
ctx := context.TODO()
|
||||
reference, referenceID := "reference", uuid.New().String()
|
||||
ctl.setReservedResources(ctx, reference, referenceID, types.ResourceList{types.ResourceCount: 1})
|
||||
ctl.setReservedResources(ctx, reference, referenceID, types.ResourceList{types.ResourceStorage: 100})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctl.getReservedResources(ctx, reference, referenceID)
|
||||
@ -164,6 +164,6 @@ func BenchmarkSetReservedResources(b *testing.B) {
|
||||
ctx := context.TODO()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s := strconv.Itoa(i)
|
||||
ctl.setReservedResources(ctx, "reference"+s, s, types.ResourceList{types.ResourceCount: 1})
|
||||
ctl.setReservedResources(ctx, "reference"+s, s, types.ResourceList{types.ResourceStorage: 100})
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/blob"
|
||||
"github.com/goharbor/harbor/src/controller/chartmuseum"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
dr "github.com/goharbor/harbor/src/pkg/quota/driver"
|
||||
"github.com/goharbor/harbor/src/pkg/types"
|
||||
"github.com/graph-gophers/dataloader"
|
||||
@ -60,7 +59,6 @@ func (d *driver) HardLimits(ctx context.Context) types.ResourceList {
|
||||
}
|
||||
|
||||
return types.ResourceList{
|
||||
types.ResourceCount: d.cfg.Get(common.CountPerProject).GetInt64(),
|
||||
types.ResourceStorage: d.cfg.Get(common.StoragePerProject).GetInt64(),
|
||||
}
|
||||
}
|
||||
@ -87,7 +85,6 @@ func (d *driver) Load(ctx context.Context, key string) (dr.RefObject, error) {
|
||||
|
||||
func (d *driver) Validate(hardLimits types.ResourceList) error {
|
||||
resources := map[types.ResourceName]bool{
|
||||
types.ResourceCount: true,
|
||||
types.ResourceStorage: true,
|
||||
}
|
||||
|
||||
@ -116,23 +113,12 @@ func (d *driver) CalculateUsage(ctx context.Context, key string) (types.Resource
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// HACK: base=* in KeyWords to filter all artifacts
|
||||
artifactsCount, err := d.artifactCtl.Count(ctx, q.New(q.KeyWords{"project_id": projectID, "base": "*"}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chartsCount, err := d.chartCtl.Count(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size, err := d.blobCtl.CalculateTotalSizeByProject(ctx, projectID, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.ResourceList{types.ResourceCount: artifactsCount + chartsCount, types.ResourceStorage: size}, nil
|
||||
return types.ResourceList{types.ResourceStorage: size}, nil
|
||||
}
|
||||
|
||||
func newDriver() dr.Driver {
|
||||
|
@ -51,14 +51,12 @@ func (suite *DriverTestSuite) SetupTest() {
|
||||
func (suite *DriverTestSuite) TestCalculateUsage() {
|
||||
|
||||
{
|
||||
mock.OnAnything(suite.artifactCtl, "Count").Return(int64(10), nil).Once()
|
||||
mock.OnAnything(suite.blobCtl, "CalculateTotalSizeByProject").Return(int64(1000), nil).Once()
|
||||
mock.OnAnything(suite.chartCtl, "Count").Return(int64(10), nil).Once()
|
||||
|
||||
resources, err := suite.d.CalculateUsage(context.TODO(), "1")
|
||||
if suite.Nil(err) {
|
||||
suite.Len(resources, 2)
|
||||
suite.Equal(resources[types.ResourceCount], int64(20))
|
||||
suite.Len(resources, 1)
|
||||
suite.Equal(resources[types.ResourceStorage], int64(1000))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,13 +104,13 @@ func (suite *RefreshForProjectsTestSuite) TestRefreshForProjects() {
|
||||
}, nil)
|
||||
|
||||
q := "a.Quota{}
|
||||
q.SetHard(types.ResourceList{types.ResourceCount: 10})
|
||||
q.SetUsed(types.ResourceList{types.ResourceCount: 0})
|
||||
q.SetHard(types.ResourceList{types.ResourceStorage: 10})
|
||||
q.SetUsed(types.ResourceList{types.ResourceStorage: 0})
|
||||
|
||||
mock.OnAnything(suite.quotaMgr, "GetByRef").Return(q, nil)
|
||||
mock.OnAnything(suite.quotaMgr, "GetByRefForUpdate").Return(q, nil)
|
||||
mock.OnAnything(suite.quotaMgr, "Update").Return(nil)
|
||||
mock.OnAnything(suite.driver, "CalculateUsage").Return(types.ResourceList{types.ResourceCount: 1}, nil)
|
||||
mock.OnAnything(suite.driver, "CalculateUsage").Return(types.ResourceList{types.ResourceStorage: 1}, nil)
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||
RefreshForProjects(ctx)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -27,9 +26,7 @@ import (
|
||||
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
rep_event "github.com/goharbor/harbor/src/replication/event"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"github.com/goharbor/harbor/src/server/middleware/orm"
|
||||
"github.com/goharbor/harbor/src/server/middleware/quota"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -603,10 +600,7 @@ func initializeChartController() (*chartserver.Controller, error) {
|
||||
return nil, errors.New("Endpoint URL of chart storage server is malformed")
|
||||
}
|
||||
|
||||
chartVersionURL := fmt.Sprintf(`^/api/chartrepo/(?P<namespace>[^?#]+)/charts/(?P<name>[^?#]+)/(?P<version>[^?#]+)/?$`)
|
||||
skipper := middleware.NegativeSkipper(middleware.MethodAndPathSkipper(http.MethodDelete, regexp.MustCompile(chartVersionURL)))
|
||||
|
||||
controller, err := chartserver.NewController(url, orm.Middleware(), quota.UploadChartVersionMiddleware(), quota.RefreshForProjectMiddleware(skipper))
|
||||
controller, err := chartserver.NewController(url, orm.Middleware())
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to initialize chart API controller")
|
||||
}
|
||||
|
@ -134,7 +134,6 @@ func (p *ProjectAPI) Post() {
|
||||
}
|
||||
|
||||
if !p.SecurityCtx.IsSysAdmin() {
|
||||
pro.CountLimit = &setting.CountPerProject
|
||||
pro.StorageLimit = &setting.StoragePerProject
|
||||
}
|
||||
|
||||
@ -560,11 +559,6 @@ func validateProjectReq(req *models.ProjectRequest) error {
|
||||
|
||||
func projectQuotaHardLimits(req *models.ProjectRequest, setting *models.QuotaSetting) (types.ResourceList, error) {
|
||||
hardLimits := types.ResourceList{}
|
||||
if req.CountLimit != nil {
|
||||
hardLimits[types.ResourceCount] = *req.CountLimit
|
||||
} else {
|
||||
hardLimits[types.ResourceCount] = setting.CountPerProject
|
||||
}
|
||||
|
||||
if req.StorageLimit != nil {
|
||||
hardLimits[types.ResourceStorage] = *req.StorageLimit
|
||||
|
@ -476,7 +476,7 @@ func TestProjectSummary(t *testing.T) {
|
||||
} else {
|
||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||
assert.Equal(int64(1), summary.ProjectAdminCount)
|
||||
assert.Equal(map[string]int64{"count": -1, "storage": -1}, summary.Quota.Hard)
|
||||
assert.Equal(map[string]int64{"storage": -1}, summary.Quota.Hard)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
|
||||
var (
|
||||
reference = "mock"
|
||||
hardLimits = types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: -1}
|
||||
hardLimits = types.ResourceList{types.ResourceStorage: -1}
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -96,7 +96,7 @@ func TestQuotaAPIGet(t *testing.T) {
|
||||
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Equal(map[string]int64{"storage": -1, "count": -1}, quota.Hard)
|
||||
assert.Equal(map[string]int64{"storage": -1}, quota.Hard)
|
||||
|
||||
code, _, err = apiTest.QuotasGetByID(*admin, "100")
|
||||
assert.Nil(err)
|
||||
@ -116,18 +116,18 @@ func TestQuotaPut(t *testing.T) {
|
||||
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Equal(map[string]int64{"count": -1, "storage": -1}, quota.Hard)
|
||||
assert.Equal(map[string]int64{"storage": -1}, quota.Hard)
|
||||
|
||||
code, err = apiTest.QuotasPut(*admin, fmt.Sprintf("%d", quotaID), models.QuotaUpdateRequest{})
|
||||
assert.Nil(err, err)
|
||||
assert.Equal(int(400), code)
|
||||
|
||||
code, err = apiTest.QuotasPut(*admin, fmt.Sprintf("%d", quotaID), models.QuotaUpdateRequest{Hard: types.ResourceList{types.ResourceCount: 100, types.ResourceStorage: 100}})
|
||||
code, err = apiTest.QuotasPut(*admin, fmt.Sprintf("%d", quotaID), models.QuotaUpdateRequest{Hard: types.ResourceList{types.ResourceStorage: 100}})
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
|
||||
code, quota, err = apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(200), code)
|
||||
assert.Equal(map[string]int64{"count": 100, "storage": 100}, quota.Hard)
|
||||
assert.Equal(map[string]int64{"storage": 100}, quota.Hard)
|
||||
}
|
||||
|
@ -477,7 +477,6 @@ func QuotaSetting() (*models.QuotaSetting, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &models.QuotaSetting{
|
||||
CountPerProject: cfgMgr.Get(common.CountPerProject).GetInt64(),
|
||||
StoragePerProject: cfgMgr.Get(common.StoragePerProject).GetInt64(),
|
||||
}, nil
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ func (suite *DaoTestSuite) SetupSuite() {
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestCreate() {
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
usage := types.ResourceList{types.ResourceCount: 0}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||
id, err := suite.dao.Create(suite.Context(), "project", "2", hardLimits, usage)
|
||||
suite.Nil(err)
|
||||
|
||||
@ -56,8 +56,8 @@ func (suite *DaoTestSuite) TestCreate() {
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestDelete() {
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
usage := types.ResourceList{types.ResourceCount: 0}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||
|
||||
id, err := suite.dao.Create(suite.Context(), "project", "3", hardLimits, usage)
|
||||
suite.Nil(err)
|
||||
@ -77,8 +77,8 @@ func (suite *DaoTestSuite) TestDelete() {
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestGetByRef() {
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
usage := types.ResourceList{types.ResourceCount: 0}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||
|
||||
reference, referenceID := "project", "4"
|
||||
id, err := suite.dao.Create(suite.Context(), reference, referenceID, hardLimits, usage)
|
||||
@ -99,8 +99,8 @@ func (suite *DaoTestSuite) TestGetByRef() {
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
usage := types.ResourceList{types.ResourceCount: 0}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||
|
||||
reference, referenceID := "project", "5"
|
||||
id, err := suite.dao.Create(suite.Context(), reference, referenceID, hardLimits, usage)
|
||||
@ -119,7 +119,7 @@ func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
||||
suite.Nil(err)
|
||||
|
||||
used, _ := q.GetUsed()
|
||||
used[types.ResourceCount]++
|
||||
used[types.ResourceStorage]++
|
||||
q.SetUsed(used)
|
||||
|
||||
suite.dao.Update(ctx, q)
|
||||
@ -136,19 +136,19 @@ func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
||||
q, err := suite.dao.Get(suite.Context(), id)
|
||||
suite.Nil(err)
|
||||
used, _ := q.GetUsed()
|
||||
suite.Equal(count, used[types.ResourceCount])
|
||||
suite.Equal(count, used[types.ResourceStorage])
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestUpdate() {
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
||||
usage := types.ResourceList{types.ResourceCount: 0}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||
|
||||
id, err := suite.dao.Create(suite.Context(), "project", "6", hardLimits, usage)
|
||||
suite.Nil(err)
|
||||
|
||||
newHardLimits := types.ResourceList{types.ResourceCount: 2}
|
||||
newUsage := types.ResourceList{types.ResourceCount: 1}
|
||||
newHardLimits := types.ResourceList{types.ResourceStorage: 200}
|
||||
newUsage := types.ResourceList{types.ResourceStorage: 1}
|
||||
|
||||
{
|
||||
q, err := suite.dao.Get(suite.Context(), id)
|
||||
|
@ -38,7 +38,7 @@ func (suite *ManagerTestSuite) TestCreate() {
|
||||
ctx := suite.Context()
|
||||
|
||||
{
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 100}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
id, err := Mgr.Create(ctx, "project", "1000", hardLimits)
|
||||
|
||||
if suite.Nil(err) {
|
||||
@ -56,8 +56,8 @@ func (suite *ManagerTestSuite) TestCreate() {
|
||||
}
|
||||
|
||||
{
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 10}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
usage := types.ResourceList{types.ResourceStorage: 10}
|
||||
id, err := Mgr.Create(ctx, "project", "1000", hardLimits, usage)
|
||||
|
||||
if suite.Nil(err) {
|
||||
@ -79,7 +79,7 @@ func (suite *ManagerTestSuite) TestUpdate() {
|
||||
ctx := suite.Context()
|
||||
|
||||
{
|
||||
hardLimits := types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 100}
|
||||
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||
id, err := Mgr.Create(ctx, "project", "1000", hardLimits)
|
||||
|
||||
q, err := Mgr.Get(ctx, id)
|
||||
|
@ -26,8 +26,8 @@ func TestGetWarningResources(t *testing.T) {
|
||||
|
||||
q := Quota{}
|
||||
|
||||
q.SetHard(types.ResourceList{types.ResourceCount: 3})
|
||||
q.SetUsed(types.ResourceList{types.ResourceCount: 3})
|
||||
q.SetHard(types.ResourceList{types.ResourceStorage: 300})
|
||||
q.SetUsed(types.ResourceList{types.ResourceStorage: 300})
|
||||
|
||||
resources, err := q.GetWarningResources(85)
|
||||
assert.Nil(err)
|
||||
|
@ -72,16 +72,6 @@ func TestIsSafe(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"hard limit not found",
|
||||
args{
|
||||
types.ResourceList{types.ResourceStorage: 100},
|
||||
types.ResourceList{types.ResourceCount: 0},
|
||||
types.ResourceList{types.ResourceCount: 1},
|
||||
false,
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -23,8 +23,6 @@ const (
|
||||
// UNLIMITED unlimited resource value
|
||||
UNLIMITED = -1
|
||||
|
||||
// ResourceCount count, in number
|
||||
ResourceCount ResourceName = "count"
|
||||
// ResourceStorage storage size, in bytes
|
||||
ResourceStorage ResourceName = "storage"
|
||||
)
|
||||
@ -139,7 +137,7 @@ func IsNegative(a ResourceList) []ResourceName {
|
||||
// IsValidResource returns true when resource was supported
|
||||
func IsValidResource(resource ResourceName) bool {
|
||||
switch resource {
|
||||
case ResourceCount, ResourceStorage:
|
||||
case ResourceStorage:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -39,50 +39,40 @@ func (suite *ResourcesSuite) TestEquals() {
|
||||
suite.True(Equals(ResourceList{}, ResourceList{}))
|
||||
suite.True(Equals(ResourceList{ResourceStorage: 100}, ResourceList{ResourceStorage: 100}))
|
||||
suite.False(Equals(ResourceList{ResourceStorage: 100}, ResourceList{ResourceStorage: 200}))
|
||||
suite.False(Equals(ResourceList{ResourceStorage: 100}, ResourceList{ResourceStorage: 100, ResourceCount: 10}))
|
||||
suite.False(Equals(ResourceList{ResourceStorage: 100, ResourceCount: 10}, ResourceList{ResourceStorage: 100}))
|
||||
}
|
||||
|
||||
func (suite *ResourcesSuite) TestAdd() {
|
||||
res1 := ResourceList{ResourceStorage: 100}
|
||||
res2 := ResourceList{ResourceStorage: 100}
|
||||
res3 := ResourceList{ResourceStorage: 100, ResourceCount: 10}
|
||||
res4 := ResourceList{ResourceCount: 10}
|
||||
res3 := ResourceList{ResourceStorage: 100}
|
||||
|
||||
suite.Equal(res1, Add(ResourceList{}, res1))
|
||||
suite.Equal(ResourceList{ResourceStorage: 200}, Add(res1, res2))
|
||||
suite.Equal(ResourceList{ResourceStorage: 200, ResourceCount: 10}, Add(res1, res3))
|
||||
suite.Equal(ResourceList{ResourceStorage: 100, ResourceCount: 10}, Add(res1, res4))
|
||||
suite.Equal(ResourceList{ResourceStorage: 200}, Add(res1, res3))
|
||||
}
|
||||
|
||||
func (suite *ResourcesSuite) TestSubtract() {
|
||||
res1 := ResourceList{ResourceStorage: 100}
|
||||
res2 := ResourceList{ResourceStorage: 100}
|
||||
res3 := ResourceList{ResourceStorage: 100, ResourceCount: 10}
|
||||
res4 := ResourceList{ResourceCount: 10}
|
||||
res3 := ResourceList{ResourceStorage: 100}
|
||||
|
||||
suite.Equal(res1, Subtract(res1, ResourceList{}))
|
||||
suite.Equal(ResourceList{ResourceStorage: 0}, Subtract(res1, res2))
|
||||
suite.Equal(ResourceList{ResourceStorage: 0, ResourceCount: -10}, Subtract(res1, res3))
|
||||
suite.Equal(ResourceList{ResourceStorage: 100, ResourceCount: -10}, Subtract(res1, res4))
|
||||
suite.Equal(ResourceList{ResourceStorage: 0}, Subtract(res1, res3))
|
||||
}
|
||||
|
||||
func (suite *ResourcesSuite) TestZero() {
|
||||
res1 := ResourceList{ResourceStorage: 100}
|
||||
res2 := ResourceList{ResourceCount: 10, ResourceStorage: 100}
|
||||
res2 := ResourceList{ResourceStorage: 100}
|
||||
|
||||
suite.Equal(ResourceList{}, Zero(ResourceList{}))
|
||||
suite.Equal(ResourceList{ResourceStorage: 0}, Zero(res1))
|
||||
suite.Equal(ResourceList{ResourceStorage: 0, ResourceCount: 0}, Zero(res2))
|
||||
suite.Equal(ResourceList{ResourceStorage: 0}, Zero(res2))
|
||||
}
|
||||
|
||||
func (suite *ResourcesSuite) TestIsNegative() {
|
||||
suite.Len(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: 100}), 1)
|
||||
suite.Contains(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: 100}), ResourceStorage)
|
||||
|
||||
suite.Len(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: -100}), 2)
|
||||
suite.Contains(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: -100}), ResourceStorage)
|
||||
suite.Contains(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: -100}), ResourceCount)
|
||||
suite.Len(IsNegative(ResourceList{ResourceStorage: -100}), 1)
|
||||
suite.Contains(IsNegative(ResourceList{ResourceStorage: -100}), ResourceStorage)
|
||||
}
|
||||
|
||||
func TestRunResourcesSuite(t *testing.T) {
|
||||
|
@ -31,21 +31,6 @@
|
||||
<label>{{ 'PROJECT.PUBLIC' | translate}}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
<clr-input-container *ngIf="isSystemAdmin">
|
||||
<label for="create_project_count_limit" class="required">{{'PROJECT.COUNT_QUOTA' | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="bottom-left" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'PROJECT.QUOTA_UNLIMIT_TIP' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<input clrInput type="text" id="create_project_count_limit" [(ngModel)]="countLimit" name="create_project_count_limit" class="input-width"
|
||||
#projectCountLimit="ngModel" autocomplete="off">
|
||||
<clr-control-error class="tooltip-content">
|
||||
{{ 'PROJECT.COUNT_QUOTA_TIP' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control" *ngIf="isSystemAdmin">
|
||||
<label for="create_project_storage_limit" class="required clr-control-label">{{'PROJECT.STORAGE_QUOTA' | translate}}
|
||||
<clr-tooltip>
|
||||
|
@ -31,7 +31,7 @@ import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.com
|
||||
import { Project } from "../project";
|
||||
import { QuotaUnits, QuotaUnlimited } from "../../../lib/entities/shared.const";
|
||||
import { ProjectService, QuotaHardInterface } from "../../../lib/services";
|
||||
import { clone, getByte, GetIntegerAndUnit, validateCountLimit, validateLimit } from "../../../lib/utils/utils";
|
||||
import { clone, getByte, GetIntegerAndUnit, validateLimit } from "../../../lib/utils/utils";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -47,12 +47,10 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
currentForm: NgForm;
|
||||
quotaUnits = QuotaUnits;
|
||||
project: Project = new Project();
|
||||
countLimit: number;
|
||||
storageLimit: number;
|
||||
storageLimitUnit: string = QuotaUnits[3].UNIT;
|
||||
storageDefaultLimit: number;
|
||||
storageDefaultLimitUnit: string;
|
||||
countDefaultLimit: number;
|
||||
initVal: Project = new Project();
|
||||
|
||||
createProjectOpened: boolean;
|
||||
@ -124,12 +122,10 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes && changes["quotaObj"] && changes["quotaObj"].currentValue) {
|
||||
this.countLimit = this.quotaObj.count_per_project;
|
||||
this.storageLimit = GetIntegerAndUnit(this.quotaObj.storage_per_project, clone(QuotaUnits), 0, clone(QuotaUnits)).partNumberHard;
|
||||
this.storageLimitUnit = this.storageLimit === QuotaUnlimited ? QuotaUnits[3].UNIT
|
||||
: GetIntegerAndUnit(this.quotaObj.storage_per_project, clone(QuotaUnits), 0, clone(QuotaUnits)).partCharacterHard;
|
||||
|
||||
this.countDefaultLimit = this.countLimit;
|
||||
this.storageDefaultLimit = this.storageLimit;
|
||||
this.storageDefaultLimitUnit = this.storageLimitUnit;
|
||||
if (this.isSystemAdmin) {
|
||||
@ -142,8 +138,7 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
this.currentForm.form.controls['create_project_count_limit'].setValidators(
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)'),
|
||||
validateCountLimit()
|
||||
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)')
|
||||
]);
|
||||
}
|
||||
this.currentForm.form.valueChanges
|
||||
@ -171,7 +166,7 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
this.isSubmitOnGoing = true;
|
||||
const storageByte = +this.storageLimit === QuotaUnlimited ? this.storageLimit : getByte(+this.storageLimit, this.storageLimitUnit);
|
||||
this.projectService
|
||||
.createProject(this.project.name, this.project.metadata, +this.countLimit, +storageByte)
|
||||
.createProject(this.project.name, this.project.metadata, +storageByte)
|
||||
.subscribe(
|
||||
status => {
|
||||
this.isSubmitOnGoing = false;
|
||||
@ -198,7 +193,6 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
this.currentForm.controls["create_project_name"].reset();
|
||||
}
|
||||
this.inlineAlert.close();
|
||||
this.countLimit = this.countDefaultLimit ;
|
||||
this.storageLimit = this.storageDefaultLimit;
|
||||
this.storageLimitUnit = this.storageDefaultLimitUnit;
|
||||
}
|
||||
|
@ -117,7 +117,6 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
this.configService.getConfiguration()
|
||||
.subscribe((configurations: Configuration) => {
|
||||
this.quotaObj = {
|
||||
count_per_project: configurations.count_per_project ? configurations.count_per_project.value : -1,
|
||||
storage_per_project: configurations.storage_per_project ? configurations.storage_per_project.value : -1
|
||||
};
|
||||
});
|
||||
|
@ -27,23 +27,6 @@
|
||||
<div class="display-flex project-detail">
|
||||
<h5 class="mt-0">{{'SUMMARY.PROJECT_QUOTAS' | translate}}</h5>
|
||||
<div class="ml-1">
|
||||
<div class="display-flex quotas-progress">
|
||||
<label class="mr-1">{{'SUMMARY.ARTIFACT_COUNT' | translate}}</label>
|
||||
<label class="progress-label">{{ summaryInformation?.quota?.used?.count }} {{ 'QUOTA.OF' | translate }}
|
||||
{{ summaryInformation?.quota?.hard?.count ===-1?('QUOTA.UNLIMITED' | translate): summaryInformation?.quota?.hard?.count }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="progress-block progress-min-width progress-div">
|
||||
<div class="progress success" [class.danger]="summaryInformation?.quota?.hard?.count!==-1?summaryInformation?.quota?.used?.count/summaryInformation?.quota?.hard?.count>quotaDangerCoefficient:false"
|
||||
[class.warning]="summaryInformation?.quota?.hard?.count!==-1?summaryInformation?.quota?.used?.count/summaryInformation?.quota?.hard?.count<=quotaDangerCoefficient&&summaryInformation?.quota?.used?.count/summaryInformation?.quota?.hard?.count>=quotaWarningCoefficient:false">
|
||||
<progress
|
||||
value="{{summaryInformation?.quota?.hard?.count===-1? 0 : summaryInformation?.quota?.used?.count}}"
|
||||
max="{{summaryInformation?.quota?.hard?.count}}" data-displayval="100%"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-flex quotas-progress">
|
||||
<label class="mr-1">{{'SUMMARY.STORAGE_CONSUMPTION' | translate}}</label>
|
||||
<label class="progress-label">
|
||||
|
@ -7,31 +7,6 @@
|
||||
<form #quotaForm="ngForm" class=" clr-form clr-form-horizontal"
|
||||
[class.clr-form-compact-common]="!defaultTextsObj.isSystemDefaultQuota">
|
||||
|
||||
<clr-input-container>
|
||||
<label class="left-label left-label-light required" for="storage">{{ defaultTextsObj?.countQuota | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'PROJECT.QUOTA_UNLIMIT_TIP' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<div class="progress-block progress-min-width progress-div" *ngIf="!defaultTextsObj.isSystemDefaultQuota">
|
||||
<div class="progress success" [class.warning]="isWarningColor(+quotaHardLimitValue.countLimit, quotaHardLimitValue.countUsed)"
|
||||
[class.danger]="isDangerColor(+quotaHardLimitValue.countLimit, quotaHardLimitValue.countUsed)">
|
||||
<progress value="{{countInput.invalid || +quotaHardLimitValue.countLimit===-1?0:quotaHardLimitValue.countUsed}}"
|
||||
max="{{countInput.invalid?100:quotaHardLimitValue.countLimit}}" data-displayval="100%"></progress>
|
||||
</div>
|
||||
<label class="progress-label">{{ quotaHardLimitValue?.countUsed }} {{ 'QUOTA.OF' | translate }}
|
||||
{{ countInput?.valid?+quotaHardLimitValue?.countLimit===-1 ? ('QUOTA.UNLIMITED' | translate): quotaHardLimitValue?.countLimit:('QUOTA.INVALID_INPUT' | translate)}}
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
<input clrInput type="text" name="count" #countInput="ngModel" class="quota-input"
|
||||
[(ngModel)]="quotaHardLimitValue.countLimit" pattern="(^-1$)|(^([1-9]+)([0-9]+)*$)" required id="count"
|
||||
size="40" />
|
||||
<clr-control-error>{{ 'PROJECT.COUNT_QUOTA_TIP' | translate }}</clr-control-error>
|
||||
</clr-input-container>
|
||||
|
||||
<clr-input-container>
|
||||
<label for="count" class="left-label required">{{ defaultTextsObj.storageQuota | translate}}
|
||||
<clr-tooltip>
|
||||
|
@ -15,9 +15,8 @@ describe('EditProjectQuotasComponent', () => {
|
||||
const mockedEditQuota: EditQuotaQuotaInterface = {
|
||||
editQuota: "Edit Default Project Quotas",
|
||||
setQuota: "Set the default project quotas when creating new projects",
|
||||
countQuota: "Default artifact count",
|
||||
storageQuota: "Default storage consumption",
|
||||
quotaHardLimitValue: {storageLimit: -1, storageUnit: "Byte", countLimit: -1},
|
||||
quotaHardLimitValue: {storageLimit: -1, storageUnit: "Byte"},
|
||||
isSystemDefaultQuota: true
|
||||
};
|
||||
beforeEach(async(() => {
|
||||
@ -41,18 +40,19 @@ describe('EditProjectQuotasComponent', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should open', async () => {
|
||||
component.openEditQuota = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component.openEditQuotaModal(mockedEditQuota);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
let countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||
countInput.value = "100";
|
||||
countInput.dispatchEvent(new Event("input"));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(component.isValid).toBeTruthy();
|
||||
});
|
||||
// ToDo update it with storage edit?
|
||||
// it('should open', async () => {
|
||||
// component.openEditQuota = true;
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// component.openEditQuotaModal(mockedEditQuota);
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// let countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||
// countInput.value = "100";
|
||||
// countInput.dispatchEvent(new Event("input"));
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// expect(component.isValid).toBeTruthy();
|
||||
// });
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ import { InlineAlertComponent } from '../../../inline-alert/inline-alert.compone
|
||||
|
||||
import { QuotaUnits, QuotaUnlimited, QUOTA_DANGER_COEFFICIENT, QUOTA_WARNING_COEFFICIENT } from "../../../../entities/shared.const";
|
||||
|
||||
import { clone, getSuitableUnit, getByte, GetIntegerAndUnit, validateCountLimit, validateLimit } from '../../../../utils/utils';
|
||||
import { clone, getSuitableUnit, getByte, GetIntegerAndUnit, validateLimit } from '../../../../utils/utils';
|
||||
import { EditQuotaQuotaInterface, QuotaHardLimitInterface } from '../../../../services';
|
||||
import { distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
@ -22,17 +22,15 @@ import { distinctUntilChanged } from 'rxjs/operators';
|
||||
})
|
||||
export class EditProjectQuotasComponent implements OnInit {
|
||||
openEditQuota: boolean;
|
||||
defaultTextsObj: { editQuota: string; setQuota: string; countQuota: string; storageQuota: string; isSystemDefaultQuota: boolean } = {
|
||||
defaultTextsObj: { editQuota: string; setQuota: string; storageQuota: string; isSystemDefaultQuota: boolean } = {
|
||||
editQuota: '',
|
||||
setQuota: '',
|
||||
countQuota: '',
|
||||
storageQuota: '',
|
||||
isSystemDefaultQuota: false,
|
||||
};
|
||||
quotaHardLimitValue: QuotaHardLimitInterface = {
|
||||
storageLimit: -1
|
||||
, storageUnit: ''
|
||||
, countLimit: -1
|
||||
};
|
||||
quotaUnits = QuotaUnits;
|
||||
staticBackdrop = true;
|
||||
@ -73,7 +71,6 @@ export class EditProjectQuotasComponent implements OnInit {
|
||||
, storageUnit: defaultTextsObj.quotaHardLimitValue.storageLimit === QuotaUnlimited ?
|
||||
QuotaUnits[3].UNIT : GetIntegerAndUnit(defaultTextsObj.quotaHardLimitValue.storageLimit
|
||||
, clone(QuotaUnits), 0, clone(QuotaUnits)).partCharacterHard
|
||||
, countLimit: defaultTextsObj.quotaHardLimitValue.countLimit
|
||||
};
|
||||
} else {
|
||||
this.quotaHardLimitValue = {
|
||||
@ -83,15 +80,12 @@ export class EditProjectQuotasComponent implements OnInit {
|
||||
, storageUnit: defaultTextsObj.quotaHardLimitValue.hard.storage === QuotaUnlimited ?
|
||||
QuotaUnits[3].UNIT : GetIntegerAndUnit(defaultTextsObj.quotaHardLimitValue.hard.storage
|
||||
, clone(QuotaUnits), defaultTextsObj.quotaHardLimitValue.used.storage, clone(QuotaUnits)).partCharacterHard
|
||||
, countLimit: defaultTextsObj.quotaHardLimitValue.hard.count
|
||||
, id: defaultTextsObj.quotaHardLimitValue.id
|
||||
, countUsed: defaultTextsObj.quotaHardLimitValue.used.count
|
||||
, storageUsed: defaultTextsObj.quotaHardLimitValue.used.storage
|
||||
};
|
||||
}
|
||||
let defaultForm = {
|
||||
count: this.quotaHardLimitValue.countLimit
|
||||
, storage: this.quotaHardLimitValue.storageLimit
|
||||
storage: this.quotaHardLimitValue.storageLimit
|
||||
, storageUnit: this.quotaHardLimitValue.storageUnit
|
||||
};
|
||||
this.currentForm.resetForm(defaultForm);
|
||||
@ -103,16 +97,10 @@ export class EditProjectQuotasComponent implements OnInit {
|
||||
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)'),
|
||||
validateLimit(this.currentForm.form.controls['storageUnit'])
|
||||
]);
|
||||
this.currentForm.form.controls['count'].setValidators(
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)'),
|
||||
validateCountLimit()
|
||||
]);
|
||||
this.currentForm.form.valueChanges
|
||||
.pipe(distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)))
|
||||
.subscribe((data) => {
|
||||
['storage', 'storageUnit', 'count'].forEach(fieldName => {
|
||||
['storage', 'storageUnit'].forEach(fieldName => {
|
||||
if (this.currentForm.form.get(fieldName) && this.currentForm.form.get(fieldName).value !== null) {
|
||||
this.currentForm.form.get(fieldName).updateValueAndValidity();
|
||||
}
|
||||
|
@ -3,16 +3,12 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 quota-top">
|
||||
<div class="default-quota">
|
||||
<div>
|
||||
<div class="default-quota-text">
|
||||
<span class="width-10rem">{{'QUOTA.PROJECT_QUOTA_DEFAULT_ARTIFACT' | translate}}</span>
|
||||
<span class="num-count">{{ quotaHardLimitValue?.countLimit === -1 ? ('QUOTA.UNLIMITED'| translate) : quotaHardLimitValue?.countLimit }}</span>
|
||||
<button id="open-edit" class="btn btn-link btn-sm default-quota-edit-button"
|
||||
(click)="editDefaultQuota(quotaHardLimitValue)">{{'QUOTA.EDIT' | translate}}</button>
|
||||
</div>
|
||||
<div class="default-quota-text">
|
||||
<span class="width-10rem">{{'QUOTA.PROJECT_QUOTA_DEFAULT_DISK' | translate}}</span>
|
||||
<span class="num-count">{{ quotaHardLimitValue?.storageLimit === -1?('QUOTA.UNLIMITED' | translate): getIntegerAndUnit(quotaHardLimitValue?.storageLimit, 0).partNumberHard}}
|
||||
{{ quotaHardLimitValue?.storageLimit === -1?'':quotaHardLimitValue?.storageUnit }}</span>
|
||||
<button id="open-edit" class="btn btn-link btn-sm default-quota-edit-button"
|
||||
(click)="editDefaultQuota(quotaHardLimitValue)">{{'QUOTA.EDIT' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -37,26 +33,12 @@
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{'QUOTA.PROJECT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'QUOTA.OWNER' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="countComparator">{{'QUOTA.COUNT' | translate }}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="storageComparator">{{'QUOTA.STORAGE' | translate }}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'QUOTA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let quota of quotaList" [clrDgItem]='quota'>
|
||||
<clr-dg-cell>
|
||||
<a href="javascript:void(0)" (click)="goToLink(quota?.ref?.id)">{{quota?.ref?.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{quota?.ref?.owner_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="progress-block progress-min-width">
|
||||
<div class="progress success"
|
||||
[class.danger]="quota.hard.count!==-1?quota.used.count/quota.hard.count>quotaDangerCoefficient:false"
|
||||
[class.warning]="quota.hard.count!==-1?quota.used.count/quota.hard.count<=quotaDangerCoefficient &"a.used.count/quota.hard.count>=quotaWarningCoefficient:false"
|
||||
>
|
||||
<progress value="{{quota.hard.count===-1? 0 : quota.used.count}}"
|
||||
max="{{quota.hard.count}}" data-displayval="100%"></progress>
|
||||
</div>
|
||||
<label class="min-label-width">{{ quota?.used?.count }} {{ 'QUOTA.OF' | translate }}
|
||||
{{ quota?.hard.count ===-1?('QUOTA.UNLIMITED' | translate): quota?.hard?.count }}</label>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="progress-block progress-min-width">
|
||||
<div class="progress success"
|
||||
|
@ -35,11 +35,9 @@ describe('ProjectQuotasComponent', () => {
|
||||
creation_time: "12212112121",
|
||||
update_time: "12212112121",
|
||||
hard: {
|
||||
count: -1,
|
||||
storage: -1,
|
||||
},
|
||||
used: {
|
||||
count: 1234,
|
||||
storage: 1234
|
||||
},
|
||||
}
|
||||
@ -83,7 +81,6 @@ describe('ProjectQuotasComponent', () => {
|
||||
fixture = TestBed.createComponent(ProjectQuotasComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.quotaHardLimitValue = {
|
||||
countLimit: 1111,
|
||||
storageLimit: 23,
|
||||
storageUnit: 'GB'
|
||||
};
|
||||
@ -119,26 +116,27 @@ describe('ProjectQuotasComponent', () => {
|
||||
const modal: HTMLElement = fixture.nativeElement.querySelector("clr-modal");
|
||||
expect(modal).toBeTruthy();
|
||||
});
|
||||
it('edit quota', async () => {
|
||||
// wait getting list and rendering
|
||||
await timeout(10);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component.selectedRow = [component.quotaList[0]];
|
||||
component.editQuota();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||
countInput.value = "100";
|
||||
countInput.dispatchEvent(new Event("input"));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const saveButton: HTMLInputElement = fixture.nativeElement.querySelector('#edit-quota-save');
|
||||
saveButton.dispatchEvent(new Event("click"));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(spyUpdate.calls.count()).toEqual(1);
|
||||
});
|
||||
// ToDo update it with storage edit?
|
||||
// it('edit quota', async () => {
|
||||
// // wait getting list and rendering
|
||||
// await timeout(10);
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// component.selectedRow = [component.quotaList[0]];
|
||||
// component.editQuota();
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// const countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||
// countInput.value = "100";
|
||||
// countInput.dispatchEvent(new Event("input"));
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// const saveButton: HTMLInputElement = fixture.nativeElement.querySelector('#edit-quota-save');
|
||||
// saveButton.dispatchEvent(new Event("click"));
|
||||
// fixture.detectChanges();
|
||||
// await fixture.whenStable();
|
||||
// expect(spyUpdate.calls.count()).toEqual(1);
|
||||
// });
|
||||
it('should call navigate function', async () => {
|
||||
// wait getting list and rendering
|
||||
await timeout(10);
|
||||
|
@ -19,7 +19,6 @@ import { QuotaService } from "../../../services/quota.service";
|
||||
import { Router } from '@angular/router';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
const quotaSort = {
|
||||
count: 'used.count',
|
||||
storage: "used.storage",
|
||||
sortType: 'string'
|
||||
};
|
||||
@ -56,7 +55,6 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
this.config = cfg;
|
||||
this.configChange.emit(this.config);
|
||||
}
|
||||
countComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.count, quotaSort.sortType);
|
||||
storageComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.storage, quotaSort.sortType);
|
||||
selectedRow: Quota[] = [];
|
||||
|
||||
@ -71,13 +69,12 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||
const defaultTexts = [this.translate.get('QUOTA.EDIT_PROJECT_QUOTAS')
|
||||
, this.translate.get('QUOTA.SET_QUOTAS', { params: this.selectedRow[0].ref.name })
|
||||
, this.translate.get('QUOTA.COUNT_QUOTA'), this.translate.get('QUOTA.STORAGE_QUOTA')];
|
||||
, this.translate.get('QUOTA.STORAGE_QUOTA')];
|
||||
forkJoin(...defaultTexts).subscribe(res => {
|
||||
const defaultTextsObj = {
|
||||
editQuota: res[0],
|
||||
setQuota: res[1],
|
||||
countQuota: res[2],
|
||||
storageQuota: res[3],
|
||||
storageQuota: res[2],
|
||||
quotaHardLimitValue: this.selectedRow[0],
|
||||
isSystemDefaultQuota: false
|
||||
};
|
||||
@ -88,13 +85,12 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
|
||||
editDefaultQuota(quotaHardLimitValue: QuotaHardLimitInterface) {
|
||||
const defaultTexts = [this.translate.get('QUOTA.EDIT_DEFAULT_PROJECT_QUOTAS'), this.translate.get('QUOTA.SET_DEFAULT_QUOTAS')
|
||||
, this.translate.get('QUOTA.COUNT_DEFAULT_QUOTA'), this.translate.get('QUOTA.STORAGE_DEFAULT_QUOTA')];
|
||||
, this.translate.get('QUOTA.STORAGE_DEFAULT_QUOTA')];
|
||||
forkJoin(...defaultTexts).subscribe(res => {
|
||||
const defaultTextsObj = {
|
||||
editQuota: res[0],
|
||||
setQuota: res[1],
|
||||
countQuota: res[2],
|
||||
storageQuota: res[3],
|
||||
storageQuota: res[2],
|
||||
quotaHardLimitValue: quotaHardLimitValue,
|
||||
isSystemDefaultQuota: true
|
||||
};
|
||||
@ -113,9 +109,7 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
getQuotaChanges(allChanges) {
|
||||
let changes = {};
|
||||
for (let prop in allChanges) {
|
||||
if (prop === 'storage_per_project'
|
||||
|| prop === 'count_per_project'
|
||||
) {
|
||||
if (prop === 'storage_per_project') {
|
||||
changes[prop] = allChanges[prop];
|
||||
}
|
||||
}
|
||||
@ -123,7 +117,6 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
}
|
||||
|
||||
public saveConfig(configQuota): void {
|
||||
this.allConfig.count_per_project.value = configQuota.count;
|
||||
this.allConfig.storage_per_project.value = +configQuota.storage === QuotaUnlimited ?
|
||||
configQuota.storage : getByte(configQuota.storage, configQuota.storageUnit);
|
||||
let changes = this.getChanges();
|
||||
@ -157,10 +150,9 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
}
|
||||
}
|
||||
saveCurrentQuota(event) {
|
||||
let count = +event.formValue.count;
|
||||
let storage = +event.formValue.storage === QuotaUnlimited ?
|
||||
+event.formValue.storage : getByte(+event.formValue.storage, event.formValue.storageUnit);
|
||||
let rep: QuotaHard = { hard: { count, storage } };
|
||||
let rep: QuotaHard = { hard: { storage } };
|
||||
this.loading = true;
|
||||
this.quotaService.updateQuota(event.id, rep).subscribe(res => {
|
||||
this.editQuotaDialog.openEditQuota = false;
|
||||
@ -176,8 +168,7 @@ export class ProjectQuotasComponent implements OnChanges {
|
||||
const storageNumberAndUnit = this.allConfig.storage_per_project ? this.allConfig.storage_per_project.value : QuotaUnlimited;
|
||||
const storageLimit = storageNumberAndUnit;
|
||||
const storageUnit = this.getIntegerAndUnit(storageNumberAndUnit, 0).partCharacterHard;
|
||||
const countLimit = this.allConfig.count_per_project ? this.allConfig.count_per_project.value : QuotaUnlimited;
|
||||
this.quotaHardLimitValue = { storageLimit, storageUnit, countLimit };
|
||||
this.quotaHardLimitValue = { storageLimit, storageUnit };
|
||||
}
|
||||
getQuotaList(state: State) {
|
||||
if (!state || !state.page) {
|
||||
|
@ -319,19 +319,16 @@ export interface Quota {
|
||||
creation_time: string;
|
||||
update_time: string;
|
||||
hard: {
|
||||
count: number;
|
||||
storage: number;
|
||||
};
|
||||
used: {
|
||||
count: number;
|
||||
storage: number;
|
||||
};
|
||||
}
|
||||
export interface QuotaHard {
|
||||
hard: QuotaCountStorage;
|
||||
hard: QuotaStorage;
|
||||
}
|
||||
export interface QuotaCountStorage {
|
||||
count: number;
|
||||
export interface QuotaStorage {
|
||||
storage: number;
|
||||
}
|
||||
|
||||
@ -449,7 +446,6 @@ export interface SystemCVEWhitelist {
|
||||
items: Array<{ "cve_id": string; }>;
|
||||
}
|
||||
export interface QuotaHardInterface {
|
||||
count_per_project: number;
|
||||
storage_per_project: number;
|
||||
}
|
||||
|
||||
@ -457,17 +453,14 @@ export interface QuotaUnitInterface {
|
||||
UNIT: string;
|
||||
}
|
||||
export interface QuotaHardLimitInterface {
|
||||
countLimit: number;
|
||||
storageLimit: number;
|
||||
storageUnit: string;
|
||||
id?: string;
|
||||
countUsed?: string;
|
||||
storageUsed?: string;
|
||||
}
|
||||
export interface EditQuotaQuotaInterface {
|
||||
editQuota: string;
|
||||
setQuota: string;
|
||||
countQuota: string;
|
||||
storageQuota: string;
|
||||
quotaHardLimitValue: QuotaHardLimitInterface | any;
|
||||
isSystemDefaultQuota: boolean;
|
||||
|
@ -69,7 +69,7 @@ export abstract class ProjectService {
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
): Observable<HttpResponse<Project[]>>;
|
||||
abstract createProject(name: string, metadata: any, countLimit: number, storageLimit: number): Observable<any>;
|
||||
abstract createProject(name: string, metadata: any, storageLimit: number): Observable<any>;
|
||||
abstract deleteProject(projectId: number): Observable<any>;
|
||||
abstract checkProjectExists(projectName: string): Observable<any>;
|
||||
abstract checkProjectMember(projectId: number): Observable<any>;
|
||||
@ -149,13 +149,13 @@ export class ProjectDefaultService extends ProjectService {
|
||||
catchError(error => observableThrowError(error)), );
|
||||
}
|
||||
|
||||
public createProject(name: string, metadata: any, countLimit: number, storageLimit: number): Observable<any> {
|
||||
public createProject(name: string, metadata: any, storageLimit: number): Observable<any> {
|
||||
return this.http
|
||||
.post(`${ CURRENT_BASE_HREF }/projects`,
|
||||
JSON.stringify({'project_name': name, 'metadata': {
|
||||
public: metadata.public ? 'true' : 'false',
|
||||
},
|
||||
count_limit: countLimit, storage_limit: storageLimit
|
||||
storage_limit: storageLimit
|
||||
})
|
||||
, HTTP_JSON_OPTIONS).pipe(
|
||||
catchError(error => observableThrowError(error)), );
|
||||
|
@ -566,17 +566,6 @@ export const GetIntegerAndUnit = (hardNumber: number, quotaUnitsDeep: QuotaUnitI
|
||||
}
|
||||
};
|
||||
|
||||
export const validateCountLimit = () => {
|
||||
return (control: AbstractControl) => {
|
||||
if (control.value > LimitCount) {
|
||||
return {
|
||||
error: true
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
};
|
||||
|
||||
export const validateLimit = unitContrl => {
|
||||
return (control: AbstractControl) => {
|
||||
if (
|
||||
|
@ -39,7 +39,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/blob"
|
||||
"github.com/goharbor/harbor/src/pkg/distribution"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
@ -110,20 +109,6 @@ func copyArtifactResources(r *http.Request, reference, referenceID string) (type
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// HACK: base=* in KeyWords to filter all artifacts
|
||||
kw := q.KeyWords{"project_id": projectID, "digest__in": artifactDigests, "repository_name": repositoryName, "base": "*"}
|
||||
count, err := artifactController.Count(ctx, q.New(kw))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
copyCount := int64(len(artifactDigests)) - count
|
||||
|
||||
if copyCount == 0 {
|
||||
// artifacts already exist in the repository of the project
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
allBlobs, err := blobController.List(ctx, blob.ListParams{ArtifactDigests: artifactDigests})
|
||||
if err != nil {
|
||||
logger.Errorf("get blobs for artifacts %s failed, error: %v", strings.Join(artifactDigests, ", "), err)
|
||||
@ -143,7 +128,7 @@ func copyArtifactResources(r *http.Request, reference, referenceID string) (type
|
||||
}
|
||||
}
|
||||
|
||||
return types.ResourceList{types.ResourceCount: copyCount, types.ResourceStorage: size}, nil
|
||||
return types.ResourceList{types.ResourceStorage: size}, nil
|
||||
}
|
||||
|
||||
func copyArtifactResourcesEvent(level int) func(*http.Request, string, string, string) event.Metadata {
|
||||
|
@ -103,8 +103,8 @@ func (suite *CopyArtifactMiddlewareTestSuite) TestResourcesWarning() {
|
||||
|
||||
{
|
||||
q := "a.Quota{}
|
||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceCount: 50})
|
||||
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceStorage: 50})
|
||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0?from=library/photon:2.0.1", nil)
|
||||
@ -119,8 +119,8 @@ func (suite *CopyArtifactMiddlewareTestSuite) TestResourcesWarning() {
|
||||
|
||||
{
|
||||
q := "a.Quota{}
|
||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceCount: 85})
|
||||
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceStorage: 85})
|
||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0?from=library/photon:2.0.1", nil)
|
||||
|
@ -97,7 +97,7 @@ func putManifestResources(r *http.Request, reference, referenceID string) (types
|
||||
}
|
||||
}
|
||||
|
||||
return types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: size}, nil
|
||||
return types.ResourceList{types.ResourceStorage: size}, nil
|
||||
}
|
||||
|
||||
func putManifestResourcesEvent(level int) func(*http.Request, string, string, string) event.Metadata {
|
||||
|
@ -87,9 +87,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(nil, nil).Once()
|
||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||
resources := args.Get(3).(types.ResourceList)
|
||||
suite.Len(resources, 2)
|
||||
suite.Len(resources, 1)
|
||||
suite.Equal(resources[types.ResourceStorage], int64(100))
|
||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
||||
|
||||
f := args.Get(4).(func() error)
|
||||
f()
|
||||
@ -114,9 +113,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||
resources := args.Get(3).(types.ResourceList)
|
||||
suite.Len(resources, 2)
|
||||
suite.Len(resources, 1)
|
||||
suite.Equal(resources[types.ResourceStorage], int64(100+10))
|
||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
||||
|
||||
f := args.Get(4).(func() error)
|
||||
f()
|
||||
@ -141,9 +139,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||
resources := args.Get(3).(types.ResourceList)
|
||||
suite.Len(resources, 2)
|
||||
suite.Len(resources, 1)
|
||||
suite.Equal(resources[types.ResourceStorage], int64(100+20))
|
||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
||||
|
||||
f := args.Get(4).(func() error)
|
||||
f()
|
||||
@ -168,9 +165,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||
resources := args.Get(3).(types.ResourceList)
|
||||
suite.Len(resources, 2)
|
||||
suite.Len(resources, 1)
|
||||
suite.Equal(resources[types.ResourceStorage], int64(100))
|
||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
||||
|
||||
f := args.Get(4).(func() error)
|
||||
f()
|
||||
@ -197,7 +193,6 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesExceeded() {
|
||||
|
||||
{
|
||||
var errs quota.Errors
|
||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceCount, 10, 10, 11))
|
||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceStorage, 100, 100, 110))
|
||||
mock.OnAnything(suite.quotaController, "Request").Return(errs).Once()
|
||||
|
||||
@ -213,7 +208,6 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesExceeded() {
|
||||
|
||||
{
|
||||
var errs quota.Errors
|
||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceCount, 10, 10, 11))
|
||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceStorage, 100, 100, 110))
|
||||
|
||||
err := errors.DeniedError(errs).WithMessage("Quota exceeded when processing the request of %v", errs)
|
||||
@ -247,8 +241,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesWarning() {
|
||||
|
||||
{
|
||||
q := "a.Quota{}
|
||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceCount: 50})
|
||||
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceStorage: 50})
|
||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0", nil)
|
||||
@ -263,8 +257,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesWarning() {
|
||||
|
||||
{
|
||||
q := "a.Quota{}
|
||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceCount: 85})
|
||||
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||
q.SetUsed(types.ResourceList{types.ResourceStorage: 85})
|
||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0", nil)
|
||||
|
@ -164,7 +164,7 @@ func (suite *RequestMiddlewareTestSuite) TestResourcesRequestOK() {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
reference, referenceID := "project", "1"
|
||||
resources := types.ResourceList{types.ResourceCount: 1}
|
||||
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||
config := suite.makeRequestConfig(reference, referenceID, resources)
|
||||
|
||||
mock.OnAnything(suite.quotaController, "IsEnabled").Return(true, nil)
|
||||
@ -183,7 +183,7 @@ func (suite *RequestMiddlewareTestSuite) TestResourcesRequestFailed() {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
reference, referenceID := "project", "1"
|
||||
resources := types.ResourceList{types.ResourceCount: 1}
|
||||
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||
config := suite.makeRequestConfig(reference, referenceID, resources)
|
||||
|
||||
mock.OnAnything(suite.quotaController, "IsEnabled").Return(true, nil)
|
||||
|
@ -1,83 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package quota
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/pkg/types"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
)
|
||||
|
||||
// UploadChartVersionMiddleware middleware to request count resources for the project
|
||||
func UploadChartVersionMiddleware() func(http.Handler) http.Handler {
|
||||
chartsURL := `^/api/chartrepo/(?P<namespace>[^?#]+)/charts/?$`
|
||||
skipper := middleware.NegativeSkipper(middleware.MethodAndPathSkipper(http.MethodPost, regexp.MustCompile(chartsURL)))
|
||||
|
||||
return RequestMiddleware(RequestConfig{
|
||||
ReferenceObject: projectReferenceObject,
|
||||
Resources: uploadChartVersionResources,
|
||||
}, skipper)
|
||||
}
|
||||
|
||||
const (
|
||||
formFieldNameForChart = "chart"
|
||||
)
|
||||
|
||||
var (
|
||||
parseChart = func(req *http.Request) (*chart.Chart, error) {
|
||||
chartFile, _, err := req.FormFile(formFieldNameForChart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chart, err := chartutil.LoadArchive(chartFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load chart from archive failed: %s", err.Error())
|
||||
}
|
||||
|
||||
return chart, nil
|
||||
}
|
||||
)
|
||||
|
||||
func uploadChartVersionResources(r *http.Request, reference, referenceID string) (types.ResourceList, error) {
|
||||
lib.NopCloseRequest(r)
|
||||
|
||||
ct, err := parseChart(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chartName, version := ct.Metadata.Name, ct.Metadata.Version
|
||||
|
||||
projectID, _ := strconv.ParseInt(referenceID, 10, 64)
|
||||
|
||||
exist, err := chartController.Exist(r.Context(), projectID, chartName, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exist {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return types.ResourceList{types.ResourceCount: 1}, nil
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package quota
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type UploadChartVersionResourcesTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *UploadChartVersionResourcesTestSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func (suite *UploadChartVersionResourcesTestSuite) TestUploadChartVersionResources() {
|
||||
}
|
||||
|
||||
func TestUploadChartVersionResourcesTestSuite(t *testing.T) {
|
||||
suite.Run(t, &UploadChartVersionResourcesTestSuite{})
|
||||
}
|
@ -49,7 +49,6 @@ class TestProjects(unittest.TestCase):
|
||||
|
||||
#5. Get project quota
|
||||
quota = self.system.get_project_quota("project", project_id, **ADMIN_CLIENT)
|
||||
self.assertEqual(quota[0].used["count"], 1)
|
||||
self.assertEqual(quota[0].used["storage"], 2789002)
|
||||
|
||||
#6. Push the image with another tag to project(PA) by user(UA), the check the project quota usage; -- {"count": 1, "storage": 2791709}
|
||||
@ -57,7 +56,6 @@ class TestProjects(unittest.TestCase):
|
||||
|
||||
#7. Get project quota
|
||||
quota = self.system.get_project_quota("project", project_id, **ADMIN_CLIENT)
|
||||
self.assertEqual(quota[0].used["count"], 1)
|
||||
self.assertEqual(quota[0].used["storage"], 2789002)
|
||||
|
||||
#8. Delete repository(RA) by user(UA);
|
||||
@ -65,7 +63,6 @@ class TestProjects(unittest.TestCase):
|
||||
|
||||
#9. Quota should be 0
|
||||
quota = self.system.get_project_quota("project", project_id, **ADMIN_CLIENT)
|
||||
self.assertEqual(quota[0].used["count"], 0)
|
||||
self.assertEqual(quota[0].used["storage"], 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
Reference in New Issue
Block a user