satellite/admin: extend admin API to allow setting and deleting geofence for projects

Issue: https://github.com/storj/storj-private/issues/357
Change-Id: Ib59319581641f1f5da71c629143e12f11eb04925
This commit is contained in:
Clement Sam 2023-07-25 15:26:35 +00:00
parent 4cc167a6bd
commit cc12a48c24
3 changed files with 151 additions and 0 deletions

View File

@ -20,6 +20,7 @@ import (
"storj.io/common/macaroon"
"storj.io/common/memory"
"storj.io/common/storj"
"storj.io/common/uuid"
"storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console"
@ -697,6 +698,55 @@ func (server *Server) checkUsage(ctx context.Context, w http.ResponseWriter, pro
return server.checkInvoicing(ctx, w, projectID)
}
func (server *Server) createGeofenceForProject(w http.ResponseWriter, r *http.Request) {
placement, err := parsePlacementConstraint(r.URL.Query().Get("region"))
if err != nil {
sendJSONError(w, err.Error(), "available: EU, EEA, US, DE, NR", http.StatusBadRequest)
return
}
server.setGeofenceForProject(w, r, placement)
}
func (server *Server) deleteGeofenceForProject(w http.ResponseWriter, r *http.Request) {
server.setGeofenceForProject(w, r, storj.EveryCountry)
}
func (server *Server) setGeofenceForProject(w http.ResponseWriter, r *http.Request, placement storj.PlacementConstraint) {
ctx := r.Context()
vars := mux.Vars(r)
projectUUIDString, ok := vars["project"]
if !ok {
sendJSONError(w, "project-uuid missing",
"", http.StatusBadRequest)
return
}
projectUUID, err := uuid.FromString(projectUUIDString)
if err != nil {
sendJSONError(w, "invalid project-uuid",
err.Error(), http.StatusBadRequest)
return
}
project, err := server.db.Console().Projects().Get(ctx, projectUUID)
if errors.Is(err, sql.ErrNoRows) {
sendJSONError(w, "project with specified uuid does not exist",
"", http.StatusNotFound)
return
}
project.DefaultPlacement = placement
err = server.db.Console().Projects().Update(ctx, project)
if err != nil {
sendJSONError(w, "unable to set geofence for project",
err.Error(), http.StatusInternalServerError)
return
}
}
func bucketNames(buckets []buckets.Bucket) []string {
var xs []string
for _, b := range buckets {

View File

@ -0,0 +1,99 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
package admin_test
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"storj.io/common/storj"
"storj.io/common/testcontext"
"storj.io/common/uuid"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite"
)
func TestAdminProjectGeofenceAPI(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
uplink := planet.Uplinks[0]
sat := planet.Satellites[0]
address := sat.Admin.Admin.Listener.Addr()
project, err := sat.DB.Console().Projects().Get(ctx, uplink.Projects[0].ID)
require.NoError(t, err)
// update project set default placement to EEA
project.DefaultPlacement = storj.EEA
require.NoError(t, sat.DB.Console().Projects().Update(ctx, project))
testCases := []struct {
name string
project uuid.UUID
// expectations
status int
body string
}{
{
name: "project does not exist",
project: uuid.NullUUID{}.UUID,
status: http.StatusNotFound,
body: `{"error":"project with specified uuid does not exist","detail":""}`,
},
{
name: "validated",
project: project.ID,
status: http.StatusOK,
body: "",
},
}
for _, testCase := range testCases {
baseURL := fmt.Sprintf("http://%s/api/projects/%s", address, testCase.project)
t.Log(baseURL)
baseGeofenceURL := fmt.Sprintf("http://%s/api/projects/%s/geofence", address, testCase.project)
t.Log(baseGeofenceURL)
t.Run(testCase.name, func(t *testing.T) {
assertReq(ctx, t, baseGeofenceURL+"?region=EU", "POST", "", testCase.status, testCase.body, sat.Config.Console.AuthToken)
if testCase.status == http.StatusOK {
t.Run("Set", func(t *testing.T) {
project, err := sat.DB.Console().Projects().Get(ctx, testCase.project)
require.NoError(t, err)
require.Equal(t, storj.EU, project.DefaultPlacement)
expected, err := json.Marshal(project)
require.NoError(t, err, "failed to json encode expected bucket")
assertGet(ctx, t, baseURL, string(expected), sat.Config.Console.AuthToken)
})
t.Run("Delete", func(t *testing.T) {
assertReq(ctx, t, baseGeofenceURL, "DELETE", "", testCase.status, testCase.body, sat.Config.Console.AuthToken)
project, err := sat.DB.Console().Projects().Get(ctx, testCase.project)
require.NoError(t, err)
expected, err := json.Marshal(project)
require.NoError(t, err)
assertGet(ctx, t, baseURL, string(expected), sat.Config.Console.AuthToken)
})
}
})
}
})
}

View File

@ -138,6 +138,8 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
fullAccessAPI.HandleFunc("/projects/{project}/buckets/{bucket}/geofence", server.deleteGeofenceForBucket).Methods("DELETE")
fullAccessAPI.HandleFunc("/projects/{project}/usage", server.checkProjectUsage).Methods("GET")
fullAccessAPI.HandleFunc("/projects/{project}/useragent", server.updateProjectsUserAgent).Methods("PATCH")
fullAccessAPI.HandleFunc("/projects/{project}/geofence", server.createGeofenceForProject).Methods("POST")
fullAccessAPI.HandleFunc("/projects/{project}/geofence", server.deleteGeofenceForProject).Methods("DELETE")
fullAccessAPI.HandleFunc("/apikeys/{apikey}", server.getAPIKey).Methods("GET")
fullAccessAPI.HandleFunc("/apikeys/{apikey}", server.deleteAPIKey).Methods("DELETE")
fullAccessAPI.HandleFunc("/restkeys/{useremail}", server.addRESTKey).Methods("POST")