satellite/admin: add check project usage endpoint and fix some leftover http.Error handling
Change-Id: I1ae3e7cb723a553f9c5a3a752beab0a27b0293bc
This commit is contained in:
parent
4cdba365ef
commit
c7b86a3481
@ -130,6 +130,12 @@ A successful response body:
|
||||
|
||||
Deletes the specified coupon.
|
||||
|
||||
## GET /api/project/{project-id}/usage
|
||||
|
||||
This endpoint returns whether the project has outstanding usage or not.
|
||||
|
||||
A project with not usage returns status code 200 and `{"result":"no project usage exist"}`.
|
||||
|
||||
## GET /api/project/{project-id}/limit
|
||||
|
||||
This endpoint returns information about project limits.
|
||||
|
@ -39,7 +39,7 @@ func TestAddCoupon(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"userId": "%s", "duration": 2, "amount": 3000, "description": "testcoupon-alice"}`, user.ID))
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/coupon", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -83,7 +83,7 @@ func TestCouponInfo(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"userId": "%s", "duration": 2, "amount": 3000, "description": "testcoupon-alice"}`, user.ID))
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/coupon", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -98,7 +98,7 @@ func TestCouponInfo(t *testing.T) {
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("http://"+address.String()+"/api/coupon/%s", id.String()), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -137,7 +137,7 @@ func TestCouponDelete(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"userId": "%s", "duration": 2, "amount": 3000, "description": "testcoupon-alice"}`, user.ID))
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/coupon", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -157,7 +157,7 @@ func TestCouponDelete(t *testing.T) {
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/coupon/%s", id), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
@ -4,6 +4,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@ -23,6 +24,31 @@ import (
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
)
|
||||
|
||||
func (server *Server) checkProjectUsage(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
projectUUIDString, ok := vars["project"]
|
||||
if !ok {
|
||||
httpJSONError(w, "project-uuid missing",
|
||||
"", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
projectUUID, err := uuid.FromString(projectUUIDString)
|
||||
if err != nil {
|
||||
httpJSONError(w, "invalid project-uuid",
|
||||
err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !server.checkUsage(ctx, w, projectUUID) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write([]byte(`{"result":"no project usage exist"}`))
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) getProjectLimit(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
@ -378,56 +404,10 @@ func (server *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// do not delete projects that have usage for the current month.
|
||||
year, month, _ := time.Now().UTC().Date()
|
||||
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
currentUsage, err := server.db.ProjectAccounting().GetProjectTotal(ctx, projectUUID, firstOfMonth, time.Now())
|
||||
if err != nil {
|
||||
httpJSONError(w, "unable to list project usage",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
// if usage exist, return error to client and exit
|
||||
if server.checkUsage(ctx, w, projectUUID) {
|
||||
return
|
||||
}
|
||||
if currentUsage.Storage > 0 || currentUsage.Egress > 0 || currentUsage.ObjectCount > 0 {
|
||||
httpJSONError(w, "usage for current month exists",
|
||||
"", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
// if usage of last month exist, make sure to look for billing records
|
||||
lastMonthUsage, err := server.db.ProjectAccounting().GetProjectTotal(ctx, projectUUID, firstOfMonth.AddDate(0, -1, 0), firstOfMonth.AddDate(0, 0, -1))
|
||||
if err != nil {
|
||||
httpJSONError(w, "error getting project totals",
|
||||
"", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if lastMonthUsage.Storage > 0 || lastMonthUsage.Egress > 0 || lastMonthUsage.ObjectCount > 0 {
|
||||
err := server.db.StripeCoinPayments().ProjectRecords().Check(ctx, projectUUID, firstOfMonth.AddDate(0, -1, 0), firstOfMonth.Add(-time.Hour))
|
||||
switch err {
|
||||
case stripecoinpayments.ErrProjectRecordExists:
|
||||
record, err := server.db.StripeCoinPayments().ProjectRecords().Get(ctx, projectUUID, firstOfMonth.AddDate(0, -1, 0), firstOfMonth.Add(-time.Hour))
|
||||
if err != nil {
|
||||
httpJSONError(w, "unable to get project records",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// state = 0 means unapplied and not invoiced yet.
|
||||
if record.State == 0 {
|
||||
httpJSONError(w, "unapplied project invoice record exist",
|
||||
"", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
case nil:
|
||||
httpJSONError(w, "usage for last month exist, but is not billed yet",
|
||||
"", http.StatusConflict)
|
||||
return
|
||||
default:
|
||||
httpJSONError(w, "unable to get project records",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = server.db.Console().Projects().Delete(ctx, projectUUID)
|
||||
if err != nil {
|
||||
@ -437,6 +417,61 @@ func (server *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) checkUsage(ctx context.Context, w http.ResponseWriter, projectID uuid.UUID) (hasUsage bool) {
|
||||
// do not delete projects that have usage for the current month.
|
||||
year, month, _ := time.Now().UTC().Date()
|
||||
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
currentUsage, err := server.db.ProjectAccounting().GetProjectTotal(ctx, projectID, firstOfMonth, time.Now())
|
||||
if err != nil {
|
||||
httpJSONError(w, "unable to list project usage",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
if currentUsage.Storage > 0 || currentUsage.Egress > 0 || currentUsage.ObjectCount > 0 {
|
||||
httpJSONError(w, "usage for current month exists",
|
||||
"", http.StatusConflict)
|
||||
return true
|
||||
}
|
||||
|
||||
// if usage of last month exist, make sure to look for billing records
|
||||
lastMonthUsage, err := server.db.ProjectAccounting().GetProjectTotal(ctx, projectID, firstOfMonth.AddDate(0, -1, 0), firstOfMonth.AddDate(0, 0, -1))
|
||||
if err != nil {
|
||||
httpJSONError(w, "error getting project totals",
|
||||
"", http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
|
||||
if lastMonthUsage.Storage > 0 || lastMonthUsage.Egress > 0 || lastMonthUsage.ObjectCount > 0 {
|
||||
err := server.db.StripeCoinPayments().ProjectRecords().Check(ctx, projectID, firstOfMonth.AddDate(0, -1, 0), firstOfMonth.Add(-time.Hour))
|
||||
switch err {
|
||||
case stripecoinpayments.ErrProjectRecordExists:
|
||||
record, err := server.db.StripeCoinPayments().ProjectRecords().Get(ctx, projectID, firstOfMonth.AddDate(0, -1, 0), firstOfMonth.Add(-time.Hour))
|
||||
if err != nil {
|
||||
httpJSONError(w, "unable to get project records",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
// state = 0 means unapplied and not invoiced yet.
|
||||
if record.State == 0 {
|
||||
httpJSONError(w, "unapplied project invoice record exist",
|
||||
"", http.StatusConflict)
|
||||
return true
|
||||
}
|
||||
case nil:
|
||||
httpJSONError(w, "usage for last month exist, but is not billed yet",
|
||||
"", http.StatusConflict)
|
||||
return true
|
||||
default:
|
||||
httpJSONError(w, "unable to get project records",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func bucketNames(buckets []storj.Bucket) []string {
|
||||
var xs []string
|
||||
for _, b := range buckets {
|
||||
|
@ -44,14 +44,14 @@ func TestAPI(t *testing.T) {
|
||||
link := "http://" + address.String() + "/api/project/" + project.ID.String() + "/limit"
|
||||
|
||||
t.Run("GetProject", func(t *testing.T) {
|
||||
assertGet(t, link, `{"usage":{"amount":"0 B","bytes":0},"bandwidth":{"amount":"0 B","bytes":0},"rate":{"rps":0},"maxBuckets":0}`)
|
||||
assertGet(t, link, `{"usage":{"amount":"0 B","bytes":0},"bandwidth":{"amount":"0 B","bytes":0},"rate":{"rps":0},"maxBuckets":0}`, planet.Satellites[0].Config.Console.AuthToken)
|
||||
})
|
||||
|
||||
t.Run("UpdateUsage", func(t *testing.T) {
|
||||
data := url.Values{"usage": []string{"1TiB"}}
|
||||
req, err := http.NewRequest(http.MethodPost, link, strings.NewReader(data.Encode()))
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
@ -59,56 +59,56 @@ func TestAPI(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
|
||||
assertGet(t, link, `{"usage":{"amount":"1.0 TiB","bytes":1099511627776},"bandwidth":{"amount":"0 B","bytes":0},"rate":{"rps":0},"maxBuckets":0}`)
|
||||
assertGet(t, link, `{"usage":{"amount":"1.0 TiB","bytes":1099511627776},"bandwidth":{"amount":"0 B","bytes":0},"rate":{"rps":0},"maxBuckets":0}`, planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
req, err = http.NewRequest(http.MethodPut, link+"?usage=1GB", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"0 B","bytes":0},"rate":{"rps":0},"maxBuckets":0}`)
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"0 B","bytes":0},"rate":{"rps":0},"maxBuckets":0}`, planet.Satellites[0].Config.Console.AuthToken)
|
||||
})
|
||||
|
||||
t.Run("UpdateBandwidth", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut, link+"?bandwidth=1MB", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"1.00 MB","bytes":1000000},"rate":{"rps":0},"maxBuckets":0}`)
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"1.00 MB","bytes":1000000},"rate":{"rps":0},"maxBuckets":0}`, planet.Satellites[0].Config.Console.AuthToken)
|
||||
})
|
||||
|
||||
t.Run("UpdateRate", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut, link+"?rate=100", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"1.00 MB","bytes":1000000},"rate":{"rps":100},"maxBuckets":0}`)
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"1.00 MB","bytes":1000000},"rate":{"rps":100},"maxBuckets":0}`, planet.Satellites[0].Config.Console.AuthToken)
|
||||
})
|
||||
t.Run("UpdateBuckets", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut, link+"?buckets=2000", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"1.00 MB","bytes":1000000},"rate":{"rps":100},"maxBuckets":2000}`)
|
||||
assertGet(t, link, `{"usage":{"amount":"1.00 GB","bytes":1000000000},"bandwidth":{"amount":"1.00 MB","bytes":1000000},"rate":{"rps":100},"maxBuckets":2000}`, planet.Satellites[0].Config.Console.AuthToken)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -130,7 +130,7 @@ func TestAddProject(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"ownerId":"%s","projectName":"Test Project"}`, userID.ID.String()))
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/project", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -174,7 +174,7 @@ func TestRenameProject(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"projectName":"%s","description":"This project got renamed"}`, newName))
|
||||
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("http://"+address.String()+"/api/project/%s", project.ID.String()), body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -217,7 +217,7 @@ func TestDeleteProject(t *testing.T) {
|
||||
// the deletion with an existing API key should fail
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/project/%s", projectID), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -229,7 +229,7 @@ func TestDeleteProject(t *testing.T) {
|
||||
|
||||
req, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/project/%s", projectID), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -242,6 +242,112 @@ func TestDeleteProject(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckUsageWithoutUsage(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
StorageNodeCount: 0,
|
||||
UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Admin.Address = "127.0.0.1:0"
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
|
||||
projectID := planet.Uplinks[0].Projects[0].ID
|
||||
|
||||
apiKeys, err := planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 2,
|
||||
Search: "",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, apiKeys.APIKeys, 1)
|
||||
|
||||
err = planet.Satellites[0].DB.Console().APIKeys().Delete(ctx, apiKeys.APIKeys[0].ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://"+address.String()+"/api/project/%s/usage", projectID), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "{\"result\":\"no project usage exist\"}", string(responseBody))
|
||||
require.NoError(t, response.Body.Close())
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckUsageWithUsage(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
StorageNodeCount: 0,
|
||||
UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Admin.Address = "127.0.0.1:0"
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
|
||||
projectID := planet.Uplinks[0].Projects[0].ID
|
||||
|
||||
apiKeys, err := planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{
|
||||
Page: 1,
|
||||
Limit: 2,
|
||||
Search: "",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, apiKeys.APIKeys, 1)
|
||||
|
||||
err = planet.Satellites[0].DB.Console().APIKeys().Delete(ctx, apiKeys.APIKeys[0].ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
accTime := time.Now().UTC().AddDate(0, 0, -1)
|
||||
tally := accounting.BucketStorageTally{
|
||||
BucketName: "test",
|
||||
ProjectID: projectID,
|
||||
IntervalStart: accTime,
|
||||
ObjectCount: 1,
|
||||
InlineSegmentCount: 1,
|
||||
RemoteSegmentCount: 1,
|
||||
InlineBytes: 10,
|
||||
RemoteBytes: 640000,
|
||||
MetadataSize: 2,
|
||||
}
|
||||
err = planet.Satellites[0].DB.ProjectAccounting().CreateStorageTally(ctx, tally)
|
||||
require.NoError(t, err)
|
||||
tally = accounting.BucketStorageTally{
|
||||
BucketName: "test",
|
||||
ProjectID: projectID,
|
||||
IntervalStart: accTime.AddDate(0, 0, 1),
|
||||
ObjectCount: 1,
|
||||
InlineSegmentCount: 1,
|
||||
RemoteSegmentCount: 1,
|
||||
InlineBytes: 10,
|
||||
RemoteBytes: 640000,
|
||||
MetadataSize: 2,
|
||||
}
|
||||
err = planet.Satellites[0].DB.ProjectAccounting().CreateStorageTally(ctx, tally)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://"+address.String()+"/api/project/%s/usage", projectID), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "{\"error\":\"usage for current month exists\",\"detail\":\"\"}", string(responseBody))
|
||||
require.NoError(t, response.Body.Close())
|
||||
require.Equal(t, http.StatusConflict, response.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteProjectWithUsageCurrentMonth(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
@ -295,23 +401,9 @@ func TestDeleteProjectWithUsageCurrentMonth(t *testing.T) {
|
||||
err = planet.Satellites[0].DB.ProjectAccounting().CreateStorageTally(ctx, tally)
|
||||
require.NoError(t, err)
|
||||
|
||||
inline, remote, err := planet.Satellites[0].DB.ProjectAccounting().GetStorageTotals(ctx, projectID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), inline)
|
||||
require.Equal(t, int64(640000), remote)
|
||||
|
||||
bw, err := planet.Satellites[0].DB.ProjectAccounting().GetAllocatedBandwidthTotal(ctx, projectID, accTime.AddDate(0, 0, -1))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, bw)
|
||||
|
||||
usage, err := planet.Satellites[0].DB.ProjectAccounting().GetProjectTotal(ctx, projectID, accTime.AddDate(0, 0, -1), accTime.AddDate(0, 0, 2))
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, usage.Egress)
|
||||
require.NotEqual(t, float64(0), usage.Storage)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/project/%s", projectID), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -379,23 +471,9 @@ func TestDeleteProjectWithUsagePreviousMonth(t *testing.T) {
|
||||
err = planet.Satellites[0].DB.ProjectAccounting().CreateStorageTally(ctx, tally)
|
||||
require.NoError(t, err)
|
||||
|
||||
inline, remote, err := planet.Satellites[0].DB.ProjectAccounting().GetStorageTotals(ctx, projectID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), inline)
|
||||
require.Equal(t, int64(640000), remote)
|
||||
|
||||
bw, err := planet.Satellites[0].DB.ProjectAccounting().GetAllocatedBandwidthTotal(ctx, projectID, accTime.AddDate(0, 0, -1))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, bw)
|
||||
|
||||
usage, err := planet.Satellites[0].DB.ProjectAccounting().GetProjectTotal(ctx, projectID, accTime.AddDate(0, 0, -1), accTime.AddDate(0, 0, 2))
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, usage.Egress)
|
||||
require.NotEqual(t, float64(0), usage.Storage)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/project/%s", projectID), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -407,13 +485,13 @@ func TestDeleteProjectWithUsagePreviousMonth(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func assertGet(t *testing.T, link string, expected string) {
|
||||
func assertGet(t *testing.T, link string, expected string, authToken string) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, link, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", authToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
@ -42,7 +42,7 @@ type DB interface {
|
||||
Buckets() metainfo.BucketsDB
|
||||
}
|
||||
|
||||
// Server provides endpoints for debugging.
|
||||
// Server provides endpoints for administrative tasks.
|
||||
type Server struct {
|
||||
log *zap.Logger
|
||||
|
||||
@ -54,7 +54,7 @@ type Server struct {
|
||||
payments payments.Accounts
|
||||
}
|
||||
|
||||
// NewServer returns a new debug.Server.
|
||||
// NewServer returns a new administration Server.
|
||||
func NewServer(log *zap.Logger, listener net.Listener, db DB, accounts payments.Accounts, config Config) *Server {
|
||||
server := &Server{
|
||||
log: log,
|
||||
@ -79,6 +79,7 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, accounts payments.
|
||||
server.mux.HandleFunc("/api/coupon", server.addCoupon).Methods("POST")
|
||||
server.mux.HandleFunc("/api/coupon/{couponid}", server.couponInfo).Methods("GET")
|
||||
server.mux.HandleFunc("/api/coupon/{couponid}", server.deleteCoupon).Methods("DELETE")
|
||||
server.mux.HandleFunc("/api/project/{project}/usage", server.checkProjectUsage).Methods("GET")
|
||||
server.mux.HandleFunc("/api/project/{project}/limit", server.getProjectLimit).Methods("GET")
|
||||
server.mux.HandleFunc("/api/project/{project}/limit", server.putProjectLimit).Methods("PUT", "POST")
|
||||
server.mux.HandleFunc("/api/project/{project}", server.renameProject).Methods("PUT")
|
||||
@ -96,7 +97,8 @@ type protectedServer struct {
|
||||
|
||||
func (server *protectedServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if server.allowedAuthorization == "" {
|
||||
http.Error(w, "Authorization not enabled.", http.StatusForbidden)
|
||||
httpJSONError(w, "Authorization not enabled.",
|
||||
"", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@ -105,7 +107,8 @@ func (server *protectedServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
[]byte(server.allowedAuthorization),
|
||||
)
|
||||
if equality != 1 {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
httpJSONError(w, "Forbidden",
|
||||
"", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@ -114,7 +117,7 @@ func (server *protectedServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
server.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Run starts the debug endpoint.
|
||||
// Run starts the admin endpoint.
|
||||
func (server *Server) Run(ctx context.Context) error {
|
||||
if server.listener == nil {
|
||||
return nil
|
||||
|
@ -52,7 +52,7 @@ func TestBasic(t *testing.T) {
|
||||
t.Run("WithAccess", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://"+address.String(), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
@ -40,10 +40,12 @@ func (server *Server) addUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch {
|
||||
case input.Email == "":
|
||||
http.Error(w, "Email is not set", http.StatusBadRequest)
|
||||
httpJSONError(w, "email is not set",
|
||||
"", http.StatusBadRequest)
|
||||
return
|
||||
case input.Password == "":
|
||||
http.Error(w, "Password is not set", http.StatusBadRequest)
|
||||
httpJSONError(w, "password is not set",
|
||||
"", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -297,12 +299,13 @@ func (server *Server) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
// Ensure user has no own projects any longer
|
||||
projects, err := server.db.Console().Projects().GetByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
httpJSONError(w, "unable to list buckets",
|
||||
httpJSONError(w, "unable to list projects",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(projects) > 0 {
|
||||
http.Error(w, fmt.Sprintf("some projects still exist: %v", projects), http.StatusConflict)
|
||||
httpJSONError(w, "some projects still exist",
|
||||
fmt.Sprintf("%v", projects), http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
@ -317,7 +320,8 @@ func (server *Server) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
for _, project := range members {
|
||||
err := server.db.Console().ProjectMembers().Delete(ctx, user.ID, project.ProjectID)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("unable to delete user project membership: %v", err), http.StatusInternalServerError)
|
||||
httpJSONError(w, "unable to delete user project membership",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -333,7 +337,8 @@ func (server *Server) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
if len(invoices) > 0 {
|
||||
for _, invoice := range invoices {
|
||||
if invoice.Status != "paid" {
|
||||
http.Error(w, "user has unpaid/pending invoices", http.StatusConflict)
|
||||
httpJSONError(w, "user has unpaid/pending invoices",
|
||||
"", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func TestGetUser(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, userLink, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -83,7 +83,7 @@ func TestAddUser(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"email":"%s","fullName":"Alice Test","password":"123a123"}`, email))
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/user", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -120,7 +120,7 @@ func TestAddUserSameEmail(t *testing.T) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"email":"%s","fullName":"Alice Test","password":"123a123"}`, email))
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/user", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -142,7 +142,7 @@ func TestAddUserSameEmail(t *testing.T) {
|
||||
body = strings.NewReader(fmt.Sprintf(`{"email":"%s","fullName":"Alice Test","password":"123a123"}`, email))
|
||||
req, err = http.NewRequest(http.MethodPost, "http://"+address.String()+"/api/user", body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -169,7 +169,7 @@ func TestUpdateUser(t *testing.T) {
|
||||
body := strings.NewReader(`{"email":"alice+2@mail.test", "shortName":"Newbie"}`)
|
||||
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("http://"+address.String()+"/api/user/%s", user.Email), body)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -208,7 +208,7 @@ func TestDeleteUser(t *testing.T) {
|
||||
// Deleting the user should fail, as project exists
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/user/%s", user.Email), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -224,7 +224,7 @@ func TestDeleteUser(t *testing.T) {
|
||||
// Deleting the user should pass, as no project exists for given user
|
||||
req, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/user/%s", user.Email), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "very-secret-token")
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
Loading…
Reference in New Issue
Block a user