Skip to content

Commit 6741376

Browse files
committed
added op mode change logic and routes
1 parent c10f973 commit 6741376

File tree

13 files changed

+350
-23
lines changed

13 files changed

+350
-23
lines changed

Diff for: model/raft.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import (
77

88
// RaftCommand is the object passed as a raft entry
99
type RaftCommand struct {
10-
Kind utils.RaftCommandType `json:"kind"`
11-
ID string `json:"projectId"`
12-
Project *config.Project `json:"project"`
13-
Deploy *config.Deploy `json:"deploy"`
10+
Kind utils.RaftCommandType `json:"kind"`
11+
ID string `json:"projectId"`
12+
Project *config.Project `json:"project"`
13+
Deploy *config.Deploy `json:"deploy"`
14+
Operation *config.OperationConfig `json:"operation"`
1415
}

Diff for: model/validate.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package model
33
// RegisterRequest is the struct which carries the space cloud register payload
44
type RegisterRequest struct {
55
ID string `json:"id"` // This is the space cloud id
6-
Secret string `json:"secret"`
6+
Key string `json:"key"`
77
Account string `json:"account"`
88
}
99

@@ -12,9 +12,3 @@ type RegisterResponse struct {
1212
Ack bool `json:"ack"`
1313
Error string `json:"error"`
1414
}
15-
16-
// ProjectFeed is the body sent to push a project config
17-
type ProjectFeed struct {
18-
Config interface{} `json:"config"`
19-
Project string `json:"project"`
20-
}

Diff for: utils/admin/admin.go

+32-4
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ import (
1010

1111
// Manager manages all admin transactions
1212
type Manager struct {
13-
lock sync.RWMutex
14-
admin *config.Admin
13+
lock sync.RWMutex
14+
nodeID string
15+
admin *config.Admin
16+
validator *validator
1517
}
1618

1719
// New creates a new admin manager instance
18-
func New() *Manager {
19-
return &Manager{}
20+
func New(nodeID string) *Manager {
21+
m := new(Manager)
22+
m.nodeID = nodeID
23+
m.validator = newValidator(m.reduceOpMode)
24+
return m
2025
}
2126

2227
// SetConfig sets the admin config
@@ -26,6 +31,29 @@ func (m *Manager) SetConfig(admin *config.Admin) {
2631
m.lock.Unlock()
2732
}
2833

34+
// SetOperationMode sets the operation mode
35+
func (m *Manager) SetOperationMode(op *config.OperationConfig) error {
36+
m.lock.Lock()
37+
defer m.lock.Unlock()
38+
39+
if op.Mode > 0 && (op.Email == "" || op.Key == "") {
40+
return errors.New("Invalid operation setting provided")
41+
}
42+
43+
if op.Mode > 0 {
44+
// Start the validation process for higher op modes
45+
if err := m.validator.startValidation(m.nodeID, op.Email, op.Key); err != nil {
46+
return err
47+
}
48+
} else {
49+
// Stop validation for open source mode
50+
m.validator.stopValidation()
51+
}
52+
53+
m.admin.Operation = *op
54+
return nil
55+
}
56+
2957
// Login handles the admin login operation
3058
func (m *Manager) Login(user, pass string) (int, string, error) {
3159
m.lock.RLock()

Diff for: utils/admin/operations.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (m *Manager) IsAdminOpAuthorised(token, scope string) (int, error) {
5050

5151
if scope == utils.ScopeDeploy {
5252
if m.admin.Operation.Mode < 1 {
53-
return http.StatusForbidden, errors.New("Operation not supported. Upgrade to avail this feature")
53+
return http.StatusForbidden, errors.New("Please upgrade your instance")
5454
}
5555
}
5656

@@ -83,3 +83,9 @@ func (m *Manager) IsAdminOpAuthorised(token, scope string) (int, error) {
8383

8484
return http.StatusForbidden, errors.New("You are not authorized to make this request")
8585
}
86+
87+
func (m *Manager) reduceOpMode() {
88+
m.lock.RLock()
89+
defer m.lock.RUnlock()
90+
m.admin.Operation.Mode = 0
91+
}

Diff for: utils/admin/validate.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package admin
2+
3+
import (
4+
"log"
5+
"sync"
6+
"time"
7+
8+
"github.com/gorilla/websocket"
9+
)
10+
11+
const url = "wss://spaceuptech.com/v1/authenticate/socket/json"
12+
13+
// validator is the object which valiates the space cloud instance
14+
type validator struct {
15+
lock sync.Mutex
16+
socket *websocket.Conn
17+
active bool
18+
reduceMode func()
19+
}
20+
21+
// New creates a new instance of validator
22+
func newValidator(cb func()) *validator {
23+
return &validator{active: false, reduceMode: cb}
24+
}
25+
26+
// Start starts the validation process
27+
func (v *validator) startValidation(id, account, key string) error {
28+
// Set validation status to active
29+
v.lock.Lock()
30+
v.active = true
31+
v.lock.Unlock()
32+
33+
if err := v.registerSpaceCloud(id, account, key); err != nil {
34+
return err
35+
}
36+
37+
go func() {
38+
timer := time.Now()
39+
for {
40+
if !v.isActive() {
41+
return
42+
}
43+
44+
if err := v.routineRead(); err != nil {
45+
log.Println("Validate: Error -", err)
46+
}
47+
48+
// Sleep for 5 minutes before connecting again
49+
time.Sleep(5 * time.Minute)
50+
51+
// Check if 15 days are lapsed without authorization
52+
if time.Since(timer).Hours() > 24*15 {
53+
54+
// Reduce op mode to open source
55+
v.reduceMode()
56+
return
57+
}
58+
59+
if err := v.registerSpaceCloud(id, account, key); err != nil {
60+
log.Println("Validate: Error -", err)
61+
} else {
62+
timer = time.Now()
63+
}
64+
}
65+
}()
66+
67+
return nil
68+
}
69+
70+
func (v *validator) stopValidation() {
71+
v.lock.Lock()
72+
v.active = false
73+
v.lock.Unlock()
74+
}
75+
76+
func (v *validator) isActive() bool {
77+
v.lock.Lock()
78+
defer v.lock.Unlock()
79+
return v.active
80+
}

Diff for: utils/admin/websocket.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package admin
2+
3+
import (
4+
"errors"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"github.com/gorilla/websocket"
10+
"github.com/mitchellh/mapstructure"
11+
12+
"github.com/spaceuptech/space-cloud/model"
13+
"github.com/spaceuptech/space-cloud/utils"
14+
)
15+
16+
func (v *validator) connect(url string) error {
17+
// Create a new client
18+
c, _, err := websocket.DefaultDialer.Dial(url, nil)
19+
if err != nil {
20+
return err
21+
}
22+
v.socket = c
23+
24+
// Start a routine to routinely send pings
25+
go func(socket *websocket.Conn) {
26+
ticker := time.NewTicker(20 * time.Second)
27+
for range ticker.C {
28+
if err := socket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
29+
log.Println("Validator Error:", err)
30+
return
31+
}
32+
}
33+
}(c)
34+
35+
return nil
36+
}
37+
38+
func (v *validator) write(id, account, key string) error {
39+
req := &model.RegisterRequest{ID: id, Account: account, Key: key}
40+
msg := &model.Message{Type: utils.TypeRegisterRequest, Data: req}
41+
return v.socket.WriteJSON(msg)
42+
}
43+
44+
func (v *validator) read() (msg *model.Message, err error) {
45+
msg = &model.Message{}
46+
err = v.socket.ReadJSON(msg)
47+
return
48+
}
49+
50+
func (v *validator) routineRead() error {
51+
for {
52+
msg, err := v.read()
53+
if err != nil {
54+
return err
55+
}
56+
57+
switch msg.Type {
58+
case utils.TypeRegisterRequest:
59+
60+
// For register request
61+
data := new(model.RegisterResponse)
62+
mapstructure.Decode(msg.Data, data)
63+
64+
if !data.Ack {
65+
log.Println("Validate Error -", data.Error)
66+
os.Exit(-1)
67+
}
68+
}
69+
}
70+
}
71+
72+
func (v *validator) registerSpaceCloud(id, account, secret string) error {
73+
err := v.connect(url)
74+
if err != nil {
75+
return err
76+
}
77+
78+
err = v.write(id, account, secret)
79+
if err != nil {
80+
return err
81+
}
82+
83+
msg, err := v.read()
84+
if err != nil {
85+
return err
86+
}
87+
88+
if msg.Type == utils.TypeRegisterRequest {
89+
// For register request
90+
data := new(model.RegisterResponse)
91+
mapstructure.Decode(msg.Data, data)
92+
93+
if !data.Ack {
94+
return errors.New(data.Error)
95+
}
96+
}
97+
98+
return nil
99+
}

Diff for: utils/constants.go

+6
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ const (
165165
// RaftCommandSetDeploy is used to set the deploy config
166166
RaftCommandSetDeploy RaftCommandType = "set-deploy"
167167

168+
// RaftCommandSetOperation is used to set the deploy config
169+
RaftCommandSetOperation RaftCommandType = "set-operation"
170+
168171
// RaftCommandDelete is used to delete a projects config
169172
RaftCommandDelete RaftCommandType = "delete"
170173
)
@@ -184,3 +187,6 @@ const (
184187
// ScopeDeploy is te scope used for the deploy module
185188
ScopeDeploy string = "deploy"
186189
)
190+
191+
// TypeRegisterRequest is the space cloud register request
192+
const TypeRegisterRequest string = "register"

Diff for: utils/handlers/config.go

+70-2
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,76 @@ func HandleStoreDeploymentConfig(adminMan *admin.Manager, syncMan *syncman.SyncM
200200
return
201201
}
202202

203-
w.WriteHeader(http.StatusForbidden)
204-
json.NewEncoder(w).Encode(map[string]string{"error": "Operation not supported. Upgrade to avail this feature"})
203+
w.WriteHeader(http.StatusOK)
204+
json.NewEncoder(w).Encode(map[string]string{})
205+
}
206+
}
207+
208+
// HandleStoreOperationModeConfig returns the handler to store the deployment config via a REST endpoint
209+
func HandleStoreOperationModeConfig(adminMan *admin.Manager, syncMan *syncman.SyncManager) http.HandlerFunc {
210+
return func(w http.ResponseWriter, r *http.Request) {
211+
212+
// Get the JWT token from header
213+
token := getToken(r)
214+
215+
// Load the body of the request
216+
c := new(config.OperationConfig)
217+
if err := json.NewDecoder(r.Body).Decode(c); err != nil {
218+
w.WriteHeader(http.StatusInternalServerError)
219+
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
220+
return
221+
}
222+
defer r.Body.Close()
223+
224+
// Check if the request is authorised
225+
status, err := adminMan.IsAdminOpAuthorised(token, "deploy")
226+
if err != nil {
227+
w.WriteHeader(status)
228+
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
229+
return
230+
}
231+
232+
// Set the operation mode config
233+
if err := adminMan.SetOperationMode(c); err != nil {
234+
w.WriteHeader(http.StatusInternalServerError)
235+
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
236+
return
237+
}
238+
239+
// Apply it to raft log
240+
if err := syncMan.SetOperationModeConfig(token, c); err != nil {
241+
// Reset the operation mode
242+
c.Mode = 0
243+
adminMan.SetOperationMode(c)
244+
245+
w.WriteHeader(http.StatusInternalServerError)
246+
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
247+
return
248+
}
249+
250+
w.WriteHeader(http.StatusOK)
251+
json.NewEncoder(w).Encode(map[string]string{})
252+
}
253+
}
254+
255+
// HandleLoadOperationModeConfig returns the handler to load the operation config via a REST endpoint
256+
func HandleLoadOperationModeConfig(adminMan *admin.Manager, syncMan *syncman.SyncManager) http.HandlerFunc {
257+
return func(w http.ResponseWriter, r *http.Request) {
258+
259+
// Get the JWT token from header
260+
token := getToken(r)
261+
262+
// Check if the token is valid
263+
if status, err := adminMan.IsAdminOpAuthorised(token, "deploy"); err != nil {
264+
w.WriteHeader(status)
265+
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
266+
return
267+
}
268+
269+
c := syncMan.GetGlobalConfig()
270+
271+
w.WriteHeader(http.StatusOK)
272+
json.NewEncoder(w).Encode(map[string]interface{}{"operation": &c.Admin.Operation})
205273
}
206274
}
207275

Diff for: utils/server/routes.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ func (s *Server) Routes(profiler bool, staticPath string) {
1212
s.router.Methods("POST").Path("/v1/api/config/login").HandlerFunc(handlers.HandleAdminLogin(s.adminMan, s.syncMan))
1313
s.router.Methods("GET").Path("/v1/api/config/projects").HandlerFunc(handlers.HandleLoadProjects(s.adminMan, s.syncMan))
1414
s.router.Methods("POST").Path("/v1/api/config/projects").HandlerFunc(handlers.HandleStoreProjectConfig(s.adminMan, s.syncMan))
15-
s.router.Methods("DELETE").Path("/v1/api/config/{project}").HandlerFunc(handlers.HandleDeleteProjectConfig(s.adminMan, s.syncMan))
1615
s.router.Methods("GET").Path("/v1/api/config/deploy").HandlerFunc(handlers.HandleLoadDeploymentConfig(s.adminMan, s.syncMan))
1716
s.router.Methods("POST").Path("/v1/api/config/deploy").HandlerFunc(handlers.HandleStoreDeploymentConfig(s.adminMan, s.syncMan))
17+
s.router.Methods("GET").Path("/v1/api/config/operation").HandlerFunc(handlers.HandleLoadOperationModeConfig(s.adminMan, s.syncMan))
18+
s.router.Methods("POST").Path("/v1/api/config/operation").HandlerFunc(handlers.HandleStoreOperationModeConfig(s.adminMan, s.syncMan))
19+
s.router.Methods("DELETE").Path("/v1/api/config/{project}").HandlerFunc(handlers.HandleDeleteProjectConfig(s.adminMan, s.syncMan))
1820

1921
// Initialize routes for the deployment module
2022
s.router.Methods("POST").Path("/v1/api/deploy").HandlerFunc(handlers.HandleUploadAndDeploy(s.adminMan, s.deploy))

0 commit comments

Comments
 (0)