You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2018/03/09 16:27:29 UTC
[incubator-trafficcontrol] 02/04: implement generic CRUD interfaces
for server api endpoints
This is an automated email from the ASF dual-hosted git repository.
dangogh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git
commit ae2f26011d5e3ed79dfdce97a322c556a7a71080
Author: Dylan Volz <Dy...@comcast.com>
AuthorDate: Wed Feb 7 18:44:53 2018 -0700
implement generic CRUD interfaces for server api endpoints
---
traffic_ops/traffic_ops_golang/routes.go | 14 +-
traffic_ops/traffic_ops_golang/server/servers.go | 353 ++++++++++++++++++---
.../server/servers_assignment.go | 4 +-
.../server/servers_assignment_test.go | 2 +-
.../traffic_ops_golang/server/servers_test.go | 2 +-
.../server/servers_update_status.go | 4 +-
.../server/servers_update_status_test.go | 2 +-
7 files changed, 325 insertions(+), 56 deletions(-)
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index b5ccaf0..6628f59 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -42,6 +42,8 @@ import (
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/status"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/types"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/server"
+
"github.com/basho/riak-go-client"
)
@@ -159,10 +161,14 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{1.2, http.MethodGet, `ping$`, ping.PingHandler(), auth.PrivLevelReadOnly, Authenticated, nil},
//Servers
- {1.2, http.MethodGet, `servers/?(\.json)?$`, serversHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
- {1.2, http.MethodGet, `servers/{id}$`, serversHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
- {1.2, http.MethodPost, `servers/{id}/deliveryservices$`, assignDeliveryServicesToServerHandler(d.DB), auth.PrivLevelOperations, Authenticated, nil},
- {1.2, http.MethodGet, `servers/{host_name}/update_status$`, getServerUpdateStatusHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.2, http.MethodGet, `servers/?(\.json)?$`, api.ReadHandler(server.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.2, http.MethodGet, `servers/{id}$`, api.ReadHandler(server.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.2, http.MethodPut, `servers/{id}$`, api.UpdateHandler(server.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+ {1.2, http.MethodPost, `servers/?$`, api.CreateHandler(server.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+ {1.2, http.MethodDelete, `servers/{id}$`, api.DeleteHandler(server.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
+ {1.2, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler(d.DB), auth.PrivLevelOperations, Authenticated, nil},
+ {1.2, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
//SSLKeys deliveryservice endpoints here that are marked marked as '-wip' need to have tenancy checks added
{1.2, http.MethodGet, `deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, getDeliveryServiceSSLKeysByXMLIDHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go
index 64e3526..cf7a0d7 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -1,4 +1,4 @@
-package main
+package server
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -20,78 +20,74 @@ package main
*/
import (
- "encoding/json"
"errors"
"fmt"
- "net/http"
"strconv"
"github.com/apache/incubator-trafficcontrol/lib/go-log"
"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
- "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/jmoiron/sqlx"
+ "github.com/lib/pq"
)
-func serversHandler(db *sqlx.DB) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- handleErrs := tc.GetHandleErrorsFunc(w, r)
+//we need a type alias to define functions on
+type TOServer tc.Server
- // p PathParams, username string, privLevel int
- ctx := r.Context()
- user, err := auth.GetCurrentUser(ctx)
- if err != nil {
- handleErrs(http.StatusInternalServerError, err)
- return
- }
- privLevel := user.PrivLevel
+//the refType is passed into the handlers where a copy of its type is used to decode the json.
+var refType = TOServer(tc.Server{})
- params, err := api.GetCombinedParams(r)
- if err != nil {
- log.Errorf("unable to get parameters from request: %s", err)
- handleErrs(http.StatusInternalServerError, err)
- }
+func GetRefType() *TOServer {
+ return &refType
+}
- resp, errs, errType := getServersResponse(params, db, privLevel)
- if len(errs) > 0 {
- for _, err := range errs {
- if err.Error() == `id cannot parse to integer` {
- handleErrs(http.StatusNotFound, errors.New("Resource not found.")) //matches perl response
- return
- }
- }
- tc.HandleErrorsWithType(errs, errType, handleErrs)
- return
- }
+//Implementation of the Identifier, Validator interface functions
+func (server *TOServer) GetID() int {
+ return server.ID
+}
- respBts, err := json.Marshal(resp)
- if err != nil {
- log.Errorln("marshaling response %v", err)
- handleErrs(http.StatusInternalServerError, err)
- return
- }
+func (server *TOServer) GetAuditName() string {
+ return server.DomainName
+}
- w.Header().Set(tc.ContentType, tc.ApplicationJson)
- fmt.Fprintf(w, "%s", respBts)
- }
+func (server *TOServer) GetType() string {
+ return "server"
}
-func getServersResponse(params map[string]string, db *sqlx.DB, privLevel int) (*tc.ServersResponse, []error, tc.ApiErrorType) {
+func (server *TOServer) SetID(i int) {
+ server.ID = i
+}
+
+func (server *TOServer) Validate(db *sqlx.DB) []error {
+ return nil
+}
+
+func (server *TOServer) Read(db *sqlx.DB, params map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+ returnable := []interface{}{}
+
+ privLevel := user.PrivLevel
+
servers, errs, errType := getServers(params, db, privLevel)
if len(errs) > 0 {
+ for _, err := range errs {
+ if err.Error() == `id cannot parse to integer` {
+ return nil, []error{errors.New("Resource not found.")}, tc.DataMissingError //matches perl response
+ }
+ }
return nil, errs, errType
}
- resp := tc.ServersResponse{
- Response: servers,
+ for _, server := range servers {
+ returnable = append(returnable, server)
}
- return &resp, nil, tc.NoError
+
+ return returnable, nil, tc.NoError
}
func getServers(params map[string]string, db *sqlx.DB, privLevel int) ([]tc.Server, []error, tc.ApiErrorType) {
-
var rows *sqlx.Rows
var err error
@@ -202,3 +198,270 @@ JOIN type t ON s.type = t.id`
return selectStmt
}
+
+//The TOServer implementation of the Updater interface
+//all implementations of Updater should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a cdn with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+func (server *TOServer) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ tx, err := db.Beginx()
+ defer func() {
+ if tx == nil {
+ return
+ }
+ if err != nil {
+ tx.Rollback()
+ return
+ }
+ tx.Commit()
+ }()
+
+ if err != nil {
+ log.Error.Printf("could not begin transaction: %v", err)
+ return tc.DBError, tc.SystemError
+ }
+
+ log.Debugf("about to run exec query: %s with server: %++v", updateServerQuery(), server)
+ resultRows, err := tx.NamedQuery(updateServerQuery(), server)
+ if err != nil {
+ if pqerr, ok := err.(*pq.Error); ok {
+ err, eType := dbhelpers.ParsePQUniqueConstraintError(pqerr)
+ if eType == tc.DataConflictError {
+ return errors.New("a server with " + err.Error()), eType
+ }
+ return err, eType
+ } else {
+ log.Errorf("received error: %++v from update execution", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ var lastUpdated tc.Time
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err := resultRows.Scan(&lastUpdated); err != nil {
+ log.Error.Printf("could not scan lastUpdated from insert: %s\n", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ log.Debugf("lastUpdated: %++v", lastUpdated)
+ server.LastUpdated = lastUpdated
+ if rowsAffected != 1 {
+ if rowsAffected < 1 {
+ return errors.New("no cdn found with this id"), tc.DataMissingError
+ } else {
+ return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
+ }
+ }
+ return nil, tc.NoError
+}
+
+func updateServerQuery() string {
+ query := `UPDATE
+server SET
+cachegroup=:cachegroup_id,
+cdn_id=:cdn_id,
+domain_name=:domain_name,
+host_name=:host_name,
+https_port=:https_port,
+ilo_ip_address=:ilo_ip_address,
+ilo_ip_netmask=:ilo_ip_netmask,
+ilo_ip_gateway=:ilo_ip_gateway,
+ilo_username=:ilo_username,
+ilo_password=:ilo_password,
+interface_mtu=:interface_mtu,
+interface_name=:interface_name,
+ip6_address=:ip6_address,
+ip6_gateway=:ip6_gateway,
+ip_address=:ip_address,
+ip_netmask=:ip_netmask,
+ip_gateway=:ip_gateway,
+mgmt_ip_address=:mgmt_ip_address,
+mgmt_ip_netmask=:mgmt_ip_netmask,
+mgmt_ip_gateway=:mgmt_ip_gateway,
+offline_reason=:offline_reason,
+phys_location=:phys_location_id,
+profile=:profile_id,
+rack=:rack,
+router_host_name=:router_host_name,
+router_port_name=:router_port_name,
+status=:status_id,
+tcp_port=:tcp_port,
+type=:server_type_id,
+upd_pending=:upd_pending
+WHERE id=:id RETURNING last_updated`
+ return query
+}
+
+//The TOServer implementation of the Inserter interface
+//all implementations of Inserter should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a server with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+//The insert sql returns the id and lastUpdated values of the newly inserted server and have
+//to be added to the struct
+func (server *TOServer) Insert(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ tx, err := db.Beginx()
+ defer func() {
+ if tx == nil {
+ return
+ }
+ if err != nil {
+ tx.Rollback()
+ return
+ }
+ tx.Commit()
+ }()
+
+ if err != nil {
+ log.Error.Printf("could not begin transaction: %v", err)
+ return tc.DBError, tc.SystemError
+ }
+ if server.XMPPID == "" {
+ server.XMPPID = server.HostName
+ }
+
+ resultRows, err := tx.NamedQuery(insertServerQuery(), server)
+ if err != nil {
+ if err, ok := err.(*pq.Error); ok {
+ err, eType := dbhelpers.ParsePQUniqueConstraintError(err)
+ return errors.New("a server with " + err.Error()), eType
+ } else {
+ log.Errorf("received non pq error: %++v from create execution", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ var id int
+ var lastUpdated tc.Time
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err := resultRows.Scan(&id, &lastUpdated); err != nil {
+ log.Error.Printf("could not scan id from insert: %s\n", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ if rowsAffected == 0 {
+ err = errors.New("no server was inserted, no id was returned")
+ log.Errorln(err)
+ return tc.DBError, tc.SystemError
+ } else if rowsAffected > 1 {
+ err = errors.New("too many ids returned from server insert")
+ log.Errorln(err)
+ return tc.DBError, tc.SystemError
+ }
+ server.SetID(id)
+ server.LastUpdated = lastUpdated
+ return nil, tc.NoError
+}
+
+func insertServerQuery() string {
+ query := `INSERT INTO server (
+cachegroup,
+cdn_id,
+domain_name,
+host_name,
+https_port,
+ilo_ip_address,
+ilo_ip_netmask,
+ilo_ip_gateway,
+ilo_username,
+ilo_password,
+interface_mtu,
+interface_name,
+ip6_address,
+ip6_gateway,
+ip_address,
+ip_netmask,
+ip_gateway,
+mgmt_ip_address,
+mgmt_ip_netmask,
+mgmt_ip_gateway,
+offline_reason,
+phys_location,
+profile,
+rack,
+router_host_name,
+router_port_name,
+status,
+tcp_port,
+type,
+upd_pending) VALUES (
+:cachegroup_id,
+:cdn_id,
+:domain_name,
+:host_name,
+:https_port,
+:ilo_ip_address,
+:ilo_ip_netmask,
+:ilo_ip_gateway,
+:ilo_username,
+:ilo_password,
+:interface_mtu,
+:interface_name,
+:ip6_address,
+:ip6_gateway,
+:ip_address,
+:ip_netmask,
+:ip_gateway,
+:mgmt_ip_address,
+:mgmt_ip_netmask,
+:mgmt_ip_gateway,
+:offline_reason,
+:phys_location_id,
+:profile_id,
+:rack,
+:router_host_name,
+:router_port_name,
+:status_id,
+:tcp_port,
+:server_type_id,
+:upd_pending) RETURNING id,last_updated`
+ return query
+}
+
+//The Server implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper errorType
+func (server *TOServer) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ tx, err := db.Beginx()
+ defer func() {
+ if tx == nil {
+ return
+ }
+ if err != nil {
+ tx.Rollback()
+ return
+ }
+ tx.Commit()
+ }()
+
+ if err != nil {
+ log.Error.Printf("could not begin transaction: %v", err)
+ return tc.DBError, tc.SystemError
+ }
+ log.Debugf("about to run exec query: %s with server: %++v", deleteServerQuery(), server)
+ result, err := tx.NamedExec(deleteServerQuery(), server)
+ if err != nil {
+ log.Errorf("received error: %++v from delete execution", err)
+ return tc.DBError, tc.SystemError
+ }
+ rowsAffected, err := result.RowsAffected()
+ if err != nil {
+ return tc.DBError, tc.SystemError
+ }
+ if rowsAffected != 1 {
+ if rowsAffected < 1 {
+ return errors.New("no server with that id found"), tc.DataMissingError
+ } else {
+ return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
+ }
+ }
+ return nil, tc.NoError
+}
+
+func deleteServerQuery() string {
+ query := `DELETE FROM server
+WHERE id=:id`
+ return query
+}
diff --git a/traffic_ops/traffic_ops_golang/server/servers_assignment.go b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
index b04883c..5655e4d 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_assignment.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
@@ -1,4 +1,4 @@
-package main
+package server
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -35,7 +35,7 @@ import (
"github.com/lib/pq"
)
-func assignDeliveryServicesToServerHandler(db *sqlx.DB) http.HandlerFunc {
+func AssignDeliveryServicesToServerHandler(db *sqlx.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handleErrs := tc.GetHandleErrorsFunc(w, r)
diff --git a/traffic_ops/traffic_ops_golang/server/servers_assignment_test.go b/traffic_ops/traffic_ops_golang/server/servers_assignment_test.go
index 803c801..55a8757 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_assignment_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_assignment_test.go
@@ -1,4 +1,4 @@
-package main
+package server
/*
* Licensed to the Apache Software Foundation (ASF) under one
diff --git a/traffic_ops/traffic_ops_golang/server/servers_test.go b/traffic_ops/traffic_ops_golang/server/servers_test.go
index 012b0d5..3a8018c 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_test.go
@@ -1,4 +1,4 @@
-package main
+package server
/*
* Licensed to the Apache Software Foundation (ASF) under one
diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status.go b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
index b0282bd..80e00ff 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_update_status.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
@@ -1,4 +1,4 @@
-package main
+package server
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -31,7 +31,7 @@ import (
"github.com/jmoiron/sqlx"
)
-func getServerUpdateStatusHandler(db *sqlx.DB) http.HandlerFunc {
+func GetServerUpdateStatusHandler(db *sqlx.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handleErrs := tc.GetHandleErrorsFunc(w, r)
diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
index a4c9a03..c72e2d7 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
@@ -1,4 +1,4 @@
-package main
+package server
/*
* Licensed to the Apache Software Foundation (ASF) under one
--
To stop receiving notification emails like this one, please contact
dangogh@apache.org.