You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/03/09 16:27:27 UTC

[GitHub] dangogh closed pull request #1861: implement servers CRUD endpoints in go

dangogh closed pull request #1861: implement servers CRUD endpoints in go
URL: https://github.com/apache/incubator-trafficcontrol/pull/1861
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go
index 123be1454..7c58206b6 100644
--- a/lib/go-tc/servers.go
+++ b/lib/go-tc/servers.go
@@ -79,6 +79,55 @@ type Server struct {
 	XMPPPasswd       string              `json:"xmppPasswd" db:"xmpp_passwd"`
 }
 
+type ServerNullable struct {
+	Cachegroup       *string              `json:"cachegroup" db:"cachegroup"`
+	CachegroupID     *int                 `json:"cachegroupId" db:"cachegroup_id"`
+	CDNID            *int                 `json:"cdnId" db:"cdn_id"`
+	CDNName          *string              `json:"cdnName" db:"cdn_name"`
+	DeliveryServices *map[string][]string `json:"deliveryServices,omitempty"`
+	DomainName       *string              `json:"domainName" db:"domain_name"`
+	FQDN             *string             `json:"fqdn,omitempty"`
+	FqdnTime         time.Time           `json:"-"`
+	GUID             *string              `json:"guid" db:"guid"`
+	HostName         *string              `json:"hostName" db:"host_name"`
+	HTTPSPort        *int                 `json:"httpsPort" db:"https_port"`
+	ID               *int                 `json:"id" db:"id"`
+	ILOIPAddress     *string              `json:"iloIpAddress" db:"ilo_ip_address"`
+	ILOIPGateway     *string              `json:"iloIpGateway" db:"ilo_ip_gateway"`
+	ILOIPNetmask     *string              `json:"iloIpNetmask" db:"ilo_ip_netmask"`
+	ILOPassword      *string              `json:"iloPassword" db:"ilo_password"`
+	ILOUsername      *string              `json:"iloUsername" db:"ilo_username"`
+	InterfaceMtu     *int                 `json:"interfaceMtu" db:"interface_mtu"`
+	InterfaceName    *string              `json:"interfaceName" db:"interface_name"`
+	IP6Address       *string              `json:"ip6Address" db:"ip6_address"`
+	IP6Gateway       *string              `json:"ip6Gateway" db:"ip6_gateway"`
+	IPAddress        *string              `json:"ipAddress" db:"ip_address"`
+	IPGateway        *string              `json:"ipGateway" db:"ip_gateway"`
+	IPNetmask        *string              `json:"ipNetmask" db:"ip_netmask"`
+	LastUpdated      TimeNoMod            `json:"lastUpdated" db:"last_updated"`
+	MgmtIPAddress    *string              `json:"mgmtIpAddress" db:"mgmt_ip_address"`
+	MgmtIPGateway    *string              `json:"mgmtIpGateway" db:"mgmt_ip_gateway"`
+	MgmtIPNetmask    *string              `json:"mgmtIpNetmask" db:"mgmt_ip_netmask"`
+	OfflineReason    *string              `json:"offlineReason" db:"offline_reason"`
+	PhysLocation     *string              `json:"physLocation" db:"phys_location"`
+	PhysLocationID   *int                 `json:"physLocationId" db:"phys_location_id"`
+	Profile          *string              `json:"profile" db:"profile"`
+	ProfileDesc      *string              `json:"profileDesc" db:"profile_desc"`
+	ProfileID        *int                 `json:"profileId" db:"profile_id"`
+	Rack             *string              `json:"rack" db:"rack"`
+	RevalPending     *bool                `json:"revalPending" db:"reval_pending"`
+	RouterHostName   *string              `json:"routerHostName" db:"router_host_name"`
+	RouterPortName   *string              `json:"routerPortName" db:"router_port_name"`
+	Status           *string              `json:"status" db:"status"`
+	StatusID         *int                 `json:"statusId" db:"status_id"`
+	TCPPort          *int                 `json:"tcpPort" db:"tcp_port"`
+	Type             string              `json:"type" db:"server_type"`
+	TypeID           *int                 `json:"typeId" db:"server_type_id"`
+	UpdPending       *bool                `json:"updPending" db:"upd_pending"`
+	XMPPID           *string              `json:"xmppId" db:"xmpp_id"`
+	XMPPPasswd       *string              `json:"xmppPasswd" db:"xmpp_passwd"`
+}
+
 type ServerUpdateStatus struct {
 	HostName           string `json:"host_name"`
 	UpdatePending      bool   `json:"upd_pending"`
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 7c7df779f..194273335 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -38,9 +38,11 @@ import (
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/physlocation"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/ping"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/region"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/server"
 	"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/basho/riak-go-client"
 )
 
@@ -154,10 +156,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
new file mode 100644
index 000000000..bbd2ec4db
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -0,0 +1,566 @@
+package server
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import (
+	"errors"
+	"fmt"
+	"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/tovalidate"
+
+	validation "github.com/go-ozzo/ozzo-validation"
+	"github.com/go-ozzo/ozzo-validation/is"
+	"github.com/jmoiron/sqlx"
+	"github.com/lib/pq"
+)
+
+//we need a type alias to define functions on
+type TOServer tc.ServerNullable
+
+//the refType is passed into the handlers where a copy of its type is used to decode the json.
+var refType = TOServer(tc.ServerNullable{})
+
+func GetRefType() *TOServer {
+	return &refType
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (server *TOServer) GetID() (int, bool) {
+	if server.ID == nil {
+		return 0, false
+	}
+	return *server.ID, true
+}
+
+func (server *TOServer) GetAuditName() string {
+	if server.DomainName != nil {
+		return *server.DomainName
+	}
+	id, _ := server.GetID()
+	return strconv.Itoa(id)
+}
+
+func (server *TOServer) GetType() string {
+	return "server"
+}
+
+func (server *TOServer) SetID(i int) {
+	server.ID = &i
+}
+
+func (server *TOServer) Validate(db *sqlx.DB) []error {
+
+	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
+
+	validateErrs := validation.Errors{
+		"cachegroupId":   validation.Validate(server.CachegroupID, validation.NotNil),
+		"cdnId":          validation.Validate(server.CDNID, validation.NotNil),
+		"domainName":     validation.Validate(server.DomainName, validation.NotNil, noSpaces),
+		"hostName":       validation.Validate(server.HostName, validation.NotNil, noSpaces),
+		"httpsPort":      validation.Validate(server.HTTPSPort, validation.NotNil),
+		"interfaceMtu":   validation.Validate(server.InterfaceMtu, validation.NotNil),
+		"interfaceName":  validation.Validate(server.InterfaceName, validation.NotNil),
+		"ipAddress":      validation.Validate(server.IPAddress, validation.NotNil, is.IPv4),
+		"ipNetmask":      validation.Validate(server.IPNetmask, validation.NotNil),
+		"ipGateway":      validation.Validate(server.IPGateway, validation.NotNil),
+		"physLocationId": validation.Validate(server.PhysLocationID, validation.NotNil),
+		"profileId":      validation.Validate(server.ProfileID, validation.NotNil),
+		"statusId":       validation.Validate(server.StatusID, validation.NotNil),
+		"tcpPort":        validation.Validate(server.TCPPort, validation.NotNil),
+		"typeId":         validation.Validate(server.TypeID, validation.NotNil),
+		"updPending":     validation.Validate(server.UpdPending, validation.NotNil),
+	}
+	errs := tovalidate.ToErrors(validateErrs)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	rows, err := db.Query("select use_in_table from type where id=$1", server.TypeID)
+	if err != nil {
+		log.Error.Printf("could not execute select use_in_table from type: %s\n", err)
+		errs = append(errs, tc.DBError)
+		return errs
+	}
+	defer rows.Close()
+	var useInTable string
+	for rows.Next() {
+		if err := rows.Scan(&useInTable); err != nil {
+			log.Error.Printf("could not scan use_in_table from type: %s\n", err)
+			errs = append(errs, tc.DBError)
+			return errs
+		}
+	}
+	if useInTable != "server" {
+		errs = append(errs, errors.New("invalid server type"))
+	}
+
+	rows, err = db.Query("select cdn from profile where id=$1", server.ProfileID)
+	if err != nil {
+		log.Error.Printf("could not execute select cdnID from profile: %s\n", err)
+		errs = append(errs, tc.DBError)
+		return errs
+	}
+	defer rows.Close()
+	var cdnID int
+	for rows.Next() {
+		if err := rows.Scan(&cdnID); err != nil {
+			log.Error.Printf("could not scan cdnID from profile: %s\n", err)
+			errs = append(errs, tc.DBError)
+			return errs
+		}
+	}
+	log.Infof("got cdn id: %d from profile and cdn id: %d from server", cdnID, *server.CDNID)
+	if cdnID != *server.CDNID {
+		errs = append(errs, errors.New("CDN of profile does not match Server CDN"))
+	}
+	return errs
+}
+
+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
+	}
+
+	for _, server := range servers {
+		returnable = append(returnable, server)
+	}
+
+	return returnable, nil, tc.NoError
+}
+
+func getServers(params map[string]string, db *sqlx.DB, privLevel int) ([]tc.ServerNullable, []error, tc.ApiErrorType) {
+	var rows *sqlx.Rows
+	var err error
+
+	// Query Parameters to Database Query column mappings
+	// see the fields mapped in the SQL query
+	queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{
+		"cachegroup":   dbhelpers.WhereColumnInfo{"s.cachegroup", api.IsInt},
+		"cdn":          dbhelpers.WhereColumnInfo{"s.cdn_id", api.IsInt},
+		"id":           dbhelpers.WhereColumnInfo{"s.id", api.IsInt},
+		"hostName":     dbhelpers.WhereColumnInfo{"s.host_name", nil},
+		"physLocation": dbhelpers.WhereColumnInfo{"s.phys_location", api.IsInt},
+		"profileId":    dbhelpers.WhereColumnInfo{"s.profile", api.IsInt},
+		"status":       dbhelpers.WhereColumnInfo{"st.name", nil},
+		"type":         dbhelpers.WhereColumnInfo{"t.name", nil},
+	}
+
+	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	if len(errs) > 0 {
+		return nil, errs, tc.DataConflictError
+	}
+
+	query := selectQuery() + where + orderBy
+	log.Debugln("Query is ", query)
+
+	rows, err = db.NamedQuery(query, queryValues)
+	if err != nil {
+		return nil, []error{fmt.Errorf("querying: %v", err)}, tc.SystemError
+	}
+	defer rows.Close()
+
+	servers := []tc.ServerNullable{}
+
+	HiddenField := "********"
+
+	for rows.Next() {
+		var s tc.ServerNullable
+		if err = rows.StructScan(&s); err != nil {
+			return nil, []error{fmt.Errorf("getting servers: %v", err)}, tc.SystemError
+		}
+		if privLevel < auth.PrivLevelAdmin {
+			s.ILOPassword = &HiddenField
+			s.XMPPPasswd = &HiddenField
+		}
+		servers = append(servers, s)
+	}
+	return servers, nil, tc.NoError
+}
+
+func selectQuery() string {
+
+	const JumboFrameBPS = 9000
+
+	// COALESCE is needed to default values that are nil in the database
+	// because Go does not allow that to marshal into the struct
+	selectStmt := `SELECT
+cg.name as cachegroup,
+s.cachegroup as cachegroup_id,
+s.cdn_id,
+cdn.name as cdn_name,
+s.domain_name,
+s.guid,
+s.host_name,
+s.https_port,
+s.id,
+s.ilo_ip_address,
+s.ilo_ip_gateway,
+s.ilo_ip_netmask,
+s.ilo_password,
+s.ilo_username,
+COALESCE(s.interface_mtu, ` + strconv.Itoa(JumboFrameBPS) + `) as interface_mtu,
+s.interface_name,
+s.ip6_address,
+s.ip6_gateway,
+s.ip_address,
+s.ip_gateway,
+s.ip_netmask,
+s.last_updated,
+s.mgmt_ip_address,
+s.mgmt_ip_gateway,
+s.mgmt_ip_netmask,
+s.offline_reason,
+pl.name as phys_location,
+s.phys_location as phys_location_id,
+p.name as profile,
+p.description as profile_desc,
+s.profile as profile_id,
+s.rack,
+s.reval_pending,
+s.router_host_name,
+s.router_port_name,
+st.name as status,
+s.status as status_id,
+s.tcp_port,
+t.name as server_type,
+s.type as server_type_id,
+s.upd_pending as upd_pending,
+s.xmpp_id,
+s.xmpp_passwd
+
+FROM server s
+
+JOIN cachegroup cg ON s.cachegroup = cg.id
+JOIN cdn cdn ON s.cdn_id = cdn.id
+JOIN phys_location pl ON s.phys_location = pl.id
+JOIN profile p ON s.profile = p.id
+JOIN status st ON s.status = st.id
+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) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	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", updateQuery(), server)
+	resultRows, err := tx.NamedQuery(updateQuery(), 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
+		}
+	}
+	defer resultRows.Close()
+
+	var lastUpdated tc.TimeNoMod
+	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
+		}
+	}
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+func updateQuery() 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) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+	if server.XMPPID == nil || *server.XMPPID == "" {
+		server.XMPPID = server.HostName
+	}
+
+	resultRows, err := tx.NamedQuery(insertQuery(), 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 non pq error: %++v from create execution", err)
+			return tc.DBError, tc.SystemError
+		}
+	}
+	defer resultRows.Close()
+
+	var id int
+	var lastUpdated tc.TimeNoMod
+	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
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+func insertQuery() 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) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	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
+		}
+	}
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+func deleteServerQuery() string {
+	query := `DELETE FROM server
+WHERE id=:id`
+	return query
+}
diff --git a/traffic_ops/traffic_ops_golang/servers_assignment.go b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
similarity index 99%
rename from traffic_ops/traffic_ops_golang/servers_assignment.go
rename to traffic_ops/traffic_ops_golang/server/servers_assignment.go
index b04883cd6..5655e4d74 100644
--- a/traffic_ops/traffic_ops_golang/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/servers_assignment_test.go b/traffic_ops/traffic_ops_golang/server/servers_assignment_test.go
similarity index 99%
rename from traffic_ops/traffic_ops_golang/servers_assignment_test.go
rename to traffic_ops/traffic_ops_golang/server/servers_assignment_test.go
index 803c801b8..55a8757dc 100644
--- a/traffic_ops/traffic_ops_golang/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/servers_test.go b/traffic_ops/traffic_ops_golang/server/servers_test.go
similarity index 99%
rename from traffic_ops/traffic_ops_golang/servers_test.go
rename to traffic_ops/traffic_ops_golang/server/servers_test.go
index 012b0d51e..3a8018c3a 100644
--- a/traffic_ops/traffic_ops_golang/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/servers_update_status.go b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
similarity index 98%
rename from traffic_ops/traffic_ops_golang/servers_update_status.go
rename to traffic_ops/traffic_ops_golang/server/servers_update_status.go
index b0282bdd5..80e00ff43 100644
--- a/traffic_ops/traffic_ops_golang/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/servers_update_status_test.go b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
similarity index 99%
rename from traffic_ops/traffic_ops_golang/servers_update_status_test.go
rename to traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
index a4c9a0322..c72e2d725 100644
--- a/traffic_ops/traffic_ops_golang/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
diff --git a/traffic_ops/traffic_ops_golang/servers.go b/traffic_ops/traffic_ops_golang/servers.go
deleted file mode 100644
index 64e352626..000000000
--- a/traffic_ops/traffic_ops_golang/servers.go
+++ /dev/null
@@ -1,204 +0,0 @@
-package main
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-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/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"
-)
-
-func serversHandler(db *sqlx.DB) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		handleErrs := tc.GetHandleErrorsFunc(w, r)
-
-		// 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
-
-		params, err := api.GetCombinedParams(r)
-		if err != nil {
-			log.Errorf("unable to get parameters from request: %s", err)
-			handleErrs(http.StatusInternalServerError, err)
-		}
-
-		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
-		}
-
-		respBts, err := json.Marshal(resp)
-		if err != nil {
-			log.Errorln("marshaling response %v", err)
-			handleErrs(http.StatusInternalServerError, err)
-			return
-		}
-
-		w.Header().Set(tc.ContentType, tc.ApplicationJson)
-		fmt.Fprintf(w, "%s", respBts)
-	}
-}
-
-func getServersResponse(params map[string]string, db *sqlx.DB, privLevel int) (*tc.ServersResponse, []error, tc.ApiErrorType) {
-	servers, errs, errType := getServers(params, db, privLevel)
-	if len(errs) > 0 {
-		return nil, errs, errType
-	}
-
-	resp := tc.ServersResponse{
-		Response: servers,
-	}
-	return &resp, 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
-
-	// Query Parameters to Database Query column mappings
-	// see the fields mapped in the SQL query
-	queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{
-		"cachegroup":   dbhelpers.WhereColumnInfo{"s.cachegroup", api.IsInt},
-		"cdn":          dbhelpers.WhereColumnInfo{"s.cdn_id", api.IsInt},
-		"id":           dbhelpers.WhereColumnInfo{"s.id", api.IsInt},
-		"hostName":     dbhelpers.WhereColumnInfo{"s.host_name", nil},
-		"physLocation": dbhelpers.WhereColumnInfo{"s.phys_location", api.IsInt},
-		"profileId":    dbhelpers.WhereColumnInfo{"s.profile", api.IsInt},
-		"status":       dbhelpers.WhereColumnInfo{"st.name", nil},
-		"type":         dbhelpers.WhereColumnInfo{"t.name", nil},
-	}
-
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
-	if len(errs) > 0 {
-		return nil, errs, tc.DataConflictError
-	}
-
-	query := selectServersQuery() + where + orderBy
-	log.Debugln("Query is ", query)
-
-	rows, err = db.NamedQuery(query, queryValues)
-	if err != nil {
-		return nil, []error{fmt.Errorf("querying: %v", err)}, tc.SystemError
-	}
-	defer rows.Close()
-
-	servers := []tc.Server{}
-
-	const HiddenField = "********"
-
-	for rows.Next() {
-		var s tc.Server
-		if err = rows.StructScan(&s); err != nil {
-			return nil, []error{fmt.Errorf("getting servers: %v", err)}, tc.SystemError
-		}
-		if privLevel < auth.PrivLevelAdmin {
-			s.ILOPassword = HiddenField
-			s.XMPPPasswd = HiddenField
-		}
-		servers = append(servers, s)
-	}
-	return servers, nil, tc.NoError
-}
-
-func selectServersQuery() string {
-
-	const JumboFrameBPS = 9000
-
-	// COALESCE is needed to default values that are nil in the database
-	// because Go does not allow that to marshal into the struct
-	selectStmt := `SELECT
-cg.name as cachegroup,
-s.cachegroup as cachegroup_id,
-s.cdn_id,
-cdn.name as cdn_name,
-s.domain_name,
-COALESCE(s.guid, '') as guid,
-s.host_name,
-COALESCE(s.https_port, 0) as https_port,
-s.id,
-COALESCE(s.ilo_ip_address, '') as ilo_ip_address,
-COALESCE(s.ilo_ip_gateway, '') as ilo_ip_gateway,
-COALESCE(s.ilo_ip_netmask, '') as ilo_ip_netmask,
-COALESCE(s.ilo_password, '') as ilo_password,
-COALESCE(s.ilo_username, '') as ilo_username,
-COALESCE(s.interface_mtu, ` + strconv.Itoa(JumboFrameBPS) + `) as interface_mtu,
-COALESCE(s.interface_name, '') as interface_name,
-COALESCE(s.ip6_address, '') as ip6_address,
-COALESCE(s.ip6_gateway, '') as ip6_gateway,
-s.ip_address,
-s.ip_gateway,
-s.ip_netmask,
-s.last_updated,
-COALESCE(s.mgmt_ip_address, '') as mgmt_ip_address,
-COALESCE(s.mgmt_ip_gateway, '') as mgmt_ip_gateway,
-COALESCE(s.mgmt_ip_netmask, '') as mgmt_ip_netmask,
-COALESCE(s.offline_reason, '') as offline_reason,
-pl.name as phys_location,
-s.phys_location as phys_location_id,
-p.name as profile,
-p.description as profile_desc,
-s.profile as profile_id,
-COALESCE(s.rack, '') as rack,
-s.reval_pending,
-COALESCE(s.router_host_name, '') as router_host_name,
-COALESCE(s.router_port_name, '') as router_port_name,
-st.name as status,
-s.status as status_id,
-COALESCE(s.tcp_port, 0) as tcp_port,
-t.name as server_type,
-s.type as server_type_id,
-s.upd_pending as upd_pending,
-COALESCE(s.xmpp_id, '') as xmpp_id,
-COALESCE(s.xmpp_passwd, '') as xmpp_passwd
-
-FROM server s
-
-JOIN cachegroup cg ON s.cachegroup = cg.id
-JOIN cdn cdn ON s.cdn_id = cdn.id
-JOIN phys_location pl ON s.phys_location = pl.id
-JOIN profile p ON s.profile = p.id
-JOIN status st ON s.status = st.id
-JOIN type t ON s.type = t.id`
-
-	return selectStmt
-}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services