mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +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 src_resource TYPE varchar(512);
|
||||||
ALTER TABLE replication_task ALTER COLUMN dst_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"},
|
{"no query", args{nil}, "b.creation_time DESC"},
|
||||||
{"order by unsupport field", args{query("unknow")}, "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 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 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 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"},
|
{"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}, ""},
|
{"no query", args{nil}, ""},
|
||||||
{"order by unsupport field", args{query("unknow")}, ""},
|
{"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 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")}, ""},
|
{"order by unsupport used resource", args{query("used.unknow")}, ""},
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,6 @@ type OIDCSetting struct {
|
|||||||
|
|
||||||
// QuotaSetting wraps the settings for Quota
|
// QuotaSetting wraps the settings for Quota
|
||||||
type QuotaSetting struct {
|
type QuotaSetting struct {
|
||||||
CountPerProject int64 `json:"count_per_project"`
|
|
||||||
StoragePerProject int64 `json:"storage_per_project"`
|
StoragePerProject int64 `json:"storage_per_project"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,6 @@ type ProjectRequest struct {
|
|||||||
Metadata map[string]string `json:"metadata"`
|
Metadata map[string]string `json:"metadata"`
|
||||||
CVEWhitelist CVEWhitelist `json:"cve_whitelist"`
|
CVEWhitelist CVEWhitelist `json:"cve_whitelist"`
|
||||||
|
|
||||||
CountLimit *int64 `json:"count_limit,omitempty"`
|
|
||||||
StorageLimit *int64 `json:"storage_limit,omitempty"`
|
StorageLimit *int64 `json:"storage_limit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,6 @@ type driver struct {
|
|||||||
|
|
||||||
func (d *driver) HardLimits() types.ResourceList {
|
func (d *driver) HardLimits() types.ResourceList {
|
||||||
return types.ResourceList{
|
return types.ResourceList{
|
||||||
types.ResourceCount: d.cfg.Get(common.CountPerProject).GetInt64(),
|
|
||||||
types.ResourceStorage: d.cfg.Get(common.StoragePerProject).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 {
|
func (d *driver) Validate(hardLimits types.ResourceList) error {
|
||||||
resources := map[types.ResourceName]bool{
|
resources := map[types.ResourceName]bool{
|
||||||
types.ResourceCount: true,
|
|
||||||
types.ResourceStorage: true,
|
types.ResourceStorage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ type DriverSuite struct {
|
|||||||
func (suite *DriverSuite) TestHardLimits() {
|
func (suite *DriverSuite) TestHardLimits() {
|
||||||
driver := newDriver()
|
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() {
|
func (suite *DriverSuite) TestLoad() {
|
||||||
@ -59,11 +59,9 @@ func (suite *DriverSuite) TestLoad() {
|
|||||||
func (suite *DriverSuite) TestValidate() {
|
func (suite *DriverSuite) TestValidate() {
|
||||||
driver := newDriver()
|
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{}))
|
||||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceCount: 1}))
|
suite.Error(driver.Validate(types.ResourceList{types.ResourceStorage: 0}))
|
||||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 0}))
|
suite.Error(driver.Validate(types.ResourceList{types.ResourceName("foo"): 1}))
|
||||||
suite.Error(driver.Validate(types.ResourceList{types.ResourceCount: 1, types.ResourceName("foo"): 1}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hardLimits = types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: 1000}
|
hardLimits = types.ResourceList{types.ResourceStorage: 1000}
|
||||||
reference = "mock"
|
reference = "mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +39,6 @@ func init() {
|
|||||||
|
|
||||||
mockHardLimitsFn := func() types.ResourceList {
|
mockHardLimitsFn := func() types.ResourceList {
|
||||||
return types.ResourceList{
|
return types.ResourceList{
|
||||||
types.ResourceCount: -1,
|
|
||||||
types.ResourceStorage: -1,
|
types.ResourceStorage: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +123,7 @@ func (suite *ManagerSuite) TestUpdateQuota() {
|
|||||||
mgr := suite.quotaManager()
|
mgr := suite.quotaManager()
|
||||||
|
|
||||||
id, _ := mgr.NewQuota(hardLimits)
|
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) {
|
if err := mgr.UpdateQuota(largeHardLimits); suite.Nil(err) {
|
||||||
quota, _ := dao.GetQuota(id)
|
quota, _ := dao.GetQuota(id)
|
||||||
@ -136,17 +135,17 @@ func (suite *ManagerSuite) TestSetResourceUsage() {
|
|||||||
mgr := suite.quotaManager()
|
mgr := suite.quotaManager()
|
||||||
id, _ := mgr.NewQuota(hardLimits)
|
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)
|
quota, _ := dao.GetQuota(id)
|
||||||
suite.Equal(hardLimits, mustResourceList(quota.Hard))
|
suite.Equal(hardLimits, mustResourceList(quota.Hard))
|
||||||
|
|
||||||
usage, _ := dao.GetQuotaUsage(id)
|
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) {
|
if err := mgr.SetResourceUsage(types.ResourceStorage, 234); suite.Nil(err) {
|
||||||
usage, _ := dao.GetQuotaUsage(id)
|
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
|
// non-existent
|
||||||
nonExistRefID := "3"
|
nonExistRefID := "3"
|
||||||
mgr := suite.quotaManager(nonExistRefID)
|
mgr := suite.quotaManager(nonExistRefID)
|
||||||
infinite := types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: -1}
|
infinite := types.ResourceList{types.ResourceStorage: -1}
|
||||||
usage := types.ResourceList{types.ResourceCount: 10, types.ResourceStorage: 10}
|
usage := types.ResourceList{types.ResourceStorage: 10}
|
||||||
err := mgr.EnsureQuota(usage)
|
err := mgr.EnsureQuota(usage)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
query := &models.QuotaQuery{
|
query := &models.QuotaQuery{
|
||||||
@ -170,7 +169,7 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
|||||||
// existent
|
// existent
|
||||||
existRefID := "4"
|
existRefID := "4"
|
||||||
mgr = suite.quotaManager(existRefID)
|
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) {
|
if id, err := mgr.NewQuota(hardLimits, used); suite.Nil(err) {
|
||||||
quota, _ := dao.GetQuota(id)
|
quota, _ := dao.GetQuota(id)
|
||||||
suite.Equal(hardLimits, mustResourceList(quota.Hard))
|
suite.Equal(hardLimits, mustResourceList(quota.Hard))
|
||||||
@ -179,7 +178,7 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
|||||||
suite.Equal(used, mustResourceList(usage.Used))
|
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)
|
err = mgr.EnsureQuota(usage2)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
query2 := &models.QuotaQuery{
|
query2 := &models.QuotaQuery{
|
||||||
@ -195,7 +194,7 @@ func (suite *ManagerSuite) TestEnsureQuota() {
|
|||||||
func (suite *ManagerSuite) TestQuotaAutoCreation() {
|
func (suite *ManagerSuite) TestQuotaAutoCreation() {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
mgr := suite.quotaManager(fmt.Sprintf("%d", 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))
|
suite.Nil(mgr.AddResources(resource))
|
||||||
}
|
}
|
||||||
@ -205,7 +204,7 @@ func (suite *ManagerSuite) TestAddResources() {
|
|||||||
mgr := suite.quotaManager()
|
mgr := suite.quotaManager()
|
||||||
id, _ := mgr.NewQuota(hardLimits)
|
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)) {
|
if suite.Nil(mgr.AddResources(resource)) {
|
||||||
usage, _ := dao.GetQuotaUsage(id)
|
usage, _ := dao.GetQuotaUsage(id)
|
||||||
@ -214,7 +213,7 @@ func (suite *ManagerSuite) TestAddResources() {
|
|||||||
|
|
||||||
if suite.Nil(mgr.AddResources(resource)) {
|
if suite.Nil(mgr.AddResources(resource)) {
|
||||||
usage, _ := dao.GetQuotaUsage(id)
|
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) {
|
if err := mgr.AddResources(types.ResourceList{types.ResourceStorage: 10000}); suite.Error(err) {
|
||||||
@ -230,7 +229,7 @@ func (suite *ManagerSuite) TestSubtractResources() {
|
|||||||
mgr := suite.quotaManager()
|
mgr := suite.quotaManager()
|
||||||
id, _ := mgr.NewQuota(hardLimits)
|
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)) {
|
if suite.Nil(mgr.AddResources(resource)) {
|
||||||
usage, _ := dao.GetQuotaUsage(id)
|
usage, _ := dao.GetQuotaUsage(id)
|
||||||
@ -239,7 +238,7 @@ func (suite *ManagerSuite) TestSubtractResources() {
|
|||||||
|
|
||||||
if suite.Nil(mgr.SubtractResources(resource)) {
|
if suite.Nil(mgr.SubtractResources(resource)) {
|
||||||
usage, _ := dao.GetQuotaUsage(id)
|
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
|
args args
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"valid", args{"project", types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 1}}, false},
|
{"valid", args{"project", types.ResourceList{types.ResourceStorage: 1}}, false},
|
||||||
{"invalid", args{"project", types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 0}}, true},
|
{"invalid", args{"project", types.ResourceList{types.ResourceStorage: 0}}, true},
|
||||||
{"not support", args{"not support", types.ResourceList{types.ResourceCount: 1}}, true},
|
{"not support", args{"not support", types.ResourceList{types.ResourceStorage: 1}}, 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) {
|
||||||
|
@ -19,8 +19,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ResourceCount alias types.ResourceCount
|
|
||||||
ResourceCount = types.ResourceCount
|
|
||||||
// ResourceStorage alias types.ResourceStorage
|
// ResourceStorage alias types.ResourceStorage
|
||||||
ResourceStorage = types.ResourceStorage
|
ResourceStorage = types.ResourceStorage
|
||||||
)
|
)
|
||||||
|
@ -72,16 +72,6 @@ func Test_isSafe(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -48,7 +48,7 @@ func (suite *ControllerTestSuite) TestGetReservedResources() {
|
|||||||
suite.Len(resources, 0)
|
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)
|
resources, err := ctl.getReservedResources(context.TODO(), reference, referenceID)
|
||||||
@ -68,7 +68,7 @@ func (suite *ControllerTestSuite) TestGetReservedResources() {
|
|||||||
func (suite *ControllerTestSuite) TestReserveResources() {
|
func (suite *ControllerTestSuite) TestReserveResources() {
|
||||||
quotaMgr := "atesting.Manager{}
|
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)
|
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{})
|
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||||
reference, referenceID := "reference", uuid.New().String()
|
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))
|
suite.Nil(ctl.reserveResources(ctx, reference, referenceID, resources))
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ func (suite *ControllerTestSuite) TestReserveResources() {
|
|||||||
func (suite *ControllerTestSuite) TestUnreserveResources() {
|
func (suite *ControllerTestSuite) TestUnreserveResources() {
|
||||||
quotaMgr := "atesting.Manager{}
|
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)
|
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{})
|
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||||
reference, referenceID := "reference", uuid.New().String()
|
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))
|
suite.Nil(ctl.reserveResources(ctx, reference, referenceID, resources))
|
||||||
|
|
||||||
@ -108,10 +108,10 @@ func (suite *ControllerTestSuite) TestUnreserveResources() {
|
|||||||
func (suite *ControllerTestSuite) TestRequest() {
|
func (suite *ControllerTestSuite) TestRequest() {
|
||||||
quotaMgr := "atesting.Manager{}
|
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()}
|
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)
|
mock.OnAnything(quotaMgr, "GetByRefForUpdate").Return(q, nil)
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ func (suite *ControllerTestSuite) TestRequest() {
|
|||||||
d := &drivertesting.Driver{}
|
d := &drivertesting.Driver{}
|
||||||
|
|
||||||
mock.OnAnything(d, "CalculateUsage").Return(used, nil).Run(func(args mock.Arguments) {
|
mock.OnAnything(d, "CalculateUsage").Return(used, nil).Run(func(args mock.Arguments) {
|
||||||
used[types.ResourceCount]++
|
used[types.ResourceStorage]++
|
||||||
})
|
})
|
||||||
|
|
||||||
driver.Register("mock", d)
|
driver.Register("mock", d)
|
||||||
@ -131,7 +131,7 @@ func (suite *ControllerTestSuite) TestRequest() {
|
|||||||
|
|
||||||
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||||
reference, referenceID := "mock", "1"
|
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 }))
|
suite.Nil(ctl.Request(ctx, reference, referenceID, resources, func() error { return nil }))
|
||||||
@ -151,7 +151,7 @@ func BenchmarkGetReservedResources(b *testing.B) {
|
|||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
reference, referenceID := "reference", uuid.New().String()
|
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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
ctl.getReservedResources(ctx, reference, referenceID)
|
ctl.getReservedResources(ctx, reference, referenceID)
|
||||||
@ -164,6 +164,6 @@ func BenchmarkSetReservedResources(b *testing.B) {
|
|||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
s := strconv.Itoa(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/blob"
|
||||||
"github.com/goharbor/harbor/src/controller/chartmuseum"
|
"github.com/goharbor/harbor/src/controller/chartmuseum"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/lib/q"
|
|
||||||
dr "github.com/goharbor/harbor/src/pkg/quota/driver"
|
dr "github.com/goharbor/harbor/src/pkg/quota/driver"
|
||||||
"github.com/goharbor/harbor/src/pkg/types"
|
"github.com/goharbor/harbor/src/pkg/types"
|
||||||
"github.com/graph-gophers/dataloader"
|
"github.com/graph-gophers/dataloader"
|
||||||
@ -60,7 +59,6 @@ func (d *driver) HardLimits(ctx context.Context) types.ResourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return types.ResourceList{
|
return types.ResourceList{
|
||||||
types.ResourceCount: d.cfg.Get(common.CountPerProject).GetInt64(),
|
|
||||||
types.ResourceStorage: d.cfg.Get(common.StoragePerProject).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 {
|
func (d *driver) Validate(hardLimits types.ResourceList) error {
|
||||||
resources := map[types.ResourceName]bool{
|
resources := map[types.ResourceName]bool{
|
||||||
types.ResourceCount: true,
|
|
||||||
types.ResourceStorage: true,
|
types.ResourceStorage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,23 +113,12 @@ func (d *driver) CalculateUsage(ctx context.Context, key string) (types.Resource
|
|||||||
return nil, err
|
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)
|
size, err := d.blobCtl.CalculateTotalSizeByProject(ctx, projectID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
func newDriver() dr.Driver {
|
||||||
|
@ -51,14 +51,12 @@ func (suite *DriverTestSuite) SetupTest() {
|
|||||||
func (suite *DriverTestSuite) TestCalculateUsage() {
|
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.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")
|
resources, err := suite.d.CalculateUsage(context.TODO(), "1")
|
||||||
if suite.Nil(err) {
|
if suite.Nil(err) {
|
||||||
suite.Len(resources, 2)
|
suite.Len(resources, 1)
|
||||||
suite.Equal(resources[types.ResourceCount], int64(20))
|
suite.Equal(resources[types.ResourceStorage], int64(1000))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,13 +104,13 @@ func (suite *RefreshForProjectsTestSuite) TestRefreshForProjects() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
q := "a.Quota{}
|
q := "a.Quota{}
|
||||||
q.SetHard(types.ResourceList{types.ResourceCount: 10})
|
q.SetHard(types.ResourceList{types.ResourceStorage: 10})
|
||||||
q.SetUsed(types.ResourceList{types.ResourceCount: 0})
|
q.SetUsed(types.ResourceList{types.ResourceStorage: 0})
|
||||||
|
|
||||||
mock.OnAnything(suite.quotaMgr, "GetByRef").Return(q, nil)
|
mock.OnAnything(suite.quotaMgr, "GetByRef").Return(q, nil)
|
||||||
mock.OnAnything(suite.quotaMgr, "GetByRefForUpdate").Return(q, nil)
|
mock.OnAnything(suite.quotaMgr, "GetByRefForUpdate").Return(q, nil)
|
||||||
mock.OnAnything(suite.quotaMgr, "Update").Return(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{})
|
ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{})
|
||||||
RefreshForProjects(ctx)
|
RefreshForProjects(ctx)
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -27,9 +26,7 @@ import (
|
|||||||
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
|
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
rep_event "github.com/goharbor/harbor/src/replication/event"
|
rep_event "github.com/goharbor/harbor/src/replication/event"
|
||||||
"github.com/goharbor/harbor/src/replication/model"
|
"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/orm"
|
||||||
"github.com/goharbor/harbor/src/server/middleware/quota"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -603,10 +600,7 @@ func initializeChartController() (*chartserver.Controller, error) {
|
|||||||
return nil, errors.New("Endpoint URL of chart storage server is malformed")
|
return nil, errors.New("Endpoint URL of chart storage server is malformed")
|
||||||
}
|
}
|
||||||
|
|
||||||
chartVersionURL := fmt.Sprintf(`^/api/chartrepo/(?P<namespace>[^?#]+)/charts/(?P<name>[^?#]+)/(?P<version>[^?#]+)/?$`)
|
controller, err := chartserver.NewController(url, orm.Middleware())
|
||||||
skipper := middleware.NegativeSkipper(middleware.MethodAndPathSkipper(http.MethodDelete, regexp.MustCompile(chartVersionURL)))
|
|
||||||
|
|
||||||
controller, err := chartserver.NewController(url, orm.Middleware(), quota.UploadChartVersionMiddleware(), quota.RefreshForProjectMiddleware(skipper))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Failed to initialize chart API controller")
|
return nil, errors.New("Failed to initialize chart API controller")
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,6 @@ func (p *ProjectAPI) Post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !p.SecurityCtx.IsSysAdmin() {
|
if !p.SecurityCtx.IsSysAdmin() {
|
||||||
pro.CountLimit = &setting.CountPerProject
|
|
||||||
pro.StorageLimit = &setting.StoragePerProject
|
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) {
|
func projectQuotaHardLimits(req *models.ProjectRequest, setting *models.QuotaSetting) (types.ResourceList, error) {
|
||||||
hardLimits := types.ResourceList{}
|
hardLimits := types.ResourceList{}
|
||||||
if req.CountLimit != nil {
|
|
||||||
hardLimits[types.ResourceCount] = *req.CountLimit
|
|
||||||
} else {
|
|
||||||
hardLimits[types.ResourceCount] = setting.CountPerProject
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.StorageLimit != nil {
|
if req.StorageLimit != nil {
|
||||||
hardLimits[types.ResourceStorage] = *req.StorageLimit
|
hardLimits[types.ResourceStorage] = *req.StorageLimit
|
||||||
|
@ -476,7 +476,7 @@ func TestProjectSummary(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
|
||||||
assert.Equal(int64(1), summary.ProjectAdminCount)
|
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")
|
fmt.Printf("\n")
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
reference = "mock"
|
reference = "mock"
|
||||||
hardLimits = types.ResourceList{types.ResourceCount: -1, types.ResourceStorage: -1}
|
hardLimits = types.ResourceList{types.ResourceStorage: -1}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -96,7 +96,7 @@ func TestQuotaAPIGet(t *testing.T) {
|
|||||||
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.Equal(int(200), code)
|
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")
|
code, _, err = apiTest.QuotasGetByID(*admin, "100")
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
@ -116,18 +116,18 @@ func TestQuotaPut(t *testing.T) {
|
|||||||
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
code, quota, err := apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.Equal(int(200), code)
|
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{})
|
code, err = apiTest.QuotasPut(*admin, fmt.Sprintf("%d", quotaID), models.QuotaUpdateRequest{})
|
||||||
assert.Nil(err, err)
|
assert.Nil(err, err)
|
||||||
assert.Equal(int(400), code)
|
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.Nil(err)
|
||||||
assert.Equal(int(200), code)
|
assert.Equal(int(200), code)
|
||||||
|
|
||||||
code, quota, err = apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
code, quota, err = apiTest.QuotasGetByID(*admin, fmt.Sprintf("%d", quotaID))
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.Equal(int(200), code)
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &models.QuotaSetting{
|
return &models.QuotaSetting{
|
||||||
CountPerProject: cfgMgr.Get(common.CountPerProject).GetInt64(),
|
|
||||||
StoragePerProject: cfgMgr.Get(common.StoragePerProject).GetInt64(),
|
StoragePerProject: cfgMgr.Get(common.StoragePerProject).GetInt64(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,8 @@ func (suite *DaoTestSuite) SetupSuite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DaoTestSuite) TestCreate() {
|
func (suite *DaoTestSuite) TestCreate() {
|
||||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||||
usage := types.ResourceList{types.ResourceCount: 0}
|
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||||
id, err := suite.dao.Create(suite.Context(), "project", "2", hardLimits, usage)
|
id, err := suite.dao.Create(suite.Context(), "project", "2", hardLimits, usage)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
|
|
||||||
@ -56,8 +56,8 @@ func (suite *DaoTestSuite) TestCreate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DaoTestSuite) TestDelete() {
|
func (suite *DaoTestSuite) TestDelete() {
|
||||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||||
usage := types.ResourceList{types.ResourceCount: 0}
|
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||||
|
|
||||||
id, err := suite.dao.Create(suite.Context(), "project", "3", hardLimits, usage)
|
id, err := suite.dao.Create(suite.Context(), "project", "3", hardLimits, usage)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
@ -77,8 +77,8 @@ func (suite *DaoTestSuite) TestDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DaoTestSuite) TestGetByRef() {
|
func (suite *DaoTestSuite) TestGetByRef() {
|
||||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||||
usage := types.ResourceList{types.ResourceCount: 0}
|
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||||
|
|
||||||
reference, referenceID := "project", "4"
|
reference, referenceID := "project", "4"
|
||||||
id, err := suite.dao.Create(suite.Context(), reference, referenceID, hardLimits, usage)
|
id, err := suite.dao.Create(suite.Context(), reference, referenceID, hardLimits, usage)
|
||||||
@ -99,8 +99,8 @@ func (suite *DaoTestSuite) TestGetByRef() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
||||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||||
usage := types.ResourceList{types.ResourceCount: 0}
|
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||||
|
|
||||||
reference, referenceID := "project", "5"
|
reference, referenceID := "project", "5"
|
||||||
id, err := suite.dao.Create(suite.Context(), reference, referenceID, hardLimits, usage)
|
id, err := suite.dao.Create(suite.Context(), reference, referenceID, hardLimits, usage)
|
||||||
@ -119,7 +119,7 @@ func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
|||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
|
|
||||||
used, _ := q.GetUsed()
|
used, _ := q.GetUsed()
|
||||||
used[types.ResourceCount]++
|
used[types.ResourceStorage]++
|
||||||
q.SetUsed(used)
|
q.SetUsed(used)
|
||||||
|
|
||||||
suite.dao.Update(ctx, q)
|
suite.dao.Update(ctx, q)
|
||||||
@ -136,19 +136,19 @@ func (suite *DaoTestSuite) TestGetByRefForUpdate() {
|
|||||||
q, err := suite.dao.Get(suite.Context(), id)
|
q, err := suite.dao.Get(suite.Context(), id)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
used, _ := q.GetUsed()
|
used, _ := q.GetUsed()
|
||||||
suite.Equal(count, used[types.ResourceCount])
|
suite.Equal(count, used[types.ResourceStorage])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DaoTestSuite) TestUpdate() {
|
func (suite *DaoTestSuite) TestUpdate() {
|
||||||
hardLimits := types.ResourceList{types.ResourceCount: 1}
|
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||||
usage := types.ResourceList{types.ResourceCount: 0}
|
usage := types.ResourceList{types.ResourceStorage: 0}
|
||||||
|
|
||||||
id, err := suite.dao.Create(suite.Context(), "project", "6", hardLimits, usage)
|
id, err := suite.dao.Create(suite.Context(), "project", "6", hardLimits, usage)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
|
|
||||||
newHardLimits := types.ResourceList{types.ResourceCount: 2}
|
newHardLimits := types.ResourceList{types.ResourceStorage: 200}
|
||||||
newUsage := types.ResourceList{types.ResourceCount: 1}
|
newUsage := types.ResourceList{types.ResourceStorage: 1}
|
||||||
|
|
||||||
{
|
{
|
||||||
q, err := suite.dao.Get(suite.Context(), id)
|
q, err := suite.dao.Get(suite.Context(), id)
|
||||||
|
@ -38,7 +38,7 @@ func (suite *ManagerTestSuite) TestCreate() {
|
|||||||
ctx := suite.Context()
|
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)
|
id, err := Mgr.Create(ctx, "project", "1000", hardLimits)
|
||||||
|
|
||||||
if suite.Nil(err) {
|
if suite.Nil(err) {
|
||||||
@ -56,8 +56,8 @@ func (suite *ManagerTestSuite) TestCreate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
hardLimits := types.ResourceList{types.ResourceCount: 1, types.ResourceStorage: 100}
|
hardLimits := types.ResourceList{types.ResourceStorage: 100}
|
||||||
usage := types.ResourceList{types.ResourceCount: 0, types.ResourceStorage: 10}
|
usage := types.ResourceList{types.ResourceStorage: 10}
|
||||||
id, err := Mgr.Create(ctx, "project", "1000", hardLimits, usage)
|
id, err := Mgr.Create(ctx, "project", "1000", hardLimits, usage)
|
||||||
|
|
||||||
if suite.Nil(err) {
|
if suite.Nil(err) {
|
||||||
@ -79,7 +79,7 @@ func (suite *ManagerTestSuite) TestUpdate() {
|
|||||||
ctx := suite.Context()
|
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)
|
id, err := Mgr.Create(ctx, "project", "1000", hardLimits)
|
||||||
|
|
||||||
q, err := Mgr.Get(ctx, id)
|
q, err := Mgr.Get(ctx, id)
|
||||||
|
@ -26,8 +26,8 @@ func TestGetWarningResources(t *testing.T) {
|
|||||||
|
|
||||||
q := Quota{}
|
q := Quota{}
|
||||||
|
|
||||||
q.SetHard(types.ResourceList{types.ResourceCount: 3})
|
q.SetHard(types.ResourceList{types.ResourceStorage: 300})
|
||||||
q.SetUsed(types.ResourceList{types.ResourceCount: 3})
|
q.SetUsed(types.ResourceList{types.ResourceStorage: 300})
|
||||||
|
|
||||||
resources, err := q.GetWarningResources(85)
|
resources, err := q.GetWarningResources(85)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
@ -72,16 +72,6 @@ func TestIsSafe(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -23,8 +23,6 @@ const (
|
|||||||
// UNLIMITED unlimited resource value
|
// UNLIMITED unlimited resource value
|
||||||
UNLIMITED = -1
|
UNLIMITED = -1
|
||||||
|
|
||||||
// ResourceCount count, in number
|
|
||||||
ResourceCount ResourceName = "count"
|
|
||||||
// ResourceStorage storage size, in bytes
|
// ResourceStorage storage size, in bytes
|
||||||
ResourceStorage ResourceName = "storage"
|
ResourceStorage ResourceName = "storage"
|
||||||
)
|
)
|
||||||
@ -139,7 +137,7 @@ func IsNegative(a ResourceList) []ResourceName {
|
|||||||
// IsValidResource returns true when resource was supported
|
// IsValidResource returns true when resource was supported
|
||||||
func IsValidResource(resource ResourceName) bool {
|
func IsValidResource(resource ResourceName) bool {
|
||||||
switch resource {
|
switch resource {
|
||||||
case ResourceCount, ResourceStorage:
|
case ResourceStorage:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
@ -39,50 +39,40 @@ func (suite *ResourcesSuite) TestEquals() {
|
|||||||
suite.True(Equals(ResourceList{}, ResourceList{}))
|
suite.True(Equals(ResourceList{}, ResourceList{}))
|
||||||
suite.True(Equals(ResourceList{ResourceStorage: 100}, ResourceList{ResourceStorage: 100}))
|
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: 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() {
|
func (suite *ResourcesSuite) TestAdd() {
|
||||||
res1 := ResourceList{ResourceStorage: 100}
|
res1 := ResourceList{ResourceStorage: 100}
|
||||||
res2 := ResourceList{ResourceStorage: 100}
|
res2 := ResourceList{ResourceStorage: 100}
|
||||||
res3 := ResourceList{ResourceStorage: 100, ResourceCount: 10}
|
res3 := ResourceList{ResourceStorage: 100}
|
||||||
res4 := ResourceList{ResourceCount: 10}
|
|
||||||
|
|
||||||
suite.Equal(res1, Add(ResourceList{}, res1))
|
suite.Equal(res1, Add(ResourceList{}, res1))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 200}, Add(res1, res2))
|
suite.Equal(ResourceList{ResourceStorage: 200}, Add(res1, res2))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 200, ResourceCount: 10}, Add(res1, res3))
|
suite.Equal(ResourceList{ResourceStorage: 200}, Add(res1, res3))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 100, ResourceCount: 10}, Add(res1, res4))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ResourcesSuite) TestSubtract() {
|
func (suite *ResourcesSuite) TestSubtract() {
|
||||||
res1 := ResourceList{ResourceStorage: 100}
|
res1 := ResourceList{ResourceStorage: 100}
|
||||||
res2 := ResourceList{ResourceStorage: 100}
|
res2 := ResourceList{ResourceStorage: 100}
|
||||||
res3 := ResourceList{ResourceStorage: 100, ResourceCount: 10}
|
res3 := ResourceList{ResourceStorage: 100}
|
||||||
res4 := ResourceList{ResourceCount: 10}
|
|
||||||
|
|
||||||
suite.Equal(res1, Subtract(res1, ResourceList{}))
|
suite.Equal(res1, Subtract(res1, ResourceList{}))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 0}, Subtract(res1, res2))
|
suite.Equal(ResourceList{ResourceStorage: 0}, Subtract(res1, res2))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 0, ResourceCount: -10}, Subtract(res1, res3))
|
suite.Equal(ResourceList{ResourceStorage: 0}, Subtract(res1, res3))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 100, ResourceCount: -10}, Subtract(res1, res4))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ResourcesSuite) TestZero() {
|
func (suite *ResourcesSuite) TestZero() {
|
||||||
res1 := ResourceList{ResourceStorage: 100}
|
res1 := ResourceList{ResourceStorage: 100}
|
||||||
res2 := ResourceList{ResourceCount: 10, ResourceStorage: 100}
|
res2 := ResourceList{ResourceStorage: 100}
|
||||||
|
|
||||||
suite.Equal(ResourceList{}, Zero(ResourceList{}))
|
suite.Equal(ResourceList{}, Zero(ResourceList{}))
|
||||||
suite.Equal(ResourceList{ResourceStorage: 0}, Zero(res1))
|
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() {
|
func (suite *ResourcesSuite) TestIsNegative() {
|
||||||
suite.Len(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: 100}), 1)
|
suite.Len(IsNegative(ResourceList{ResourceStorage: -100}), 1)
|
||||||
suite.Contains(IsNegative(ResourceList{ResourceStorage: -100, ResourceCount: 100}), ResourceStorage)
|
suite.Contains(IsNegative(ResourceList{ResourceStorage: -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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunResourcesSuite(t *testing.T) {
|
func TestRunResourcesSuite(t *testing.T) {
|
||||||
|
@ -31,21 +31,6 @@
|
|||||||
<label>{{ 'PROJECT.PUBLIC' | translate}}</label>
|
<label>{{ 'PROJECT.PUBLIC' | translate}}</label>
|
||||||
</clr-checkbox-wrapper>
|
</clr-checkbox-wrapper>
|
||||||
</clr-checkbox-container>
|
</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">
|
<div class="clr-form-control" *ngIf="isSystemAdmin">
|
||||||
<label for="create_project_storage_limit" class="required clr-control-label">{{'PROJECT.STORAGE_QUOTA' | translate}}
|
<label for="create_project_storage_limit" class="required clr-control-label">{{'PROJECT.STORAGE_QUOTA' | translate}}
|
||||||
<clr-tooltip>
|
<clr-tooltip>
|
||||||
|
@ -31,7 +31,7 @@ import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.com
|
|||||||
import { Project } from "../project";
|
import { Project } from "../project";
|
||||||
import { QuotaUnits, QuotaUnlimited } from "../../../lib/entities/shared.const";
|
import { QuotaUnits, QuotaUnlimited } from "../../../lib/entities/shared.const";
|
||||||
import { ProjectService, QuotaHardInterface } from "../../../lib/services";
|
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({
|
@Component({
|
||||||
@ -47,12 +47,10 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
|||||||
currentForm: NgForm;
|
currentForm: NgForm;
|
||||||
quotaUnits = QuotaUnits;
|
quotaUnits = QuotaUnits;
|
||||||
project: Project = new Project();
|
project: Project = new Project();
|
||||||
countLimit: number;
|
|
||||||
storageLimit: number;
|
storageLimit: number;
|
||||||
storageLimitUnit: string = QuotaUnits[3].UNIT;
|
storageLimitUnit: string = QuotaUnits[3].UNIT;
|
||||||
storageDefaultLimit: number;
|
storageDefaultLimit: number;
|
||||||
storageDefaultLimitUnit: string;
|
storageDefaultLimitUnit: string;
|
||||||
countDefaultLimit: number;
|
|
||||||
initVal: Project = new Project();
|
initVal: Project = new Project();
|
||||||
|
|
||||||
createProjectOpened: boolean;
|
createProjectOpened: boolean;
|
||||||
@ -124,12 +122,10 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
|||||||
}
|
}
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes && changes["quotaObj"] && changes["quotaObj"].currentValue) {
|
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.storageLimit = GetIntegerAndUnit(this.quotaObj.storage_per_project, clone(QuotaUnits), 0, clone(QuotaUnits)).partNumberHard;
|
||||||
this.storageLimitUnit = this.storageLimit === QuotaUnlimited ? QuotaUnits[3].UNIT
|
this.storageLimitUnit = this.storageLimit === QuotaUnlimited ? QuotaUnits[3].UNIT
|
||||||
: GetIntegerAndUnit(this.quotaObj.storage_per_project, clone(QuotaUnits), 0, clone(QuotaUnits)).partCharacterHard;
|
: GetIntegerAndUnit(this.quotaObj.storage_per_project, clone(QuotaUnits), 0, clone(QuotaUnits)).partCharacterHard;
|
||||||
|
|
||||||
this.countDefaultLimit = this.countLimit;
|
|
||||||
this.storageDefaultLimit = this.storageLimit;
|
this.storageDefaultLimit = this.storageLimit;
|
||||||
this.storageDefaultLimitUnit = this.storageLimitUnit;
|
this.storageDefaultLimitUnit = this.storageLimitUnit;
|
||||||
if (this.isSystemAdmin) {
|
if (this.isSystemAdmin) {
|
||||||
@ -142,8 +138,7 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
|||||||
this.currentForm.form.controls['create_project_count_limit'].setValidators(
|
this.currentForm.form.controls['create_project_count_limit'].setValidators(
|
||||||
[
|
[
|
||||||
Validators.required,
|
Validators.required,
|
||||||
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)'),
|
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)')
|
||||||
validateCountLimit()
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
this.currentForm.form.valueChanges
|
this.currentForm.form.valueChanges
|
||||||
@ -171,7 +166,7 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
|||||||
this.isSubmitOnGoing = true;
|
this.isSubmitOnGoing = true;
|
||||||
const storageByte = +this.storageLimit === QuotaUnlimited ? this.storageLimit : getByte(+this.storageLimit, this.storageLimitUnit);
|
const storageByte = +this.storageLimit === QuotaUnlimited ? this.storageLimit : getByte(+this.storageLimit, this.storageLimitUnit);
|
||||||
this.projectService
|
this.projectService
|
||||||
.createProject(this.project.name, this.project.metadata, +this.countLimit, +storageByte)
|
.createProject(this.project.name, this.project.metadata, +storageByte)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
status => {
|
status => {
|
||||||
this.isSubmitOnGoing = false;
|
this.isSubmitOnGoing = false;
|
||||||
@ -198,7 +193,6 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
|||||||
this.currentForm.controls["create_project_name"].reset();
|
this.currentForm.controls["create_project_name"].reset();
|
||||||
}
|
}
|
||||||
this.inlineAlert.close();
|
this.inlineAlert.close();
|
||||||
this.countLimit = this.countDefaultLimit ;
|
|
||||||
this.storageLimit = this.storageDefaultLimit;
|
this.storageLimit = this.storageDefaultLimit;
|
||||||
this.storageLimitUnit = this.storageDefaultLimitUnit;
|
this.storageLimitUnit = this.storageDefaultLimitUnit;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,6 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
|||||||
this.configService.getConfiguration()
|
this.configService.getConfiguration()
|
||||||
.subscribe((configurations: Configuration) => {
|
.subscribe((configurations: Configuration) => {
|
||||||
this.quotaObj = {
|
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
|
storage_per_project: configurations.storage_per_project ? configurations.storage_per_project.value : -1
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -27,23 +27,6 @@
|
|||||||
<div class="display-flex project-detail">
|
<div class="display-flex project-detail">
|
||||||
<h5 class="mt-0">{{'SUMMARY.PROJECT_QUOTAS' | translate}}</h5>
|
<h5 class="mt-0">{{'SUMMARY.PROJECT_QUOTAS' | translate}}</h5>
|
||||||
<div class="ml-1">
|
<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">
|
<div class="display-flex quotas-progress">
|
||||||
<label class="mr-1">{{'SUMMARY.STORAGE_CONSUMPTION' | translate}}</label>
|
<label class="mr-1">{{'SUMMARY.STORAGE_CONSUMPTION' | translate}}</label>
|
||||||
<label class="progress-label">
|
<label class="progress-label">
|
||||||
|
@ -7,31 +7,6 @@
|
|||||||
<form #quotaForm="ngForm" class=" clr-form clr-form-horizontal"
|
<form #quotaForm="ngForm" class=" clr-form clr-form-horizontal"
|
||||||
[class.clr-form-compact-common]="!defaultTextsObj.isSystemDefaultQuota">
|
[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>
|
<clr-input-container>
|
||||||
<label for="count" class="left-label required">{{ defaultTextsObj.storageQuota | translate}}
|
<label for="count" class="left-label required">{{ defaultTextsObj.storageQuota | translate}}
|
||||||
<clr-tooltip>
|
<clr-tooltip>
|
||||||
|
@ -15,9 +15,8 @@ describe('EditProjectQuotasComponent', () => {
|
|||||||
const mockedEditQuota: EditQuotaQuotaInterface = {
|
const mockedEditQuota: EditQuotaQuotaInterface = {
|
||||||
editQuota: "Edit Default Project Quotas",
|
editQuota: "Edit Default Project Quotas",
|
||||||
setQuota: "Set the default project quotas when creating new projects",
|
setQuota: "Set the default project quotas when creating new projects",
|
||||||
countQuota: "Default artifact count",
|
|
||||||
storageQuota: "Default storage consumption",
|
storageQuota: "Default storage consumption",
|
||||||
quotaHardLimitValue: {storageLimit: -1, storageUnit: "Byte", countLimit: -1},
|
quotaHardLimitValue: {storageLimit: -1, storageUnit: "Byte"},
|
||||||
isSystemDefaultQuota: true
|
isSystemDefaultQuota: true
|
||||||
};
|
};
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@ -41,18 +40,19 @@ describe('EditProjectQuotasComponent', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('should open', async () => {
|
// ToDo update it with storage edit?
|
||||||
component.openEditQuota = true;
|
// it('should open', async () => {
|
||||||
fixture.detectChanges();
|
// component.openEditQuota = true;
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
component.openEditQuotaModal(mockedEditQuota);
|
// await fixture.whenStable();
|
||||||
fixture.detectChanges();
|
// component.openEditQuotaModal(mockedEditQuota);
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
let countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
// await fixture.whenStable();
|
||||||
countInput.value = "100";
|
// let countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||||
countInput.dispatchEvent(new Event("input"));
|
// countInput.value = "100";
|
||||||
fixture.detectChanges();
|
// countInput.dispatchEvent(new Event("input"));
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
expect(component.isValid).toBeTruthy();
|
// 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 { 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 { EditQuotaQuotaInterface, QuotaHardLimitInterface } from '../../../../services';
|
||||||
import { distinctUntilChanged } from 'rxjs/operators';
|
import { distinctUntilChanged } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -22,17 +22,15 @@ import { distinctUntilChanged } from 'rxjs/operators';
|
|||||||
})
|
})
|
||||||
export class EditProjectQuotasComponent implements OnInit {
|
export class EditProjectQuotasComponent implements OnInit {
|
||||||
openEditQuota: boolean;
|
openEditQuota: boolean;
|
||||||
defaultTextsObj: { editQuota: string; setQuota: string; countQuota: string; storageQuota: string; isSystemDefaultQuota: boolean } = {
|
defaultTextsObj: { editQuota: string; setQuota: string; storageQuota: string; isSystemDefaultQuota: boolean } = {
|
||||||
editQuota: '',
|
editQuota: '',
|
||||||
setQuota: '',
|
setQuota: '',
|
||||||
countQuota: '',
|
|
||||||
storageQuota: '',
|
storageQuota: '',
|
||||||
isSystemDefaultQuota: false,
|
isSystemDefaultQuota: false,
|
||||||
};
|
};
|
||||||
quotaHardLimitValue: QuotaHardLimitInterface = {
|
quotaHardLimitValue: QuotaHardLimitInterface = {
|
||||||
storageLimit: -1
|
storageLimit: -1
|
||||||
, storageUnit: ''
|
, storageUnit: ''
|
||||||
, countLimit: -1
|
|
||||||
};
|
};
|
||||||
quotaUnits = QuotaUnits;
|
quotaUnits = QuotaUnits;
|
||||||
staticBackdrop = true;
|
staticBackdrop = true;
|
||||||
@ -73,7 +71,6 @@ export class EditProjectQuotasComponent implements OnInit {
|
|||||||
, storageUnit: defaultTextsObj.quotaHardLimitValue.storageLimit === QuotaUnlimited ?
|
, storageUnit: defaultTextsObj.quotaHardLimitValue.storageLimit === QuotaUnlimited ?
|
||||||
QuotaUnits[3].UNIT : GetIntegerAndUnit(defaultTextsObj.quotaHardLimitValue.storageLimit
|
QuotaUnits[3].UNIT : GetIntegerAndUnit(defaultTextsObj.quotaHardLimitValue.storageLimit
|
||||||
, clone(QuotaUnits), 0, clone(QuotaUnits)).partCharacterHard
|
, clone(QuotaUnits), 0, clone(QuotaUnits)).partCharacterHard
|
||||||
, countLimit: defaultTextsObj.quotaHardLimitValue.countLimit
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.quotaHardLimitValue = {
|
this.quotaHardLimitValue = {
|
||||||
@ -83,15 +80,12 @@ export class EditProjectQuotasComponent implements OnInit {
|
|||||||
, storageUnit: defaultTextsObj.quotaHardLimitValue.hard.storage === QuotaUnlimited ?
|
, storageUnit: defaultTextsObj.quotaHardLimitValue.hard.storage === QuotaUnlimited ?
|
||||||
QuotaUnits[3].UNIT : GetIntegerAndUnit(defaultTextsObj.quotaHardLimitValue.hard.storage
|
QuotaUnits[3].UNIT : GetIntegerAndUnit(defaultTextsObj.quotaHardLimitValue.hard.storage
|
||||||
, clone(QuotaUnits), defaultTextsObj.quotaHardLimitValue.used.storage, clone(QuotaUnits)).partCharacterHard
|
, clone(QuotaUnits), defaultTextsObj.quotaHardLimitValue.used.storage, clone(QuotaUnits)).partCharacterHard
|
||||||
, countLimit: defaultTextsObj.quotaHardLimitValue.hard.count
|
|
||||||
, id: defaultTextsObj.quotaHardLimitValue.id
|
, id: defaultTextsObj.quotaHardLimitValue.id
|
||||||
, countUsed: defaultTextsObj.quotaHardLimitValue.used.count
|
|
||||||
, storageUsed: defaultTextsObj.quotaHardLimitValue.used.storage
|
, storageUsed: defaultTextsObj.quotaHardLimitValue.used.storage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let defaultForm = {
|
let defaultForm = {
|
||||||
count: this.quotaHardLimitValue.countLimit
|
storage: this.quotaHardLimitValue.storageLimit
|
||||||
, storage: this.quotaHardLimitValue.storageLimit
|
|
||||||
, storageUnit: this.quotaHardLimitValue.storageUnit
|
, storageUnit: this.quotaHardLimitValue.storageUnit
|
||||||
};
|
};
|
||||||
this.currentForm.resetForm(defaultForm);
|
this.currentForm.resetForm(defaultForm);
|
||||||
@ -103,16 +97,10 @@ export class EditProjectQuotasComponent implements OnInit {
|
|||||||
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)'),
|
Validators.pattern('(^-1$)|(^([1-9]+)([0-9]+)*$)'),
|
||||||
validateLimit(this.currentForm.form.controls['storageUnit'])
|
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
|
this.currentForm.form.valueChanges
|
||||||
.pipe(distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)))
|
.pipe(distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)))
|
||||||
.subscribe((data) => {
|
.subscribe((data) => {
|
||||||
['storage', 'storageUnit', 'count'].forEach(fieldName => {
|
['storage', 'storageUnit'].forEach(fieldName => {
|
||||||
if (this.currentForm.form.get(fieldName) && this.currentForm.form.get(fieldName).value !== null) {
|
if (this.currentForm.form.get(fieldName) && this.currentForm.form.get(fieldName).value !== null) {
|
||||||
this.currentForm.form.get(fieldName).updateValueAndValidity();
|
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="col-lg-12 col-md-12 col-sm-12 col-xs-12 quota-top">
|
||||||
<div class="default-quota">
|
<div class="default-quota">
|
||||||
<div>
|
<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">
|
<div class="default-quota-text">
|
||||||
<span class="width-10rem">{{'QUOTA.PROJECT_QUOTA_DEFAULT_DISK' | translate}}</span>
|
<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}}
|
<span class="num-count">{{ quotaHardLimitValue?.storageLimit === -1?('QUOTA.UNLIMITED' | translate): getIntegerAndUnit(quotaHardLimitValue?.storageLimit, 0).partNumberHard}}
|
||||||
{{ quotaHardLimitValue?.storageLimit === -1?'':quotaHardLimitValue?.storageUnit }}</span>
|
{{ 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -37,26 +33,12 @@
|
|||||||
</clr-dg-action-bar>
|
</clr-dg-action-bar>
|
||||||
<clr-dg-column>{{'QUOTA.PROJECT' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'QUOTA.PROJECT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'QUOTA.OWNER' | 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-column [clrDgSortBy]="storageComparator">{{'QUOTA.STORAGE' | translate }}</clr-dg-column>
|
||||||
<clr-dg-placeholder>{{'QUOTA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
<clr-dg-placeholder>{{'QUOTA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||||
<clr-dg-row *ngFor="let quota of quotaList" [clrDgItem]='quota'>
|
<clr-dg-row *ngFor="let quota of quotaList" [clrDgItem]='quota'>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<a href="javascript:void(0)" (click)="goToLink(quota?.ref?.id)">{{quota?.ref?.name}}</a></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>{{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>
|
<clr-dg-cell>
|
||||||
<div class="progress-block progress-min-width">
|
<div class="progress-block progress-min-width">
|
||||||
<div class="progress success"
|
<div class="progress success"
|
||||||
|
@ -35,11 +35,9 @@ describe('ProjectQuotasComponent', () => {
|
|||||||
creation_time: "12212112121",
|
creation_time: "12212112121",
|
||||||
update_time: "12212112121",
|
update_time: "12212112121",
|
||||||
hard: {
|
hard: {
|
||||||
count: -1,
|
|
||||||
storage: -1,
|
storage: -1,
|
||||||
},
|
},
|
||||||
used: {
|
used: {
|
||||||
count: 1234,
|
|
||||||
storage: 1234
|
storage: 1234
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -83,7 +81,6 @@ describe('ProjectQuotasComponent', () => {
|
|||||||
fixture = TestBed.createComponent(ProjectQuotasComponent);
|
fixture = TestBed.createComponent(ProjectQuotasComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.quotaHardLimitValue = {
|
component.quotaHardLimitValue = {
|
||||||
countLimit: 1111,
|
|
||||||
storageLimit: 23,
|
storageLimit: 23,
|
||||||
storageUnit: 'GB'
|
storageUnit: 'GB'
|
||||||
};
|
};
|
||||||
@ -119,26 +116,27 @@ describe('ProjectQuotasComponent', () => {
|
|||||||
const modal: HTMLElement = fixture.nativeElement.querySelector("clr-modal");
|
const modal: HTMLElement = fixture.nativeElement.querySelector("clr-modal");
|
||||||
expect(modal).toBeTruthy();
|
expect(modal).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('edit quota', async () => {
|
// ToDo update it with storage edit?
|
||||||
// wait getting list and rendering
|
// it('edit quota', async () => {
|
||||||
await timeout(10);
|
// // wait getting list and rendering
|
||||||
fixture.detectChanges();
|
// await timeout(10);
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
component.selectedRow = [component.quotaList[0]];
|
// await fixture.whenStable();
|
||||||
component.editQuota();
|
// component.selectedRow = [component.quotaList[0]];
|
||||||
fixture.detectChanges();
|
// component.editQuota();
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
const countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
// await fixture.whenStable();
|
||||||
countInput.value = "100";
|
// const countInput: HTMLInputElement = fixture.nativeElement.querySelector('#count');
|
||||||
countInput.dispatchEvent(new Event("input"));
|
// countInput.value = "100";
|
||||||
fixture.detectChanges();
|
// countInput.dispatchEvent(new Event("input"));
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
const saveButton: HTMLInputElement = fixture.nativeElement.querySelector('#edit-quota-save');
|
// await fixture.whenStable();
|
||||||
saveButton.dispatchEvent(new Event("click"));
|
// const saveButton: HTMLInputElement = fixture.nativeElement.querySelector('#edit-quota-save');
|
||||||
fixture.detectChanges();
|
// saveButton.dispatchEvent(new Event("click"));
|
||||||
await fixture.whenStable();
|
// fixture.detectChanges();
|
||||||
expect(spyUpdate.calls.count()).toEqual(1);
|
// await fixture.whenStable();
|
||||||
});
|
// expect(spyUpdate.calls.count()).toEqual(1);
|
||||||
|
// });
|
||||||
it('should call navigate function', async () => {
|
it('should call navigate function', async () => {
|
||||||
// wait getting list and rendering
|
// wait getting list and rendering
|
||||||
await timeout(10);
|
await timeout(10);
|
||||||
|
@ -19,7 +19,6 @@ import { QuotaService } from "../../../services/quota.service";
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { finalize } from 'rxjs/operators';
|
import { finalize } from 'rxjs/operators';
|
||||||
const quotaSort = {
|
const quotaSort = {
|
||||||
count: 'used.count',
|
|
||||||
storage: "used.storage",
|
storage: "used.storage",
|
||||||
sortType: 'string'
|
sortType: 'string'
|
||||||
};
|
};
|
||||||
@ -56,7 +55,6 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
this.config = cfg;
|
this.config = cfg;
|
||||||
this.configChange.emit(this.config);
|
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);
|
storageComparator: Comparator<Quota> = new CustomComparator<Quota>(quotaSort.storage, quotaSort.sortType);
|
||||||
selectedRow: Quota[] = [];
|
selectedRow: Quota[] = [];
|
||||||
|
|
||||||
@ -71,13 +69,12 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||||
const defaultTexts = [this.translate.get('QUOTA.EDIT_PROJECT_QUOTAS')
|
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.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 => {
|
forkJoin(...defaultTexts).subscribe(res => {
|
||||||
const defaultTextsObj = {
|
const defaultTextsObj = {
|
||||||
editQuota: res[0],
|
editQuota: res[0],
|
||||||
setQuota: res[1],
|
setQuota: res[1],
|
||||||
countQuota: res[2],
|
storageQuota: res[2],
|
||||||
storageQuota: res[3],
|
|
||||||
quotaHardLimitValue: this.selectedRow[0],
|
quotaHardLimitValue: this.selectedRow[0],
|
||||||
isSystemDefaultQuota: false
|
isSystemDefaultQuota: false
|
||||||
};
|
};
|
||||||
@ -88,13 +85,12 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
|
|
||||||
editDefaultQuota(quotaHardLimitValue: QuotaHardLimitInterface) {
|
editDefaultQuota(quotaHardLimitValue: QuotaHardLimitInterface) {
|
||||||
const defaultTexts = [this.translate.get('QUOTA.EDIT_DEFAULT_PROJECT_QUOTAS'), this.translate.get('QUOTA.SET_DEFAULT_QUOTAS')
|
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 => {
|
forkJoin(...defaultTexts).subscribe(res => {
|
||||||
const defaultTextsObj = {
|
const defaultTextsObj = {
|
||||||
editQuota: res[0],
|
editQuota: res[0],
|
||||||
setQuota: res[1],
|
setQuota: res[1],
|
||||||
countQuota: res[2],
|
storageQuota: res[2],
|
||||||
storageQuota: res[3],
|
|
||||||
quotaHardLimitValue: quotaHardLimitValue,
|
quotaHardLimitValue: quotaHardLimitValue,
|
||||||
isSystemDefaultQuota: true
|
isSystemDefaultQuota: true
|
||||||
};
|
};
|
||||||
@ -113,9 +109,7 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
getQuotaChanges(allChanges) {
|
getQuotaChanges(allChanges) {
|
||||||
let changes = {};
|
let changes = {};
|
||||||
for (let prop in allChanges) {
|
for (let prop in allChanges) {
|
||||||
if (prop === 'storage_per_project'
|
if (prop === 'storage_per_project') {
|
||||||
|| prop === 'count_per_project'
|
|
||||||
) {
|
|
||||||
changes[prop] = allChanges[prop];
|
changes[prop] = allChanges[prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +117,6 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public saveConfig(configQuota): void {
|
public saveConfig(configQuota): void {
|
||||||
this.allConfig.count_per_project.value = configQuota.count;
|
|
||||||
this.allConfig.storage_per_project.value = +configQuota.storage === QuotaUnlimited ?
|
this.allConfig.storage_per_project.value = +configQuota.storage === QuotaUnlimited ?
|
||||||
configQuota.storage : getByte(configQuota.storage, configQuota.storageUnit);
|
configQuota.storage : getByte(configQuota.storage, configQuota.storageUnit);
|
||||||
let changes = this.getChanges();
|
let changes = this.getChanges();
|
||||||
@ -157,10 +150,9 @@ export class ProjectQuotasComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveCurrentQuota(event) {
|
saveCurrentQuota(event) {
|
||||||
let count = +event.formValue.count;
|
|
||||||
let storage = +event.formValue.storage === QuotaUnlimited ?
|
let storage = +event.formValue.storage === QuotaUnlimited ?
|
||||||
+event.formValue.storage : getByte(+event.formValue.storage, event.formValue.storageUnit);
|
+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.loading = true;
|
||||||
this.quotaService.updateQuota(event.id, rep).subscribe(res => {
|
this.quotaService.updateQuota(event.id, rep).subscribe(res => {
|
||||||
this.editQuotaDialog.openEditQuota = false;
|
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 storageNumberAndUnit = this.allConfig.storage_per_project ? this.allConfig.storage_per_project.value : QuotaUnlimited;
|
||||||
const storageLimit = storageNumberAndUnit;
|
const storageLimit = storageNumberAndUnit;
|
||||||
const storageUnit = this.getIntegerAndUnit(storageNumberAndUnit, 0).partCharacterHard;
|
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 };
|
||||||
this.quotaHardLimitValue = { storageLimit, storageUnit, countLimit };
|
|
||||||
}
|
}
|
||||||
getQuotaList(state: State) {
|
getQuotaList(state: State) {
|
||||||
if (!state || !state.page) {
|
if (!state || !state.page) {
|
||||||
|
@ -319,19 +319,16 @@ export interface Quota {
|
|||||||
creation_time: string;
|
creation_time: string;
|
||||||
update_time: string;
|
update_time: string;
|
||||||
hard: {
|
hard: {
|
||||||
count: number;
|
|
||||||
storage: number;
|
storage: number;
|
||||||
};
|
};
|
||||||
used: {
|
used: {
|
||||||
count: number;
|
|
||||||
storage: number;
|
storage: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface QuotaHard {
|
export interface QuotaHard {
|
||||||
hard: QuotaCountStorage;
|
hard: QuotaStorage;
|
||||||
}
|
}
|
||||||
export interface QuotaCountStorage {
|
export interface QuotaStorage {
|
||||||
count: number;
|
|
||||||
storage: number;
|
storage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,7 +446,6 @@ export interface SystemCVEWhitelist {
|
|||||||
items: Array<{ "cve_id": string; }>;
|
items: Array<{ "cve_id": string; }>;
|
||||||
}
|
}
|
||||||
export interface QuotaHardInterface {
|
export interface QuotaHardInterface {
|
||||||
count_per_project: number;
|
|
||||||
storage_per_project: number;
|
storage_per_project: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,17 +453,14 @@ export interface QuotaUnitInterface {
|
|||||||
UNIT: string;
|
UNIT: string;
|
||||||
}
|
}
|
||||||
export interface QuotaHardLimitInterface {
|
export interface QuotaHardLimitInterface {
|
||||||
countLimit: number;
|
|
||||||
storageLimit: number;
|
storageLimit: number;
|
||||||
storageUnit: string;
|
storageUnit: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
countUsed?: string;
|
|
||||||
storageUsed?: string;
|
storageUsed?: string;
|
||||||
}
|
}
|
||||||
export interface EditQuotaQuotaInterface {
|
export interface EditQuotaQuotaInterface {
|
||||||
editQuota: string;
|
editQuota: string;
|
||||||
setQuota: string;
|
setQuota: string;
|
||||||
countQuota: string;
|
|
||||||
storageQuota: string;
|
storageQuota: string;
|
||||||
quotaHardLimitValue: QuotaHardLimitInterface | any;
|
quotaHardLimitValue: QuotaHardLimitInterface | any;
|
||||||
isSystemDefaultQuota: boolean;
|
isSystemDefaultQuota: boolean;
|
||||||
|
@ -69,7 +69,7 @@ export abstract class ProjectService {
|
|||||||
page?: number,
|
page?: number,
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
): Observable<HttpResponse<Project[]>>;
|
): 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 deleteProject(projectId: number): Observable<any>;
|
||||||
abstract checkProjectExists(projectName: string): Observable<any>;
|
abstract checkProjectExists(projectName: string): Observable<any>;
|
||||||
abstract checkProjectMember(projectId: number): Observable<any>;
|
abstract checkProjectMember(projectId: number): Observable<any>;
|
||||||
@ -149,13 +149,13 @@ export class ProjectDefaultService extends ProjectService {
|
|||||||
catchError(error => observableThrowError(error)), );
|
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
|
return this.http
|
||||||
.post(`${ CURRENT_BASE_HREF }/projects`,
|
.post(`${ CURRENT_BASE_HREF }/projects`,
|
||||||
JSON.stringify({'project_name': name, 'metadata': {
|
JSON.stringify({'project_name': name, 'metadata': {
|
||||||
public: metadata.public ? 'true' : 'false',
|
public: metadata.public ? 'true' : 'false',
|
||||||
},
|
},
|
||||||
count_limit: countLimit, storage_limit: storageLimit
|
storage_limit: storageLimit
|
||||||
})
|
})
|
||||||
, HTTP_JSON_OPTIONS).pipe(
|
, HTTP_JSON_OPTIONS).pipe(
|
||||||
catchError(error => observableThrowError(error)), );
|
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 => {
|
export const validateLimit = unitContrl => {
|
||||||
return (control: AbstractControl) => {
|
return (control: AbstractControl) => {
|
||||||
if (
|
if (
|
||||||
|
@ -39,7 +39,6 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/controller/event/metadata"
|
"github.com/goharbor/harbor/src/controller/event/metadata"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"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/blob"
|
||||||
"github.com/goharbor/harbor/src/pkg/distribution"
|
"github.com/goharbor/harbor/src/pkg/distribution"
|
||||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||||
@ -110,20 +109,6 @@ func copyArtifactResources(r *http.Request, reference, referenceID string) (type
|
|||||||
return nil, err
|
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})
|
allBlobs, err := blobController.List(ctx, blob.ListParams{ArtifactDigests: artifactDigests})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("get blobs for artifacts %s failed, error: %v", strings.Join(artifactDigests, ", "), err)
|
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 {
|
func copyArtifactResourcesEvent(level int) func(*http.Request, string, string, string) event.Metadata {
|
||||||
|
@ -103,8 +103,8 @@ func (suite *CopyArtifactMiddlewareTestSuite) TestResourcesWarning() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
q := "a.Quota{}
|
q := "a.Quota{}
|
||||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||||
q.SetUsed(types.ResourceList{types.ResourceCount: 50})
|
q.SetUsed(types.ResourceList{types.ResourceStorage: 50})
|
||||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
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)
|
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 := "a.Quota{}
|
||||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||||
q.SetUsed(types.ResourceList{types.ResourceCount: 85})
|
q.SetUsed(types.ResourceList{types.ResourceStorage: 85})
|
||||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
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)
|
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 {
|
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.blobController, "FindMissingAssociationsForProject").Return(nil, nil).Once()
|
||||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||||
resources := args.Get(3).(types.ResourceList)
|
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.ResourceStorage], int64(100))
|
||||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
|
||||||
|
|
||||||
f := args.Get(4).(func() error)
|
f := args.Get(4).(func() error)
|
||||||
f()
|
f()
|
||||||
@ -114,9 +113,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
|||||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
||||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||||
resources := args.Get(3).(types.ResourceList)
|
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.ResourceStorage], int64(100+10))
|
||||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
|
||||||
|
|
||||||
f := args.Get(4).(func() error)
|
f := args.Get(4).(func() error)
|
||||||
f()
|
f()
|
||||||
@ -141,9 +139,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
|||||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
||||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||||
resources := args.Get(3).(types.ResourceList)
|
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.ResourceStorage], int64(100+20))
|
||||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
|
||||||
|
|
||||||
f := args.Get(4).(func() error)
|
f := args.Get(4).(func() error)
|
||||||
f()
|
f()
|
||||||
@ -168,9 +165,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestMiddleware() {
|
|||||||
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
mock.OnAnything(suite.blobController, "FindMissingAssociationsForProject").Return(missing, nil).Once()
|
||||||
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) {
|
||||||
resources := args.Get(3).(types.ResourceList)
|
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.ResourceStorage], int64(100))
|
||||||
suite.Equal(resources[types.ResourceCount], int64(1))
|
|
||||||
|
|
||||||
f := args.Get(4).(func() error)
|
f := args.Get(4).(func() error)
|
||||||
f()
|
f()
|
||||||
@ -197,7 +193,6 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesExceeded() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var errs quota.Errors
|
var errs quota.Errors
|
||||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceCount, 10, 10, 11))
|
|
||||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceStorage, 100, 100, 110))
|
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceStorage, 100, 100, 110))
|
||||||
mock.OnAnything(suite.quotaController, "Request").Return(errs).Once()
|
mock.OnAnything(suite.quotaController, "Request").Return(errs).Once()
|
||||||
|
|
||||||
@ -213,7 +208,6 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesExceeded() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var errs quota.Errors
|
var errs quota.Errors
|
||||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceCount, 10, 10, 11))
|
|
||||||
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceStorage, 100, 100, 110))
|
errs = errs.Add(quota.NewResourceOverflowError(types.ResourceStorage, 100, 100, 110))
|
||||||
|
|
||||||
err := errors.DeniedError(errs).WithMessage("Quota exceeded when processing the request of %v", errs)
|
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 := "a.Quota{}
|
||||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||||
q.SetUsed(types.ResourceList{types.ResourceCount: 50})
|
q.SetUsed(types.ResourceList{types.ResourceStorage: 50})
|
||||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0", nil)
|
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0", nil)
|
||||||
@ -263,8 +257,8 @@ func (suite *PutManifestMiddlewareTestSuite) TestResourcesWarning() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
q := "a.Quota{}
|
q := "a.Quota{}
|
||||||
q.SetHard(types.ResourceList{types.ResourceCount: 100})
|
q.SetHard(types.ResourceList{types.ResourceStorage: 100})
|
||||||
q.SetUsed(types.ResourceList{types.ResourceCount: 85})
|
q.SetUsed(types.ResourceList{types.ResourceStorage: 85})
|
||||||
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
mock.OnAnything(suite.quotaController, "GetByRef").Return(q, nil).Once()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0", nil)
|
req := httptest.NewRequest(http.MethodPut, "/v2/library/photon/manifests/2.0", nil)
|
||||||
|
@ -164,7 +164,7 @@ func (suite *RequestMiddlewareTestSuite) TestResourcesRequestOK() {
|
|||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
reference, referenceID := "project", "1"
|
reference, referenceID := "project", "1"
|
||||||
resources := types.ResourceList{types.ResourceCount: 1}
|
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||||
config := suite.makeRequestConfig(reference, referenceID, resources)
|
config := suite.makeRequestConfig(reference, referenceID, resources)
|
||||||
|
|
||||||
mock.OnAnything(suite.quotaController, "IsEnabled").Return(true, nil)
|
mock.OnAnything(suite.quotaController, "IsEnabled").Return(true, nil)
|
||||||
@ -183,7 +183,7 @@ func (suite *RequestMiddlewareTestSuite) TestResourcesRequestFailed() {
|
|||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
reference, referenceID := "project", "1"
|
reference, referenceID := "project", "1"
|
||||||
resources := types.ResourceList{types.ResourceCount: 1}
|
resources := types.ResourceList{types.ResourceStorage: 100}
|
||||||
config := suite.makeRequestConfig(reference, referenceID, resources)
|
config := suite.makeRequestConfig(reference, referenceID, resources)
|
||||||
|
|
||||||
mock.OnAnything(suite.quotaController, "IsEnabled").Return(true, nil)
|
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
|
#5. Get project quota
|
||||||
quota = self.system.get_project_quota("project", project_id, **ADMIN_CLIENT)
|
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)
|
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}
|
#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
|
#7. Get project quota
|
||||||
quota = self.system.get_project_quota("project", project_id, **ADMIN_CLIENT)
|
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)
|
self.assertEqual(quota[0].used["storage"], 2789002)
|
||||||
|
|
||||||
#8. Delete repository(RA) by user(UA);
|
#8. Delete repository(RA) by user(UA);
|
||||||
@ -65,7 +63,6 @@ class TestProjects(unittest.TestCase):
|
|||||||
|
|
||||||
#9. Quota should be 0
|
#9. Quota should be 0
|
||||||
quota = self.system.get_project_quota("project", project_id, **ADMIN_CLIENT)
|
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)
|
self.assertEqual(quota[0].used["storage"], 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
Reference in New Issue
Block a user