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.