satellite/admin: add check project usage endpoint and fix some leftover http.Error handling

Change-Id: I1ae3e7cb723a553f9c5a3a752beab0a27b0293bc
This commit is contained in:
stefanbenten 2020-08-13 14:40:05 +02:00 committed by Stefan Benten
parent 4cdba365ef
commit c7b86a3481
8 changed files with 246 additions and 119 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
}
}

View File

@ -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)