Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
Add version 2 rest api
Browse files Browse the repository at this point in the history
Adds version 2 of the rest api in order to have more consistent
responses, errors, and conform to REST better.

- Add package v2/mock
- Add medium tests for v2
- Implement API interface for v2
- Implement v2 routes
  • Loading branch information
kindermoumoute committed Jan 25, 2017
1 parent da7e778 commit e741f41
Show file tree
Hide file tree
Showing 13 changed files with 2,527 additions and 0 deletions.
463 changes: 463 additions & 0 deletions mgmt/rest/rest_v2_test.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions mgmt/rest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/intelsdi-x/snap/mgmt/rest/api"
"github.com/intelsdi-x/snap/mgmt/rest/v1"
"github.com/intelsdi-x/snap/mgmt/rest/v2"
)

var (
Expand Down Expand Up @@ -82,6 +83,7 @@ func New(cfg *Config) (*Server, error) {

s.apis = []api.API{
v1.New(&s.wg, s.killChan, protocolPrefix),
v2.New(&s.wg, s.killChan, protocolPrefix),
}

s.n = negroni.New(
Expand Down
114 changes: 114 additions & 0 deletions mgmt/rest/v2/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
http://www.apache.org/licenses/LICENSE-2.0.txt
Copyright 2017 Intel Corporation
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 v2

import (
"encoding/json"
"sync"

"net/http"

log "github.com/Sirupsen/logrus"
"github.com/intelsdi-x/snap/mgmt/rest/api"
"github.com/urfave/negroni"
)

const (
version = "v2"
prefix = "/" + version
)

var (
restLogger = log.WithField("_module", "_mgmt-rest-v2")
protocolPrefix = "http"
)

type apiV2 struct {
metricManager api.Metrics
taskManager api.Tasks
configManager api.Config

wg *sync.WaitGroup
killChan chan struct{}
}

func New(wg *sync.WaitGroup, killChan chan struct{}, protocol string) *apiV2 {
protocolPrefix = protocol
return &apiV2{wg: wg, killChan: killChan}
}

func (s *apiV2) GetRoutes() []api.Route {
routes := []api.Route{
// plugin routes
api.Route{Method: "GET", Path: prefix + "/plugins", Handle: s.getPlugins},
api.Route{Method: "GET", Path: prefix + "/plugins/:type/:name/:version", Handle: s.getPlugin},
api.Route{Method: "POST", Path: prefix + "/plugins", Handle: s.loadPlugin},
api.Route{Method: "DELETE", Path: prefix + "/plugins/:type/:name/:version", Handle: s.unloadPlugin},

api.Route{Method: "GET", Path: prefix + "/plugins/:type/:name/:version/config", Handle: s.getPluginConfigItem},
api.Route{Method: "PUT", Path: prefix + "/plugins/:type/:name/:version/config", Handle: s.setPluginConfigItem},
api.Route{Method: "DELETE", Path: prefix + "/plugins/:type/:name/:version/config", Handle: s.deletePluginConfigItem},

// metric routes
api.Route{Method: "GET", Path: prefix + "/metrics", Handle: s.getMetrics},

// task routes
api.Route{Method: "GET", Path: prefix + "/tasks", Handle: s.getTasks},
api.Route{Method: "GET", Path: prefix + "/tasks/:id", Handle: s.getTask},
api.Route{Method: "GET", Path: prefix + "/tasks/:id/watch", Handle: s.watchTask},
api.Route{Method: "POST", Path: prefix + "/tasks", Handle: s.addTask},
api.Route{Method: "PUT", Path: prefix + "/tasks/:id", Handle: s.updateTaskState},
api.Route{Method: "DELETE", Path: prefix + "/tasks/:id", Handle: s.removeTask},
}
return routes
}

func (s *apiV2) BindMetricManager(metricManager api.Metrics) {
s.metricManager = metricManager
}

func (s *apiV2) BindTaskManager(taskManager api.Tasks) {
s.taskManager = taskManager
}

func (s *apiV2) BindTribeManager(tribeManager api.Tribe) {}

func (s *apiV2) BindConfigManager(configManager api.Config) {
s.configManager = configManager
}

func Write(code int, body interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; version=2; charset=utf-8")
w.Header().Set("Version", "beta")

if !w.(negroni.ResponseWriter).Written() {
w.WriteHeader(code)
}

if body != nil {
e := json.NewEncoder(w)
e.SetIndent("", " ")
e.SetEscapeHTML(false)
err := e.Encode(body)
if err != nil {
restLogger.Fatalln(err)
}
}
}
161 changes: 161 additions & 0 deletions mgmt/rest/v2/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
http://www.apache.org/licenses/LICENSE-2.0.txt
Copyright 2017 Intel Corporation
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 v2

import (
"net/http"
"strconv"

"github.com/intelsdi-x/snap/control/plugin/cpolicy"
"github.com/intelsdi-x/snap/core"
"github.com/intelsdi-x/snap/core/cdata"
"github.com/julienschmidt/httprouter"
)

type PolicyTable cpolicy.RuleTable

type PolicyTableSlice []cpolicy.RuleTable

// cdata.ConfigDataNode implements it's own UnmarshalJSON
type PluginConfigItem struct {
cdata.ConfigDataNode
}

func (s *apiV2) getPluginConfigItem(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var err error
styp := p.ByName("type")
if styp == "" {
cdn := s.configManager.GetPluginConfigDataNodeAll()
item := &PluginConfigItem{ConfigDataNode: cdn}
Write(200, item, w)
return
}

typ, err := getPluginType(styp)
if err != nil {
Write(400, FromError(err), w)
return
}

name := p.ByName("name")
sver := p.ByName("version")
iver := -2
if sver != "" {
if iver, err = strconv.Atoi(sver); err != nil {
Write(400, FromError(err), w)
return
}
}

cdn := s.configManager.GetPluginConfigDataNode(typ, name, iver)
item := &PluginConfigItem{ConfigDataNode: cdn}
Write(200, item, w)
}

func (s *apiV2) deletePluginConfigItem(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var err error
var typ core.PluginType
styp := p.ByName("type")
if styp != "" {
typ, err = getPluginType(styp)
if err != nil {
Write(400, FromError(err), w)
return
}
}

name := p.ByName("name")
sver := p.ByName("version")
iver := -2
if sver != "" {
if iver, err = strconv.Atoi(sver); err != nil {
Write(400, FromError(err), w)
return
}
}

src := []string{}
errCode, err := core.UnmarshalBody(&src, r.Body)
if errCode != 0 && err != nil {
Write(400, FromError(err), w)
return
}

var res cdata.ConfigDataNode
if styp == "" {
res = s.configManager.DeletePluginConfigDataNodeFieldAll(src...)
} else {
res = s.configManager.DeletePluginConfigDataNodeField(typ, name, iver, src...)
}

item := &PluginConfigItem{ConfigDataNode: res}
Write(200, item, w)
}

func (s *apiV2) setPluginConfigItem(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var err error
var typ core.PluginType
styp := p.ByName("type")
if styp != "" {
typ, err = getPluginType(styp)
if err != nil {
Write(400, FromError(err), w)
return
}
}

name := p.ByName("name")
sver := p.ByName("version")
iver := -2
if sver != "" {
if iver, err = strconv.Atoi(sver); err != nil {
Write(400, FromError(err), w)
return
}
}

src := cdata.NewNode()
errCode, err := core.UnmarshalBody(src, r.Body)
if errCode != 0 && err != nil {
Write(400, FromError(err), w)
return
}

var res cdata.ConfigDataNode
if styp == "" {
res = s.configManager.MergePluginConfigDataNodeAll(src)
} else {
res = s.configManager.MergePluginConfigDataNode(typ, name, iver, src)
}

item := &PluginConfigItem{ConfigDataNode: res}
Write(200, item, w)
}

func getPluginType(t string) (core.PluginType, error) {
if ityp, err := strconv.Atoi(t); err == nil {
return core.PluginType(ityp), nil
}
ityp, err := core.ToPluginType(t)
if err != nil {
return core.PluginType(-1), err
}
return ityp, nil
}
76 changes: 76 additions & 0 deletions mgmt/rest/v2/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
http://www.apache.org/licenses/LICENSE-2.0.txt
Copyright 2015 Intel Corporation
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 v2

import (
"fmt"

"errors"

"github.com/intelsdi-x/snap/core/serror"
)

const (
ErrPluginAlreadyLoaded = "plugin is already loaded"
ErrTaskNotFound = "task not found"
ErrTaskDisabledNotRunnable = "task is disabled"
)

var (
ErrPluginNotFound = errors.New("plugin not found")
ErrStreamingUnsupported = errors.New("streaming unsupported")
ErrNoActionSpecified = errors.New("no action was specified in the request")
ErrWrongAction = errors.New("wrong action requested")
)

// Unsuccessful generic response to a failed API call
type Error struct {
ErrorMessage string `json:"message"`
Fields map[string]string `json:"fields"`
}

func FromSnapError(pe serror.SnapError) *Error {
e := &Error{ErrorMessage: pe.Error(), Fields: make(map[string]string)}
// Convert into string format
for k, v := range pe.Fields() {
e.Fields[k] = fmt.Sprint(v)
}
return e
}

func FromSnapErrors(errs []serror.SnapError) *Error {
fields := make(map[string]string)
var msg string
for i, err := range errs {
for k, v := range err.Fields() {
fields[fmt.Sprintf("%s_err_%d", k, i)] = fmt.Sprint(v)
}
msg = msg + fmt.Sprintf("error %d: %s ", i, err.Error())
}
return &Error{
ErrorMessage: msg,
Fields: fields,
}
}

func FromError(err error) *Error {
e := &Error{ErrorMessage: err.Error(), Fields: make(map[string]string)}
return e
}
Loading

0 comments on commit e741f41

Please sign in to comment.