rawlinp commented on a change in pull request #4700:
URL: https://github.com/apache/trafficcontrol/pull/4700#discussion_r432733101
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -33,103 +35,328 @@ import (
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/routing/middleware"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
- validation "github.com/go-ozzo/ozzo-validation"
+
+ "github.com/go-ozzo/ozzo-validation"
"github.com/go-ozzo/ozzo-validation/is"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
-// TOServer combines data about a server with metadata from an API request and
-// provides methods that implement several interfaces from the api package.
-type TOServer struct {
- api.APIInfoImpl `json:"-"`
- tc.ServerNullable
-}
-
-func (s *TOServer) SetLastUpdated(t tc.TimeNoMod) { s.LastUpdated = &t }
-func (*TOServer) InsertQuery() string { return insertQuery() }
-func (*TOServer) UpdateQuery() string { return updateQuery() }
-func (*TOServer) DeleteQuery() string { return deleteQuery() }
+const unfilteredServersQuery = `
+SELECT COUNT(server.id)
+FROM server
+`
-func (TOServer) GetKeyFieldsInfo() []api.KeyFieldInfo {
- return []api.KeyFieldInfo{{"id", api.GetIntKey}}
-}
+const selectQuery = `
+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,
+ 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 AS 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
+`
-func (s TOServer) GetKeys() (map[string]interface{}, bool) {
- if s.ID == nil {
- return map[string]interface{}{"id": 0}, false
- }
- return map[string]interface{}{"id": *s.ID}, true
-}
+const SelectInterfacesQuery = `
Review comment:
Can we share some of this query with the other N places it's duplicated?
##########
File path: docs/source/glossary.rst
##########
@@ -302,22 +302,22 @@ Glossary
GET /foo/bar/fun.html HTTP/1.1
Host: www-origin-cache.cdn.com
- #. The :dfn:`reverse proxy` finds out the URL of the true
:term:`origin` - in the case of :abbr:`ATS (Apache Traffic Server)` this is
done by looking up ``www-origin-cache.cdn.com`` in its remap rules - and finds
that it is ``www.origin.com``.
Review comment:
I really think these changes should've been out of scope for this PR,
but it is what it is.
##########
File path: traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go
##########
@@ -1,148 +0,0 @@
-package atsserver
Review comment:
Removing these three ATS config gen routes from 1.x could be its own PR
as well (probably including the removal of _all_ ATS config gen routes).
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -139,94 +366,213 @@ func (s *TOServer) Validate() error {
validateErrs["ip6Address"] = validation.Validate(s.IP6Address,
validation.By(tovalidate.IsValidIPv6CIDROrAddress))
}
errs = append(errs, tovalidate.ToErrors(validateErrs)...)
- if len(errs) > 0 {
- return util.JoinErrs(errs)
- }
+ errs = append(errs, validateCommon(s.CommonServerProperties, tx)...)
+
+ return util.JoinErrs(errs)
+}
+
+func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) error {
+ var errs []error
- if _, err := tc.ValidateTypeID(s.ReqInfo.Tx.Tx, s.TypeID, "server");
err != nil {
+ if err := validateV1(s.ServerNullableV11, tx); err != nil {
return err
}
- rows, err := s.ReqInfo.Tx.Tx.Query("select cdn from profile where
id=$1", s.ProfileID)
- if err != nil {
- log.Error.Printf("could not execute select cdnID from profile:
%s\n", err)
- errs = append(errs, tc.DBError)
- return util.JoinErrs(errs)
+ // default boolean value is false
+ if s.IPIsService == nil {
+ s.IPIsService = new(bool)
}
- 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, errors.New("associated profile must
have a cdn associated"))
- return util.JoinErrs(errs)
- }
+ if s.IP6IsService == nil {
+ s.IP6IsService = new(bool)
}
- log.Infof("got cdn id: %d from profile and cdn id: %d from server",
cdnID, *s.CDNID)
- if cdnID != *s.CDNID {
- errs = append(errs, errors.New(fmt.Sprintf("CDN id '%d' for
profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID)))
+
+ if !*s.IPIsService && !*s.IP6IsService {
+ errs = append(errs, tc.NeedsAtLeastOneServiceAddressError)
}
- return util.JoinErrs(errs)
-}
-// ChangeLogMessage implements the api.ChangeLogger interface for a custom log
message
-func (s TOServer) ChangeLogMessage(action string) (string, error) {
+ if *s.IPIsService && s.IPAddress == nil {
+ errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
+ }
- var status string
- if s.Status != nil {
- status = *s.Status
+ if *s.IP6IsService && s.IP6Address == nil {
+ errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
}
+ return util.JoinErrs(errs)
+}
- var hostName string
- if s.HostName != nil {
- hostName = *s.HostName
+func validateMTU(mtu interface{}) error {
+ m := mtu.(*uint64)
Review comment:
this should probably use the non-panicking type assertion `m, ok :=
mtu.(*uint64)` and return an error `if !ok`
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -139,94 +366,213 @@ func (s *TOServer) Validate() error {
validateErrs["ip6Address"] = validation.Validate(s.IP6Address,
validation.By(tovalidate.IsValidIPv6CIDROrAddress))
}
errs = append(errs, tovalidate.ToErrors(validateErrs)...)
- if len(errs) > 0 {
- return util.JoinErrs(errs)
- }
+ errs = append(errs, validateCommon(s.CommonServerProperties, tx)...)
+
+ return util.JoinErrs(errs)
+}
+
+func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) error {
+ var errs []error
- if _, err := tc.ValidateTypeID(s.ReqInfo.Tx.Tx, s.TypeID, "server");
err != nil {
+ if err := validateV1(s.ServerNullableV11, tx); err != nil {
return err
}
- rows, err := s.ReqInfo.Tx.Tx.Query("select cdn from profile where
id=$1", s.ProfileID)
- if err != nil {
- log.Error.Printf("could not execute select cdnID from profile:
%s\n", err)
- errs = append(errs, tc.DBError)
- return util.JoinErrs(errs)
+ // default boolean value is false
+ if s.IPIsService == nil {
+ s.IPIsService = new(bool)
}
- 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, errors.New("associated profile must
have a cdn associated"))
- return util.JoinErrs(errs)
- }
+ if s.IP6IsService == nil {
+ s.IP6IsService = new(bool)
}
- log.Infof("got cdn id: %d from profile and cdn id: %d from server",
cdnID, *s.CDNID)
- if cdnID != *s.CDNID {
- errs = append(errs, errors.New(fmt.Sprintf("CDN id '%d' for
profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID)))
+
+ if !*s.IPIsService && !*s.IP6IsService {
+ errs = append(errs, tc.NeedsAtLeastOneServiceAddressError)
}
- return util.JoinErrs(errs)
-}
-// ChangeLogMessage implements the api.ChangeLogger interface for a custom log
message
-func (s TOServer) ChangeLogMessage(action string) (string, error) {
+ if *s.IPIsService && s.IPAddress == nil {
+ errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
+ }
- var status string
- if s.Status != nil {
- status = *s.Status
+ if *s.IP6IsService && s.IP6Address == nil {
+ errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
}
+ return util.JoinErrs(errs)
+}
- var hostName string
- if s.HostName != nil {
- hostName = *s.HostName
+func validateMTU(mtu interface{}) error {
+ m := mtu.(*uint64)
+ if m == nil {
+ return nil
}
- var domainName string
- if s.DomainName != nil {
- domainName = *s.DomainName
+ if *m < 1280 {
+ return errors.New("must be at least 1280")
}
+ return nil
+}
+
+func validateV3(s tc.ServerNullable, tx *sql.Tx) (string, error) {
- var serverID string
- if s.ID != nil {
- serverID = strconv.Itoa(*s.ID)
+ if len(s.Interfaces) == 0 {
+ return "", errors.New("a server must have at least one
interface")
}
+ var errs []error
+ var serviceAddrV4Found bool
+ var serviceAddrV6Found bool
+ var serviceInterface string
+ for _, iface := range s.Interfaces {
+
+ ruleName := fmt.Sprintf("interface '%s' ", iface.Name)
+ errs = append(errs, tovalidate.ToErrors(validation.Errors{
+ ruleName + "name":
validation.Validate(iface.Name, validation.Required),
+ ruleName + "mtu":
validation.Validate(iface.MaxBandwidth, validation.By(validateMTU)),
+ ruleName + "ipAddresses":
validation.Validate(iface.IPAddresses, validation.Required),
+ })...)
+
+ for _, addr := range iface.IPAddresses {
+ ruleName += fmt.Sprintf("address '%s'", addr.Address)
+
+ var parsedIP net.IP
+ var err error
+ if parsedIP, _, err = net.ParseCIDR(addr.Address); err
!= nil {
+ if parsedIP = net.ParseIP(addr.Address);
parsedIP == nil {
+ errs = append(errs, fmt.Errorf("%s:
address: %v", ruleName, err))
+ continue
+ }
+ }
- message := action + ` ` + status + ` server: { "hostName":"` + hostName
+ `", "domainName":"` + domainName + `", id:` + serverID + ` }`
+ if addr.Gateway != nil {
+ if gateway := net.ParseIP(*addr.Gateway);
gateway == nil {
+ errs = append(errs, fmt.Errorf("%s:
gateway: could not parse '%s' as a network gateway", ruleName, *addr.Gateway))
+ } else if (gateway.To4() == nil &&
parsedIP.To4() != nil) || (gateway.To4() != nil && parsedIP.To4() == nil) {
+ errs = append(errs,
errors.New(ruleName+": address family mismatch between address and gateway"))
+ }
+ }
- return message, nil
+ if addr.ServiceAddress {
+ if serviceInterface != "" && serviceInterface
!= iface.Name {
+ errs = append(errs,
fmt.Errorf("interfaces: both %s and %s interfaces contain service addresses -
only one service-address-containing-interface is allowed", serviceInterface,
iface.Name))
+ }
+ serviceInterface = iface.Name
+ if parsedIP.To4() != nil {
+ if serviceAddrV4Found {
+ errs = append(errs,
fmt.Errorf("interfaces: address '%s' of interface '%s' is marked as a service
address, but an IPv4 service address appears earlier in the list",
addr.Address, iface.Name))
+ }
+ serviceAddrV4Found = true
+ } else {
+ if serviceAddrV6Found {
+ errs = append(errs,
fmt.Errorf("interfaces: address '%s' of interface '%s' is marked as a service
address, but an IPv6 service address appears earlier in the list",
addr.Address, iface.Name))
+ }
+ serviceAddrV6Found = true
+ }
+ }
+ }
+ }
+
+ errs = append(errs, validateCommon(s.CommonServerProperties, tx)...)
+ return serviceInterface, util.JoinErrs(errs)
}
-func (s *TOServer) Read() ([]interface{}, error, error, int) {
- version := s.APIInfo().Version
- if version == nil {
- return nil, nil, errors.New("TOServer.Read called with nil API
version"), http.StatusInternalServerError
+func Read(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
}
+ defer inf.Close()
- returnable := []interface{}{}
+ // Middleware should've already handled this, so idk why this is a
pointer at all tbh
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
- servers, userErr, sysErr, errCode := getServers(s.ReqInfo.Params,
s.ReqInfo.Tx, s.ReqInfo.User)
+ servers := []tc.ServerNullable{}
+ var unfiltered uint64
+ servers, unfiltered, userErr, sysErr, errCode = getServers(inf.Params,
inf.Tx, inf.User)
if userErr != nil || sysErr != nil {
- return nil, userErr, sysErr, errCode
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if version.Major >= 3 {
+ api.WriteRespWithSummary(w, r, servers, unfiltered)
+ return
}
+ if version.Major <= 1 {
+ legacyServers := make([]tc.ServerNullableV11, 0, len(servers))
+ for _, server := range servers {
+ legacyServer, err := server.ToServerV2()
+ if err != nil {
+ api.HandleErr(w, r, tx,
http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to
legacy format: %v", err))
+ return
+ }
+ legacyServers = append(legacyServers,
legacyServer.ServerNullableV11)
+ }
+ api.WriteResp(w, r, legacyServers)
+ return
+ }
+
+ legacyServers := make([]tc.ServerNullableV2, 0, len(servers))
for _, server := range servers {
- switch {
- // NOTE: it's required to handle minor version cases in a
descending >= manner
- case version.Major >= 2:
- returnable = append(returnable, server)
- case version.Major == 1 && version.Minor >= 1:
- returnable = append(returnable,
server.ServerNullableV11)
- default:
- return nil, nil, fmt.Errorf("TOServer.Read called with
invalid API version: %d.%d", version.Major, version.Minor),
http.StatusInternalServerError
+ legacyServer, err := server.ToServerV2()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("failed to convert servers to legacy format: %v", err))
+ return
}
+ legacyServers = append(legacyServers, legacyServer)
+ }
+ api.WriteResp(w, r, legacyServers)
+}
+
+func ReadID(w http.ResponseWriter, r *http.Request) {
+ alternative := "GET /servers with query parameter id"
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"id"})
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleDeprecatedErr(w, r, tx, errCode, userErr, sysErr,
&alternative)
+ return
}
+ defer inf.Close()
+
+ servers := []tc.ServerNullable{}
+ servers, _, userErr, sysErr, errCode = getServers(inf.Params, inf.Tx,
inf.User)
- return returnable, nil, nil, http.StatusOK
+ if len(servers) > 1 {
+ api.HandleDeprecatedErr(w, r, tx,
http.StatusInternalServerError, nil, fmt.Errorf("ID '%d' matched more than one
server (%d total)", inf.IntParams["id"], len(servers)), &alternative)
+ return
+ }
+
+ deprecationAlerts := api.CreateDeprecationAlerts(&alternative)
+
+ // No need to bother converting if there's no data
+ if len(servers) < 1 {
+ api.WriteAlertsObj(w, r, http.StatusOK, deprecationAlerts,
servers)
+ }
+
+ legacyServers := make([]tc.ServerNullableV11, 0, len(servers))
+ for _, server := range servers {
+ legacyServer, err := server.ToServerV2()
+ if err != nil {
+ api.HandleDeprecatedErr(w, r, tx,
http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to
legacy format: %v", err), &alternative)
+ return
+ }
+ legacyServers = append(legacyServers,
legacyServer.ServerNullableV11)
+ }
+ api.WriteAlertsObj(w, r, http.StatusOK, deprecationAlerts,
legacyServers)
}
-func getServers(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser)
([]tc.ServerNullable, error, error, int) {
+func getServers(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser)
([]tc.ServerNullable, uint64, error, error, int) {
+ var unfiltered uint64
Review comment:
this var (and the associated query var) would probably make more sense
with `serverCount` or something similar in the name
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -359,267 +737,482 @@ WHERE s.id IN (` + edgeIDs + `)))
return mids, nil, nil, http.StatusOK
}
-func (s *TOServer) Update() (error, error, int) {
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
- }
- // see if type changed
- typeID := -1
- // see if cdn changed
- cdnId := -1
-
- if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server
WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil {
+func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx)
(error, error, int) {
+ // see if cdn or type changed
+ var cdnID int
+ var typeID int
+ if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1",
*server.ID).Scan(&typeID, &cdnID); err != nil {
if err == sql.ErrNoRows {
- return errors.New("no server found with this id"), nil,
http.StatusNotFound
+ return errors.New("no server found with this ID"), nil,
http.StatusNotFound
}
return nil, fmt.Errorf("getting current server type: %v", err),
http.StatusInternalServerError
}
- dsIDs := []int64{}
- if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice
FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
+ var dsIDs []int64
+ if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM
deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("getting server assigned delivery
services: %v", err), http.StatusInternalServerError
}
+ // If type is changing ensure it isn't assigned to any DSes.
+ if typeID != *server.TypeID {
+ if len(dsIDs) != 0 {
+ return errors.New("server type can not be updated when
it is currently assigned to Delivery Services"), nil, http.StatusConflict
+ }
+ }
// Check to see if the user is trying to change the CDN of a server,
which is already linked with a DS
- if cdnId != *s.CDNID && len(dsIDs) != 0 {
+ if cdnID != *server.CDNID && len(dsIDs) != 0 {
return errors.New("server cdn can not be updated when it is
currently assigned to delivery services"), nil, http.StatusConflict
}
- // If type is changing ensure it isn't assigned to any DSes.
- if typeID != *s.TypeID {
- if len(dsIDs) != 0 {
- return errors.New("server type can not be updated when
it is currently assigned to delivery services"), nil, http.StatusConflict
+
+ return nil, nil, http.StatusOK
+}
+
+func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx)
(error, error, int) {
+ ifaceQry := `
+ INSERT INTO interface (
+ max_bandwidth,
+ monitor,
+ mtu,
+ name,
+ server
+ ) VALUES
+ `
+ ipQry := `
+ INSERT INTO ip_address (
+ address,
+ gateway,
+ interface,
+ server,
+ service_address
+ ) VALUES
+ `
+
+ ifaceQueryParts := make([]string, 0, len(interfaces))
+ ipQueryParts := make([]string, 0, len(interfaces))
+ ifaceArgs := make([]interface{}, 0, len(interfaces))
+ ipArgs := make([]interface{}, 0, len(interfaces))
+ for i, iface := range interfaces {
+ argStart := i * 5
+ ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ifaceArgs = append(ifaceArgs, iface.MaxBandwidth,
iface.Monitor, iface.MTU, iface.Name, id)
+ for _, ip := range iface.IPAddresses {
+ argStart = len(ipArgs)
+ ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ipArgs = append(ipArgs, ip.Address, ip.Gateway,
iface.Name, id, ip.ServiceAddress)
}
}
- current := TOServer{}
- err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE
sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t)
+ ifaceQry += strings.Join(ifaceQueryParts, ",")
+ log.Debugf("Inserting interfaces for new server, query is: %s",
ifaceQry)
+
+ ifaceRows, err := tx.Query(ifaceQry, ifaceArgs...)
if err != nil {
return api.ParseDBError(err)
}
- defaultIsService := true
- if s.IPIsService == nil {
- if current.IPIsService != nil {
- s.IPIsService = current.IPIsService
- } else {
- s.IPIsService = &defaultIsService
+ defer ifaceRows.Close()
+ insertedIfaces := 0
+ for ifaceRows.Next() {
+ insertedIfaces++
+ }
+ log.Debugf("Inserted %d interfaces", insertedIfaces)
+
+ ipQry += strings.Join(ipQueryParts, ",")
+ log.Debugf("Inserting IP addresses for new server, query is: %s", ipQry)
+
+ ipRows, err := tx.Query(ipQry, ipArgs...)
+ if err != nil {
+ return api.ParseDBError(err)
+ }
+ defer ipRows.Close()
+
+ return nil, nil, http.StatusOK
+}
+
+func deleteInterfaces(id int, tx *sql.Tx) (error, error, int) {
+ if err := tx.QueryRow(deleteIPsQuery, id).Scan(); err != nil && err !=
sql.ErrNoRows {
+ return api.ParseDBError(err)
+ }
+
+ if err := tx.QueryRow(deleteInterfacesQuery, id).Scan(); err != nil &&
err != sql.ErrNoRows {
+ return api.ParseDBError(err)
+ }
+
+ return nil, nil, http.StatusOK
+}
+
+func Update(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"},
[]string{"id"})
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ var server tc.ServerNullableV2
+ var interfaces []tc.ServerInterfaceInfo
+ if inf.Version.Major >= 3 {
+ var newServer tc.ServerNullable
+ if err := json.NewDecoder(r.Body).Decode(&newServer); err !=
nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ serviceInterface, err := validateV3(newServer, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ server, err = newServer.ToServerV2()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("converting v3 server to v2 for update: %v", err))
+ return
+ }
+ server.InterfaceName = util.StrPtr(serviceInterface)
+ interfaces = newServer.Interfaces
+ } else if inf.Version.Major == 2 {
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ err := validateV2(&server, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ interfaces, err =
server.LegacyInterfaceDetails.ToInterfaces(*server.IPIsService,
*server.IP6IsService)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("converting server legacy interfaces to interface array: %v",
err))
+ return
+ }
+ } else {
+ var legacyServer tc.ServerNullableV11
+ if err := json.NewDecoder(r.Body).Decode(&legacyServer); err !=
nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ err := validateV1(legacyServer, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ interfaces, err =
legacyServer.LegacyInterfaceDetails.ToInterfaces(true, true)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("converting server legacy interfaces to interface array: %v",
err))
+ return
+ }
+ server = tc.ServerNullableV2{
+ ServerNullableV11: legacyServer,
+ IPIsService: util.BoolPtr(true),
+ IP6IsService: util.BoolPtr(true),
}
}
- if s.IP6IsService == nil {
- if current.IP6IsService != nil {
- s.IP6IsService = current.IP6IsService
- } else {
- s.IP6IsService = &defaultIsService
+
+ server.ID = new(int)
+ *server.ID = inf.IntParams["id"]
+
+ if userErr, sysErr, errCode =
checkTypeChangeSafety(server.CommonServerProperties, inf.Tx); userErr != nil ||
sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ rows, err := inf.Tx.NamedQuery(updateQuery, server)
+ if err != nil {
+ userErr, sysErr, errCode = api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer rows.Close()
+
+ rowsAffected := 0
+ for rows.Next() {
+ if err := rows.StructScan(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusNotFound, nil,
fmt.Errorf("scanning lastUpdated from server insert: %v", err))
+ return
}
+ rowsAffected++
+ }
+
+ if rowsAffected < 1 {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no
server found with this id"), nil)
+ return
+ }
+ if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("update for server #%d affected too many rows (%d)", *server.ID,
rowsAffected))
+ return
+ }
+
+ if userErr, sysErr, errCode = deleteInterfaces(inf.IntParams["id"],
tx); userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if userErr, sysErr, errCode = createInterfaces(inf.IntParams["id"],
interfaces, tx); userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if inf.Version.Major >= 3 {
+ api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated",
tc.ServerNullable{CommonServerProperties: server.CommonServerProperties,
Interfaces: interfaces})
+ } else if inf.Version.Minor <= 1 {
+ api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated",
server.ServerNullableV11)
+ } else {
+ api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated",
server)
}
- return api.GenericUpdate(s)
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: updated",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-func (s *TOServer) Create() (error, error, int) {
- // TODO put in Validate()
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
+func createV1(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
+ var server tc.ServerNullableV11
+
+ tx := inf.Tx.Tx
+
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
}
- if s.XMPPID == nil || *s.XMPPID == "" {
- hostName := *s.HostName
- s.XMPPID = &hostName
+
+ if err := validateV1(server, tx); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
}
- // default the is service field to true if omitted and to upgrade
version < 1.4
- defaultIsService := true
- if s.IPIsService == nil {
- s.IPIsService = &defaultIsService
+ resultRows, err := inf.Tx.NamedQuery(insertQuery, server)
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
}
- if s.IP6IsService == nil {
- s.IP6IsService = &defaultIsService
+ defer resultRows.Close()
+
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err :=
resultRows.StructScan(&server.CommonServerProperties); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("server create scanning: %v", err))
+ return
+ }
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("server create: no server was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from server insert"))
}
- return api.GenericCreate(s)
-}
+ ifaces, err := server.LegacyInterfaceDetails.ToInterfaces(true, true)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ }
-func (s *TOServer) Delete() (error, error, int) { return api.GenericDelete(s) }
+ if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces,
tx); err != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
-func selectV20UpdatesQuery() string {
- return `SELECT
-sv.ip_address_is_service,
-sv.ip6_address_is_service
-FROM
- server sv`
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server)
+
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-func selectQuery() string {
- const JumboFrameBPS = 9000
- return `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_address_is_service,
-s.ip6_gateway,
-s.ip_address,
-s.ip_address_is_service,
-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`
+func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
+ var server tc.ServerNullableV2
+
+ tx := inf.Tx.Tx
+
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ if err := validateV2(&server, tx); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ resultRows, err := inf.Tx.NamedQuery(insertQuery, server)
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer resultRows.Close()
+
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err :=
resultRows.StructScan(&server.CommonServerProperties); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("server create scanning: %v", err))
+ return
+ }
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("server create: no server was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from server insert"))
+ }
+
+ ifaces, err :=
server.LegacyInterfaceDetails.ToInterfaces(*server.IPIsService,
*server.IP6IsService)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ }
+
+ if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces,
tx); err != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server)
+
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-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_address_is_service,
-ip6_gateway,
-ip_address,
-ip_address_is_service,
-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,
-xmpp_id,
-xmpp_passwd
-) 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_address_is_service,
-:ip6_gateway,
-:ip_address,
-:ip_address_is_service,
-: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,
-:xmpp_id,
-:xmpp_passwd
-) RETURNING id,last_updated`
- return query
+func createV3(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
+ var server tc.ServerNullable
+
+ tx := inf.Tx.Tx
+
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ serviceInterface, err := validateV3(server, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ v2Server, err := server.ToServerV2()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+
+ v2Server.InterfaceName = &serviceInterface
+
+ resultRows, err := inf.Tx.NamedQuery(insertQuery, v2Server)
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer resultRows.Close()
+
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err :=
resultRows.StructScan(&server.CommonServerProperties); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("server create scanning: %v", err))
+ return
+ }
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("server create: no server was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from server insert"))
+ return
+ }
+
+ userErr, sysErr, errCode := createInterfaces(*server.ID,
server.Interfaces, tx)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server)
+
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-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_address_is_service=:ip6_address_is_service,
-ip6_gateway=:ip6_gateway,
-ip_address=:ip_address,
-ip_address_is_service=:ip_address_is_service,
-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,
-xmpp_id=:xmpp_id,
-xmpp_passwd=:xmpp_passwd
-WHERE id=:id RETURNING last_updated`
- return query
+func Create(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ switch {
+ case inf.Version.Major <= 1:
+ createV1(inf, w, r)
+ case inf.Version.Major == 2:
+ createV2(inf, w, r)
+ default:
+ createV3(inf, w, r)
+ }
}
-func deleteQuery() string {
- return `DELETE FROM server WHERE id = :id`
+func Delete(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"},
[]string{"id"})
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ id := inf.IntParams["id"]
+
+ var servers []tc.ServerNullable
+ servers, _, userErr, sysErr, errCode =
getServers(map[string]string{"id": inf.Params["id"]}, inf.Tx, inf.User)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if len(servers) < 1 {
+ api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no
server exists by id #%d", id), nil)
+ return
+ }
+ if len(servers) > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("there are somehow two servers with id %d - cannot delete", id))
+ return
+ }
+
+ userErr, sysErr, errCode = deleteInterfaces(id, tx)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if err := tx.QueryRow(deleteServerQuery, id).Scan(); err != nil && err
!= sql.ErrNoRows {
+ userErr, sysErr, errCode = api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
Review comment:
Should we also handle the `sql.ErrNoRows` error here?
##########
File path: infrastructure/cdn-in-a-box/enroller/Dockerfile
##########
@@ -20,7 +20,7 @@ FROM golang:1.14.2 AS enroller-builder
# enroller source and dependencies
COPY ./lib/ /go/src/github.com/apache/trafficcontrol/lib/
COPY ./vendor/ /go/src/github.com/apache/trafficcontrol/vendor/
-COPY ./traffic_ops/client/
/go/src/github.com/apache/trafficcontrol/traffic_ops/v2-client/
+COPY ./traffic_ops/v2-client/
/go/src/github.com/apache/trafficcontrol/traffic_ops/v2-client/
Review comment:
This probably should've been a separate PR as well -- others ran into
this issue too: https://github.com/apache/trafficcontrol/pull/4739
##########
File path: traffic_ops/traffic_ops_golang/server/detail.go
##########
@@ -202,7 +202,7 @@ SELECT
SELECT ( json_build_object (
'address', ip_address.address,
'gateway', ip_address.gateway,
- 'service_address',
ip_address.service_address
+ 'serviceAddress',
ip_address.service_address
Review comment:
This whole query chunk for `interfaces` seems like it should be moved
into a shared constant, since it's already out of sync here w/ the same query
in dbhelpers.go. Also, why change this from snake_case but leave
`max_bandwidth` as-is?
##########
File path: traffic_ops/client/server.go
##########
@@ -33,158 +33,142 @@ const (
API_SERVER_ASSIGN_DELIVERY_SERVICES = API_SERVER_DELIVERY_SERVICES +
"?replace=%t"
)
+func needAndCanFetch(id *int, name *string) bool {
+ return (id == nil || *id == 0) && name != nil && *name != ""
+}
+
// CreateServer creates a Server.
-func (to *Session) CreateServer(server tc.Server) (tc.Alerts, ReqInf, error) {
+func (to *Session) CreateServer(server tc.ServerNullable) (tc.Alerts, ReqInf,
error) {
+ var alerts tc.Alerts
var remoteAddr net.Addr
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr:
remoteAddr}
- if server.CachegroupID == 0 && server.Cachegroup != "" {
- cg, _, err := to.GetCacheGroupNullableByName(server.Cachegroup)
+ if needAndCanFetch(server.CachegroupID, server.Cachegroup) {
+ cg, _, err := to.GetCacheGroupNullableByName(*server.Cachegroup)
if err != nil {
- return tc.Alerts{}, ReqInf{}, errors.New("no cachegroup
named " + server.Cachegroup + ":" + err.Error())
+ return alerts, reqInf, fmt.Errorf("no cachegroup named
%s: %v", *server.Cachegroup, err)
}
if len(cg) == 0 {
- return tc.Alerts{}, ReqInf{}, errors.New("no cachegroup
named " + server.Cachegroup)
+ return alerts, reqInf, fmt.Errorf("no cachegroup named
%s", *server.Cachegroup)
}
if cg[0].ID == nil {
- return tc.Alerts{}, ReqInf{}, errors.New("Cachegroup
named " + server.Cachegroup + " has a nil ID")
+ return alerts, reqInf, fmt.Errorf("Cachegroup named %s
has a nil ID", *server.Cachegroup)
}
- server.CachegroupID = *cg[0].ID
+ server.CachegroupID = cg[0].ID
}
- if server.CDNID == 0 && server.CDNName != "" {
- c, _, err := to.GetCDNByName(server.CDNName)
+ if needAndCanFetch(server.CDNID, server.CDNName) {
+ c, _, err := to.GetCDNByName(*server.CDNName)
if err != nil {
- return tc.Alerts{}, ReqInf{}, errors.New("no CDN named
" + server.CDNName + ":" + err.Error())
+ return alerts, reqInf, fmt.Errorf("no CDN named %s:
%v", *server.CDNName, err)
}
if len(c) == 0 {
- return tc.Alerts{}, ReqInf{}, errors.New("no CDN named
" + server.CDNName)
+ return alerts, reqInf, fmt.Errorf("no CDN named %s",
*server.CDNName)
}
- server.CDNID = c[0].ID
+ server.CDNID = &c[0].ID
}
- if server.PhysLocationID == 0 && server.PhysLocation != "" {
- ph, _, err := to.GetPhysLocationByName(server.PhysLocation)
+ if needAndCanFetch(server.PhysLocationID, server.PhysLocation) {
+ ph, _, err := to.GetPhysLocationByName(*server.PhysLocation)
if err != nil {
- return tc.Alerts{}, ReqInf{}, errors.New("no
physlocation named " + server.PhysLocation + ":" + err.Error())
+ return alerts, reqInf, fmt.Errorf("no physlocation
named %s: %v", *server.PhysLocation, err)
}
if len(ph) == 0 {
- return tc.Alerts{}, ReqInf{}, errors.New("no
physlocation named " + server.PhysLocation)
+ return alerts, reqInf, fmt.Errorf("no physlocation
named %s", *server.PhysLocation)
}
- server.PhysLocationID = ph[0].ID
+ server.PhysLocationID = &ph[0].ID
}
- if server.ProfileID == 0 && server.Profile != "" {
- pr, _, err := to.GetProfileByName(server.Profile)
+ if needAndCanFetch(server.ProfileID, server.Profile) {
+ pr, _, err := to.GetProfileByName(*server.Profile)
if err != nil {
- return tc.Alerts{}, ReqInf{}, errors.New("no profile
named " + server.Profile + ":" + err.Error())
+ return alerts, reqInf, fmt.Errorf("no profile named %s:
%v", *server.Profile, err)
}
if len(pr) == 0 {
- return tc.Alerts{}, ReqInf{}, errors.New("no profile
named " + server.Profile)
+ return alerts, reqInf, fmt.Errorf("no profile named
%s", *server.Profile)
}
- server.ProfileID = pr[0].ID
+ server.ProfileID = &pr[0].ID
}
- if server.StatusID == 0 && server.Status != "" {
- st, _, err := to.GetStatusByName(server.Status)
+ if needAndCanFetch(server.StatusID, server.Status) {
+ st, _, err := to.GetStatusByName(*server.Status)
if err != nil {
- return tc.Alerts{}, ReqInf{}, errors.New("no status
named " + server.Status + ":" + err.Error())
+ return alerts, reqInf, fmt.Errorf("no status named %s:
%v", *server.Status, err)
}
if len(st) == 0 {
- return tc.Alerts{}, ReqInf{}, errors.New("no status
named " + server.Status)
+ return alerts, reqInf, fmt.Errorf("no status named %s",
*server.Status)
}
- server.StatusID = st[0].ID
+ server.StatusID = &st[0].ID
}
- if server.TypeID == 0 && server.Type != "" {
+ if (server.TypeID == nil || *server.TypeID == 0) && server.Type != "" {
ty, _, err := to.GetTypeByName(server.Type)
if err != nil {
- return tc.Alerts{}, ReqInf{}, errors.New("no type named
" + server.Type + ":" + err.Error())
+ return alerts, reqInf, fmt.Errorf("no type named %s:
%v", server.Type, err)
}
if len(ty) == 0 {
- return tc.Alerts{}, ReqInf{}, errors.New("no type named
" + server.Type)
+ return alerts, reqInf, fmt.Errorf("no type named %s",
server.Type)
}
- server.TypeID = ty[0].ID
+ server.TypeID = &ty[0].ID
}
+
reqBody, err := json.Marshal(server)
if err != nil {
- return tc.Alerts{}, reqInf, err
+ return alerts, reqInf, err
}
resp, remoteAddr, err := to.request(http.MethodPost, API_SERVERS,
reqBody)
+ reqInf.RemoteAddr = remoteAddr
if err != nil {
- return tc.Alerts{}, reqInf, err
+ return alerts, reqInf, err
}
defer resp.Body.Close()
- var alerts tc.Alerts
+
err = json.NewDecoder(resp.Body).Decode(&alerts)
- return alerts, reqInf, nil
+ return alerts, reqInf, err
}
// UpdateServerByID updates a Server by ID.
-func (to *Session) UpdateServerByID(id int, server tc.Server) (tc.Alerts,
ReqInf, error) {
-
+func (to *Session) UpdateServerByID(id int, server tc.ServerNullable)
(tc.Alerts, ReqInf, error) {
+ var alerts tc.Alerts
var remoteAddr net.Addr
- reqBody, err := json.Marshal(server)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr:
remoteAddr}
+
+ reqBody, err := json.Marshal(server)
if err != nil {
- return tc.Alerts{}, reqInf, err
+ return alerts, reqInf, err
}
+
route := fmt.Sprintf("%s/%d", API_SERVERS, id)
resp, remoteAddr, err := to.request(http.MethodPut, route, reqBody)
+ reqInf.RemoteAddr = remoteAddr
if err != nil {
- return tc.Alerts{}, reqInf, err
+ return alerts, reqInf, err
}
defer resp.Body.Close()
- var alerts tc.Alerts
+
err = json.NewDecoder(resp.Body).Decode(&alerts)
- return alerts, reqInf, nil
+ return alerts, reqInf, err
}
// GetServers returns a list of Servers.
-func (to *Session) GetServers() ([]tc.Server, ReqInf, error) {
- resp, remoteAddr, err := to.request(http.MethodGet, API_SERVERS, nil)
- reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr:
remoteAddr}
- if err != nil {
- return nil, reqInf, err
+// The 'params' parameter can be used to optionally pass URL "query string
+// parameters" in the request.
+// It returns, in order, the API response that Traffic Ops returned, a request
+// info object, and any error that occurred.
+func (to *Session) GetServers(params *url.Values) (tc.ServersV3Response,
ReqInf, error) {
Review comment:
These non-nullable -> nullable client changes would've been nice to
split into another PR as well, as a bit of a "step 1", then have this PR
implement just the v3-specific conversions.
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -33,103 +35,328 @@ import (
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/routing/middleware"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
- validation "github.com/go-ozzo/ozzo-validation"
+
+ "github.com/go-ozzo/ozzo-validation"
"github.com/go-ozzo/ozzo-validation/is"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
-// TOServer combines data about a server with metadata from an API request and
-// provides methods that implement several interfaces from the api package.
-type TOServer struct {
- api.APIInfoImpl `json:"-"`
- tc.ServerNullable
-}
-
-func (s *TOServer) SetLastUpdated(t tc.TimeNoMod) { s.LastUpdated = &t }
-func (*TOServer) InsertQuery() string { return insertQuery() }
-func (*TOServer) UpdateQuery() string { return updateQuery() }
-func (*TOServer) DeleteQuery() string { return deleteQuery() }
+const unfilteredServersQuery = `
Review comment:
this doesn't seem to be the right name for this query
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -139,94 +366,213 @@ func (s *TOServer) Validate() error {
validateErrs["ip6Address"] = validation.Validate(s.IP6Address,
validation.By(tovalidate.IsValidIPv6CIDROrAddress))
}
errs = append(errs, tovalidate.ToErrors(validateErrs)...)
- if len(errs) > 0 {
- return util.JoinErrs(errs)
- }
+ errs = append(errs, validateCommon(s.CommonServerProperties, tx)...)
+
+ return util.JoinErrs(errs)
+}
+
+func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) error {
+ var errs []error
- if _, err := tc.ValidateTypeID(s.ReqInfo.Tx.Tx, s.TypeID, "server");
err != nil {
+ if err := validateV1(s.ServerNullableV11, tx); err != nil {
return err
}
- rows, err := s.ReqInfo.Tx.Tx.Query("select cdn from profile where
id=$1", s.ProfileID)
- if err != nil {
- log.Error.Printf("could not execute select cdnID from profile:
%s\n", err)
- errs = append(errs, tc.DBError)
- return util.JoinErrs(errs)
+ // default boolean value is false
+ if s.IPIsService == nil {
+ s.IPIsService = new(bool)
}
- 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, errors.New("associated profile must
have a cdn associated"))
- return util.JoinErrs(errs)
- }
+ if s.IP6IsService == nil {
+ s.IP6IsService = new(bool)
}
- log.Infof("got cdn id: %d from profile and cdn id: %d from server",
cdnID, *s.CDNID)
- if cdnID != *s.CDNID {
- errs = append(errs, errors.New(fmt.Sprintf("CDN id '%d' for
profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID)))
+
+ if !*s.IPIsService && !*s.IP6IsService {
+ errs = append(errs, tc.NeedsAtLeastOneServiceAddressError)
}
- return util.JoinErrs(errs)
-}
-// ChangeLogMessage implements the api.ChangeLogger interface for a custom log
message
-func (s TOServer) ChangeLogMessage(action string) (string, error) {
+ if *s.IPIsService && s.IPAddress == nil {
+ errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
+ }
- var status string
- if s.Status != nil {
- status = *s.Status
+ if *s.IP6IsService && s.IP6Address == nil {
+ errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
}
+ return util.JoinErrs(errs)
+}
- var hostName string
- if s.HostName != nil {
- hostName = *s.HostName
+func validateMTU(mtu interface{}) error {
+ m := mtu.(*uint64)
+ if m == nil {
+ return nil
}
- var domainName string
- if s.DomainName != nil {
- domainName = *s.DomainName
+ if *m < 1280 {
+ return errors.New("must be at least 1280")
}
+ return nil
+}
+
+func validateV3(s tc.ServerNullable, tx *sql.Tx) (string, error) {
- var serverID string
- if s.ID != nil {
- serverID = strconv.Itoa(*s.ID)
+ if len(s.Interfaces) == 0 {
+ return "", errors.New("a server must have at least one
interface")
}
+ var errs []error
+ var serviceAddrV4Found bool
+ var serviceAddrV6Found bool
+ var serviceInterface string
+ for _, iface := range s.Interfaces {
+
+ ruleName := fmt.Sprintf("interface '%s' ", iface.Name)
+ errs = append(errs, tovalidate.ToErrors(validation.Errors{
+ ruleName + "name":
validation.Validate(iface.Name, validation.Required),
+ ruleName + "mtu":
validation.Validate(iface.MaxBandwidth, validation.By(validateMTU)),
+ ruleName + "ipAddresses":
validation.Validate(iface.IPAddresses, validation.Required),
+ })...)
+
+ for _, addr := range iface.IPAddresses {
+ ruleName += fmt.Sprintf("address '%s'", addr.Address)
+
+ var parsedIP net.IP
+ var err error
+ if parsedIP, _, err = net.ParseCIDR(addr.Address); err
!= nil {
+ if parsedIP = net.ParseIP(addr.Address);
parsedIP == nil {
+ errs = append(errs, fmt.Errorf("%s:
address: %v", ruleName, err))
+ continue
+ }
+ }
- message := action + ` ` + status + ` server: { "hostName":"` + hostName
+ `", "domainName":"` + domainName + `", id:` + serverID + ` }`
+ if addr.Gateway != nil {
+ if gateway := net.ParseIP(*addr.Gateway);
gateway == nil {
+ errs = append(errs, fmt.Errorf("%s:
gateway: could not parse '%s' as a network gateway", ruleName, *addr.Gateway))
+ } else if (gateway.To4() == nil &&
parsedIP.To4() != nil) || (gateway.To4() != nil && parsedIP.To4() == nil) {
+ errs = append(errs,
errors.New(ruleName+": address family mismatch between address and gateway"))
+ }
+ }
- return message, nil
+ if addr.ServiceAddress {
+ if serviceInterface != "" && serviceInterface
!= iface.Name {
+ errs = append(errs,
fmt.Errorf("interfaces: both %s and %s interfaces contain service addresses -
only one service-address-containing-interface is allowed", serviceInterface,
iface.Name))
+ }
+ serviceInterface = iface.Name
+ if parsedIP.To4() != nil {
+ if serviceAddrV4Found {
+ errs = append(errs,
fmt.Errorf("interfaces: address '%s' of interface '%s' is marked as a service
address, but an IPv4 service address appears earlier in the list",
addr.Address, iface.Name))
+ }
+ serviceAddrV4Found = true
+ } else {
+ if serviceAddrV6Found {
+ errs = append(errs,
fmt.Errorf("interfaces: address '%s' of interface '%s' is marked as a service
address, but an IPv6 service address appears earlier in the list",
addr.Address, iface.Name))
+ }
+ serviceAddrV6Found = true
+ }
+ }
+ }
+ }
+
+ errs = append(errs, validateCommon(s.CommonServerProperties, tx)...)
+ return serviceInterface, util.JoinErrs(errs)
}
-func (s *TOServer) Read() ([]interface{}, error, error, int) {
- version := s.APIInfo().Version
- if version == nil {
- return nil, nil, errors.New("TOServer.Read called with nil API
version"), http.StatusInternalServerError
+func Read(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
}
+ defer inf.Close()
- returnable := []interface{}{}
+ // Middleware should've already handled this, so idk why this is a
pointer at all tbh
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
- servers, userErr, sysErr, errCode := getServers(s.ReqInfo.Params,
s.ReqInfo.Tx, s.ReqInfo.User)
+ servers := []tc.ServerNullable{}
+ var unfiltered uint64
+ servers, unfiltered, userErr, sysErr, errCode = getServers(inf.Params,
inf.Tx, inf.User)
if userErr != nil || sysErr != nil {
- return nil, userErr, sysErr, errCode
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if version.Major >= 3 {
+ api.WriteRespWithSummary(w, r, servers, unfiltered)
+ return
}
+ if version.Major <= 1 {
+ legacyServers := make([]tc.ServerNullableV11, 0, len(servers))
+ for _, server := range servers {
+ legacyServer, err := server.ToServerV2()
Review comment:
Is it possible that server data changes could be made using the V3 API
that would prevent the ability to use the V2/V1 APIs? Basically, is there any
valid reason why a V3 server couldn't be converted to a V2/V1 server?
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -359,267 +737,482 @@ WHERE s.id IN (` + edgeIDs + `)))
return mids, nil, nil, http.StatusOK
}
-func (s *TOServer) Update() (error, error, int) {
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
- }
- // see if type changed
- typeID := -1
- // see if cdn changed
- cdnId := -1
-
- if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server
WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil {
+func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx)
(error, error, int) {
+ // see if cdn or type changed
+ var cdnID int
+ var typeID int
+ if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1",
*server.ID).Scan(&typeID, &cdnID); err != nil {
if err == sql.ErrNoRows {
- return errors.New("no server found with this id"), nil,
http.StatusNotFound
+ return errors.New("no server found with this ID"), nil,
http.StatusNotFound
}
return nil, fmt.Errorf("getting current server type: %v", err),
http.StatusInternalServerError
}
- dsIDs := []int64{}
- if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice
FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
+ var dsIDs []int64
+ if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM
deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("getting server assigned delivery
services: %v", err), http.StatusInternalServerError
}
+ // If type is changing ensure it isn't assigned to any DSes.
+ if typeID != *server.TypeID {
+ if len(dsIDs) != 0 {
+ return errors.New("server type can not be updated when
it is currently assigned to Delivery Services"), nil, http.StatusConflict
+ }
+ }
// Check to see if the user is trying to change the CDN of a server,
which is already linked with a DS
- if cdnId != *s.CDNID && len(dsIDs) != 0 {
+ if cdnID != *server.CDNID && len(dsIDs) != 0 {
return errors.New("server cdn can not be updated when it is
currently assigned to delivery services"), nil, http.StatusConflict
}
- // If type is changing ensure it isn't assigned to any DSes.
- if typeID != *s.TypeID {
- if len(dsIDs) != 0 {
- return errors.New("server type can not be updated when
it is currently assigned to delivery services"), nil, http.StatusConflict
+
+ return nil, nil, http.StatusOK
+}
+
+func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx)
(error, error, int) {
+ ifaceQry := `
+ INSERT INTO interface (
+ max_bandwidth,
+ monitor,
+ mtu,
+ name,
+ server
+ ) VALUES
+ `
+ ipQry := `
+ INSERT INTO ip_address (
+ address,
+ gateway,
+ interface,
+ server,
+ service_address
+ ) VALUES
+ `
+
+ ifaceQueryParts := make([]string, 0, len(interfaces))
+ ipQueryParts := make([]string, 0, len(interfaces))
+ ifaceArgs := make([]interface{}, 0, len(interfaces))
+ ipArgs := make([]interface{}, 0, len(interfaces))
+ for i, iface := range interfaces {
+ argStart := i * 5
+ ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ifaceArgs = append(ifaceArgs, iface.MaxBandwidth,
iface.Monitor, iface.MTU, iface.Name, id)
+ for _, ip := range iface.IPAddresses {
+ argStart = len(ipArgs)
+ ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ipArgs = append(ipArgs, ip.Address, ip.Gateway,
iface.Name, id, ip.ServiceAddress)
}
}
- current := TOServer{}
- err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE
sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t)
+ ifaceQry += strings.Join(ifaceQueryParts, ",")
+ log.Debugf("Inserting interfaces for new server, query is: %s",
ifaceQry)
+
+ ifaceRows, err := tx.Query(ifaceQry, ifaceArgs...)
Review comment:
This seems like it should be an `Exec` instead if we really only care
about the number of rows affected.
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -248,71 +594,103 @@ func getServers(params map[string]string, tx *sqlx.Tx,
user *auth.CurrentUser) (
// don't allow query on ds outside user's tenant
dsID, err := strconv.Atoi(dsIDStr)
if err != nil {
- return nil, errors.New("dsId must be an integer"), nil,
http.StatusNotFound
+ return nil, unfiltered, errors.New("dsId must be an
integer"), nil, http.StatusNotFound
}
userErr, sysErr, _ := tenant.CheckID(tx.Tx, user, dsID)
if userErr != nil || sysErr != nil {
- return nil, errors.New("Forbidden"), sysErr,
http.StatusForbidden
+ return nil, unfiltered, errors.New("forbidden"),
sysErr, http.StatusForbidden
}
// only if dsId is part of params: add join on
deliveryservice_server table
- queryAddition = `
-FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id
-`
+ queryAddition = "\nFULL OUTER JOIN deliveryservice_server dss
ON dss.server = s.id\n"
Review comment:
I actually prefer the previous format -- it more clearly shows the query
addition without the newlines.
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -359,267 +737,482 @@ WHERE s.id IN (` + edgeIDs + `)))
return mids, nil, nil, http.StatusOK
}
-func (s *TOServer) Update() (error, error, int) {
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
- }
- // see if type changed
- typeID := -1
- // see if cdn changed
- cdnId := -1
-
- if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server
WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil {
+func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx)
(error, error, int) {
+ // see if cdn or type changed
+ var cdnID int
+ var typeID int
+ if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1",
*server.ID).Scan(&typeID, &cdnID); err != nil {
if err == sql.ErrNoRows {
- return errors.New("no server found with this id"), nil,
http.StatusNotFound
+ return errors.New("no server found with this ID"), nil,
http.StatusNotFound
}
return nil, fmt.Errorf("getting current server type: %v", err),
http.StatusInternalServerError
}
- dsIDs := []int64{}
- if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice
FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
+ var dsIDs []int64
+ if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM
deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("getting server assigned delivery
services: %v", err), http.StatusInternalServerError
}
+ // If type is changing ensure it isn't assigned to any DSes.
+ if typeID != *server.TypeID {
+ if len(dsIDs) != 0 {
+ return errors.New("server type can not be updated when
it is currently assigned to Delivery Services"), nil, http.StatusConflict
+ }
+ }
// Check to see if the user is trying to change the CDN of a server,
which is already linked with a DS
- if cdnId != *s.CDNID && len(dsIDs) != 0 {
+ if cdnID != *server.CDNID && len(dsIDs) != 0 {
return errors.New("server cdn can not be updated when it is
currently assigned to delivery services"), nil, http.StatusConflict
}
- // If type is changing ensure it isn't assigned to any DSes.
- if typeID != *s.TypeID {
- if len(dsIDs) != 0 {
- return errors.New("server type can not be updated when
it is currently assigned to delivery services"), nil, http.StatusConflict
+
+ return nil, nil, http.StatusOK
+}
+
+func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx)
(error, error, int) {
+ ifaceQry := `
+ INSERT INTO interface (
+ max_bandwidth,
+ monitor,
+ mtu,
+ name,
+ server
+ ) VALUES
+ `
+ ipQry := `
+ INSERT INTO ip_address (
+ address,
+ gateway,
+ interface,
+ server,
+ service_address
+ ) VALUES
+ `
+
+ ifaceQueryParts := make([]string, 0, len(interfaces))
+ ipQueryParts := make([]string, 0, len(interfaces))
+ ifaceArgs := make([]interface{}, 0, len(interfaces))
+ ipArgs := make([]interface{}, 0, len(interfaces))
+ for i, iface := range interfaces {
+ argStart := i * 5
+ ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ifaceArgs = append(ifaceArgs, iface.MaxBandwidth,
iface.Monitor, iface.MTU, iface.Name, id)
+ for _, ip := range iface.IPAddresses {
+ argStart = len(ipArgs)
+ ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ipArgs = append(ipArgs, ip.Address, ip.Gateway,
iface.Name, id, ip.ServiceAddress)
}
}
- current := TOServer{}
- err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE
sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t)
+ ifaceQry += strings.Join(ifaceQueryParts, ",")
+ log.Debugf("Inserting interfaces for new server, query is: %s",
ifaceQry)
+
+ ifaceRows, err := tx.Query(ifaceQry, ifaceArgs...)
if err != nil {
return api.ParseDBError(err)
}
- defaultIsService := true
- if s.IPIsService == nil {
- if current.IPIsService != nil {
- s.IPIsService = current.IPIsService
- } else {
- s.IPIsService = &defaultIsService
+ defer ifaceRows.Close()
+ insertedIfaces := 0
+ for ifaceRows.Next() {
+ insertedIfaces++
+ }
+ log.Debugf("Inserted %d interfaces", insertedIfaces)
+
+ ipQry += strings.Join(ipQueryParts, ",")
+ log.Debugf("Inserting IP addresses for new server, query is: %s", ipQry)
+
+ ipRows, err := tx.Query(ipQry, ipArgs...)
Review comment:
Similar to the previous comment, this seems like it should be an `Exec`.
It's also a bit of a red flag to not iterate over all of the returned `Rows`
since later queries in the same transaction will fail if there are `Rows` that
haven't been `Next()`'d from a previous query in the transaction.
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -359,267 +737,482 @@ WHERE s.id IN (` + edgeIDs + `)))
return mids, nil, nil, http.StatusOK
}
-func (s *TOServer) Update() (error, error, int) {
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
- }
- // see if type changed
- typeID := -1
- // see if cdn changed
- cdnId := -1
-
- if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server
WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil {
+func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx)
(error, error, int) {
+ // see if cdn or type changed
+ var cdnID int
+ var typeID int
+ if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1",
*server.ID).Scan(&typeID, &cdnID); err != nil {
if err == sql.ErrNoRows {
- return errors.New("no server found with this id"), nil,
http.StatusNotFound
+ return errors.New("no server found with this ID"), nil,
http.StatusNotFound
}
return nil, fmt.Errorf("getting current server type: %v", err),
http.StatusInternalServerError
}
- dsIDs := []int64{}
- if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice
FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
+ var dsIDs []int64
+ if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM
deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("getting server assigned delivery
services: %v", err), http.StatusInternalServerError
}
+ // If type is changing ensure it isn't assigned to any DSes.
+ if typeID != *server.TypeID {
+ if len(dsIDs) != 0 {
+ return errors.New("server type can not be updated when
it is currently assigned to Delivery Services"), nil, http.StatusConflict
+ }
+ }
// Check to see if the user is trying to change the CDN of a server,
which is already linked with a DS
- if cdnId != *s.CDNID && len(dsIDs) != 0 {
+ if cdnID != *server.CDNID && len(dsIDs) != 0 {
return errors.New("server cdn can not be updated when it is
currently assigned to delivery services"), nil, http.StatusConflict
}
- // If type is changing ensure it isn't assigned to any DSes.
- if typeID != *s.TypeID {
- if len(dsIDs) != 0 {
- return errors.New("server type can not be updated when
it is currently assigned to delivery services"), nil, http.StatusConflict
+
+ return nil, nil, http.StatusOK
+}
+
+func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx)
(error, error, int) {
+ ifaceQry := `
+ INSERT INTO interface (
+ max_bandwidth,
+ monitor,
+ mtu,
+ name,
+ server
+ ) VALUES
+ `
+ ipQry := `
+ INSERT INTO ip_address (
+ address,
+ gateway,
+ interface,
+ server,
+ service_address
+ ) VALUES
+ `
+
+ ifaceQueryParts := make([]string, 0, len(interfaces))
+ ipQueryParts := make([]string, 0, len(interfaces))
+ ifaceArgs := make([]interface{}, 0, len(interfaces))
+ ipArgs := make([]interface{}, 0, len(interfaces))
+ for i, iface := range interfaces {
+ argStart := i * 5
+ ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ifaceArgs = append(ifaceArgs, iface.MaxBandwidth,
iface.Monitor, iface.MTU, iface.Name, id)
+ for _, ip := range iface.IPAddresses {
+ argStart = len(ipArgs)
+ ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ipArgs = append(ipArgs, ip.Address, ip.Gateway,
iface.Name, id, ip.ServiceAddress)
}
}
- current := TOServer{}
- err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE
sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t)
+ ifaceQry += strings.Join(ifaceQueryParts, ",")
+ log.Debugf("Inserting interfaces for new server, query is: %s",
ifaceQry)
+
+ ifaceRows, err := tx.Query(ifaceQry, ifaceArgs...)
if err != nil {
return api.ParseDBError(err)
}
- defaultIsService := true
- if s.IPIsService == nil {
- if current.IPIsService != nil {
- s.IPIsService = current.IPIsService
- } else {
- s.IPIsService = &defaultIsService
+ defer ifaceRows.Close()
+ insertedIfaces := 0
+ for ifaceRows.Next() {
+ insertedIfaces++
+ }
+ log.Debugf("Inserted %d interfaces", insertedIfaces)
+
+ ipQry += strings.Join(ipQueryParts, ",")
+ log.Debugf("Inserting IP addresses for new server, query is: %s", ipQry)
+
+ ipRows, err := tx.Query(ipQry, ipArgs...)
+ if err != nil {
+ return api.ParseDBError(err)
+ }
+ defer ipRows.Close()
+
+ return nil, nil, http.StatusOK
+}
+
+func deleteInterfaces(id int, tx *sql.Tx) (error, error, int) {
+ if err := tx.QueryRow(deleteIPsQuery, id).Scan(); err != nil && err !=
sql.ErrNoRows {
Review comment:
Similar to the previous comment, this seems like it should be an `Exec`
since the `Scan()` does nothing.
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -359,267 +737,482 @@ WHERE s.id IN (` + edgeIDs + `)))
return mids, nil, nil, http.StatusOK
}
-func (s *TOServer) Update() (error, error, int) {
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
- }
- // see if type changed
- typeID := -1
- // see if cdn changed
- cdnId := -1
-
- if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server
WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil {
+func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx)
(error, error, int) {
+ // see if cdn or type changed
+ var cdnID int
+ var typeID int
+ if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1",
*server.ID).Scan(&typeID, &cdnID); err != nil {
if err == sql.ErrNoRows {
- return errors.New("no server found with this id"), nil,
http.StatusNotFound
+ return errors.New("no server found with this ID"), nil,
http.StatusNotFound
}
return nil, fmt.Errorf("getting current server type: %v", err),
http.StatusInternalServerError
}
- dsIDs := []int64{}
- if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice
FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
+ var dsIDs []int64
+ if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM
deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("getting server assigned delivery
services: %v", err), http.StatusInternalServerError
}
+ // If type is changing ensure it isn't assigned to any DSes.
+ if typeID != *server.TypeID {
+ if len(dsIDs) != 0 {
+ return errors.New("server type can not be updated when
it is currently assigned to Delivery Services"), nil, http.StatusConflict
+ }
+ }
// Check to see if the user is trying to change the CDN of a server,
which is already linked with a DS
- if cdnId != *s.CDNID && len(dsIDs) != 0 {
+ if cdnID != *server.CDNID && len(dsIDs) != 0 {
return errors.New("server cdn can not be updated when it is
currently assigned to delivery services"), nil, http.StatusConflict
}
- // If type is changing ensure it isn't assigned to any DSes.
- if typeID != *s.TypeID {
- if len(dsIDs) != 0 {
- return errors.New("server type can not be updated when
it is currently assigned to delivery services"), nil, http.StatusConflict
+
+ return nil, nil, http.StatusOK
+}
+
+func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx)
(error, error, int) {
+ ifaceQry := `
+ INSERT INTO interface (
+ max_bandwidth,
+ monitor,
+ mtu,
+ name,
+ server
+ ) VALUES
+ `
+ ipQry := `
+ INSERT INTO ip_address (
+ address,
+ gateway,
+ interface,
+ server,
+ service_address
+ ) VALUES
+ `
+
+ ifaceQueryParts := make([]string, 0, len(interfaces))
+ ipQueryParts := make([]string, 0, len(interfaces))
+ ifaceArgs := make([]interface{}, 0, len(interfaces))
+ ipArgs := make([]interface{}, 0, len(interfaces))
+ for i, iface := range interfaces {
+ argStart := i * 5
+ ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ifaceArgs = append(ifaceArgs, iface.MaxBandwidth,
iface.Monitor, iface.MTU, iface.Name, id)
+ for _, ip := range iface.IPAddresses {
+ argStart = len(ipArgs)
+ ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ipArgs = append(ipArgs, ip.Address, ip.Gateway,
iface.Name, id, ip.ServiceAddress)
}
}
- current := TOServer{}
- err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE
sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t)
+ ifaceQry += strings.Join(ifaceQueryParts, ",")
+ log.Debugf("Inserting interfaces for new server, query is: %s",
ifaceQry)
+
+ ifaceRows, err := tx.Query(ifaceQry, ifaceArgs...)
if err != nil {
return api.ParseDBError(err)
}
- defaultIsService := true
- if s.IPIsService == nil {
- if current.IPIsService != nil {
- s.IPIsService = current.IPIsService
- } else {
- s.IPIsService = &defaultIsService
+ defer ifaceRows.Close()
+ insertedIfaces := 0
+ for ifaceRows.Next() {
+ insertedIfaces++
+ }
+ log.Debugf("Inserted %d interfaces", insertedIfaces)
+
+ ipQry += strings.Join(ipQueryParts, ",")
+ log.Debugf("Inserting IP addresses for new server, query is: %s", ipQry)
+
+ ipRows, err := tx.Query(ipQry, ipArgs...)
+ if err != nil {
+ return api.ParseDBError(err)
+ }
+ defer ipRows.Close()
+
+ return nil, nil, http.StatusOK
+}
+
+func deleteInterfaces(id int, tx *sql.Tx) (error, error, int) {
+ if err := tx.QueryRow(deleteIPsQuery, id).Scan(); err != nil && err !=
sql.ErrNoRows {
+ return api.ParseDBError(err)
+ }
+
+ if err := tx.QueryRow(deleteInterfacesQuery, id).Scan(); err != nil &&
err != sql.ErrNoRows {
Review comment:
Similar to the previous comment, this seems like it should be an `Exec`
since the `Scan()` does nothing.
##########
File path: traffic_ops/traffic_ops_golang/server/servers.go
##########
@@ -359,267 +737,482 @@ WHERE s.id IN (` + edgeIDs + `)))
return mids, nil, nil, http.StatusOK
}
-func (s *TOServer) Update() (error, error, int) {
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
- }
- // see if type changed
- typeID := -1
- // see if cdn changed
- cdnId := -1
-
- if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server
WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil {
+func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx)
(error, error, int) {
+ // see if cdn or type changed
+ var cdnID int
+ var typeID int
+ if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1",
*server.ID).Scan(&typeID, &cdnID); err != nil {
if err == sql.ErrNoRows {
- return errors.New("no server found with this id"), nil,
http.StatusNotFound
+ return errors.New("no server found with this ID"), nil,
http.StatusNotFound
}
return nil, fmt.Errorf("getting current server type: %v", err),
http.StatusInternalServerError
}
- dsIDs := []int64{}
- if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice
FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
+ var dsIDs []int64
+ if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM
deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs));
err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("getting server assigned delivery
services: %v", err), http.StatusInternalServerError
}
+ // If type is changing ensure it isn't assigned to any DSes.
+ if typeID != *server.TypeID {
+ if len(dsIDs) != 0 {
+ return errors.New("server type can not be updated when
it is currently assigned to Delivery Services"), nil, http.StatusConflict
+ }
+ }
// Check to see if the user is trying to change the CDN of a server,
which is already linked with a DS
- if cdnId != *s.CDNID && len(dsIDs) != 0 {
+ if cdnID != *server.CDNID && len(dsIDs) != 0 {
return errors.New("server cdn can not be updated when it is
currently assigned to delivery services"), nil, http.StatusConflict
}
- // If type is changing ensure it isn't assigned to any DSes.
- if typeID != *s.TypeID {
- if len(dsIDs) != 0 {
- return errors.New("server type can not be updated when
it is currently assigned to delivery services"), nil, http.StatusConflict
+
+ return nil, nil, http.StatusOK
+}
+
+func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx)
(error, error, int) {
+ ifaceQry := `
+ INSERT INTO interface (
+ max_bandwidth,
+ monitor,
+ mtu,
+ name,
+ server
+ ) VALUES
+ `
+ ipQry := `
+ INSERT INTO ip_address (
+ address,
+ gateway,
+ interface,
+ server,
+ service_address
+ ) VALUES
+ `
+
+ ifaceQueryParts := make([]string, 0, len(interfaces))
+ ipQueryParts := make([]string, 0, len(interfaces))
+ ifaceArgs := make([]interface{}, 0, len(interfaces))
+ ipArgs := make([]interface{}, 0, len(interfaces))
+ for i, iface := range interfaces {
+ argStart := i * 5
+ ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ifaceArgs = append(ifaceArgs, iface.MaxBandwidth,
iface.Monitor, iface.MTU, iface.Name, id)
+ for _, ip := range iface.IPAddresses {
+ argStart = len(ipArgs)
+ ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d,
$%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4,
argStart+5))
+ ipArgs = append(ipArgs, ip.Address, ip.Gateway,
iface.Name, id, ip.ServiceAddress)
}
}
- current := TOServer{}
- err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE
sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t)
+ ifaceQry += strings.Join(ifaceQueryParts, ",")
+ log.Debugf("Inserting interfaces for new server, query is: %s",
ifaceQry)
+
+ ifaceRows, err := tx.Query(ifaceQry, ifaceArgs...)
if err != nil {
return api.ParseDBError(err)
}
- defaultIsService := true
- if s.IPIsService == nil {
- if current.IPIsService != nil {
- s.IPIsService = current.IPIsService
- } else {
- s.IPIsService = &defaultIsService
+ defer ifaceRows.Close()
+ insertedIfaces := 0
+ for ifaceRows.Next() {
+ insertedIfaces++
+ }
+ log.Debugf("Inserted %d interfaces", insertedIfaces)
+
+ ipQry += strings.Join(ipQueryParts, ",")
+ log.Debugf("Inserting IP addresses for new server, query is: %s", ipQry)
+
+ ipRows, err := tx.Query(ipQry, ipArgs...)
+ if err != nil {
+ return api.ParseDBError(err)
+ }
+ defer ipRows.Close()
+
+ return nil, nil, http.StatusOK
+}
+
+func deleteInterfaces(id int, tx *sql.Tx) (error, error, int) {
+ if err := tx.QueryRow(deleteIPsQuery, id).Scan(); err != nil && err !=
sql.ErrNoRows {
+ return api.ParseDBError(err)
+ }
+
+ if err := tx.QueryRow(deleteInterfacesQuery, id).Scan(); err != nil &&
err != sql.ErrNoRows {
+ return api.ParseDBError(err)
+ }
+
+ return nil, nil, http.StatusOK
+}
+
+func Update(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"},
[]string{"id"})
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ var server tc.ServerNullableV2
+ var interfaces []tc.ServerInterfaceInfo
+ if inf.Version.Major >= 3 {
+ var newServer tc.ServerNullable
+ if err := json.NewDecoder(r.Body).Decode(&newServer); err !=
nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ serviceInterface, err := validateV3(newServer, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ server, err = newServer.ToServerV2()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("converting v3 server to v2 for update: %v", err))
+ return
+ }
+ server.InterfaceName = util.StrPtr(serviceInterface)
+ interfaces = newServer.Interfaces
+ } else if inf.Version.Major == 2 {
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ err := validateV2(&server, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ interfaces, err =
server.LegacyInterfaceDetails.ToInterfaces(*server.IPIsService,
*server.IP6IsService)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("converting server legacy interfaces to interface array: %v",
err))
+ return
+ }
+ } else {
+ var legacyServer tc.ServerNullableV11
+ if err := json.NewDecoder(r.Body).Decode(&legacyServer); err !=
nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ err := validateV1(legacyServer, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ interfaces, err =
legacyServer.LegacyInterfaceDetails.ToInterfaces(true, true)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("converting server legacy interfaces to interface array: %v",
err))
+ return
+ }
+ server = tc.ServerNullableV2{
+ ServerNullableV11: legacyServer,
+ IPIsService: util.BoolPtr(true),
+ IP6IsService: util.BoolPtr(true),
}
}
- if s.IP6IsService == nil {
- if current.IP6IsService != nil {
- s.IP6IsService = current.IP6IsService
- } else {
- s.IP6IsService = &defaultIsService
+
+ server.ID = new(int)
+ *server.ID = inf.IntParams["id"]
+
+ if userErr, sysErr, errCode =
checkTypeChangeSafety(server.CommonServerProperties, inf.Tx); userErr != nil ||
sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ rows, err := inf.Tx.NamedQuery(updateQuery, server)
+ if err != nil {
+ userErr, sysErr, errCode = api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer rows.Close()
+
+ rowsAffected := 0
+ for rows.Next() {
+ if err := rows.StructScan(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusNotFound, nil,
fmt.Errorf("scanning lastUpdated from server insert: %v", err))
+ return
}
+ rowsAffected++
+ }
+
+ if rowsAffected < 1 {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no
server found with this id"), nil)
+ return
+ }
+ if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("update for server #%d affected too many rows (%d)", *server.ID,
rowsAffected))
+ return
+ }
+
+ if userErr, sysErr, errCode = deleteInterfaces(inf.IntParams["id"],
tx); userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if userErr, sysErr, errCode = createInterfaces(inf.IntParams["id"],
interfaces, tx); userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if inf.Version.Major >= 3 {
+ api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated",
tc.ServerNullable{CommonServerProperties: server.CommonServerProperties,
Interfaces: interfaces})
+ } else if inf.Version.Minor <= 1 {
+ api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated",
server.ServerNullableV11)
+ } else {
+ api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated",
server)
}
- return api.GenericUpdate(s)
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: updated",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-func (s *TOServer) Create() (error, error, int) {
- // TODO put in Validate()
- if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
- s.IP6Address = nil
+func createV1(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
+ var server tc.ServerNullableV11
+
+ tx := inf.Tx.Tx
+
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
}
- if s.XMPPID == nil || *s.XMPPID == "" {
- hostName := *s.HostName
- s.XMPPID = &hostName
+
+ if err := validateV1(server, tx); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
}
- // default the is service field to true if omitted and to upgrade
version < 1.4
- defaultIsService := true
- if s.IPIsService == nil {
- s.IPIsService = &defaultIsService
+ resultRows, err := inf.Tx.NamedQuery(insertQuery, server)
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
}
- if s.IP6IsService == nil {
- s.IP6IsService = &defaultIsService
+ defer resultRows.Close()
+
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err :=
resultRows.StructScan(&server.CommonServerProperties); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("server create scanning: %v", err))
+ return
+ }
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("server create: no server was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from server insert"))
}
- return api.GenericCreate(s)
-}
+ ifaces, err := server.LegacyInterfaceDetails.ToInterfaces(true, true)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ }
-func (s *TOServer) Delete() (error, error, int) { return api.GenericDelete(s) }
+ if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces,
tx); err != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
-func selectV20UpdatesQuery() string {
- return `SELECT
-sv.ip_address_is_service,
-sv.ip6_address_is_service
-FROM
- server sv`
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server)
+
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-func selectQuery() string {
- const JumboFrameBPS = 9000
- return `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_address_is_service,
-s.ip6_gateway,
-s.ip_address,
-s.ip_address_is_service,
-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`
+func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
+ var server tc.ServerNullableV2
+
+ tx := inf.Tx.Tx
+
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ if err := validateV2(&server, tx); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ resultRows, err := inf.Tx.NamedQuery(insertQuery, server)
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer resultRows.Close()
+
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err :=
resultRows.StructScan(&server.CommonServerProperties); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("server create scanning: %v", err))
+ return
+ }
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("server create: no server was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from server insert"))
+ }
+
+ ifaces, err :=
server.LegacyInterfaceDetails.ToInterfaces(*server.IPIsService,
*server.IP6IsService)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ }
+
+ if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces,
tx); err != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server)
+
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-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_address_is_service,
-ip6_gateway,
-ip_address,
-ip_address_is_service,
-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,
-xmpp_id,
-xmpp_passwd
-) 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_address_is_service,
-:ip6_gateway,
-:ip_address,
-:ip_address_is_service,
-: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,
-:xmpp_id,
-:xmpp_passwd
-) RETURNING id,last_updated`
- return query
+func createV3(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
+ var server tc.ServerNullable
+
+ tx := inf.Tx.Tx
+
+ if err := json.NewDecoder(r.Body).Decode(&server); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ serviceInterface, err := validateV3(server, tx)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+
+ v2Server, err := server.ToServerV2()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+
+ v2Server.InterfaceName = &serviceInterface
+
+ resultRows, err := inf.Tx.NamedQuery(insertQuery, v2Server)
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer resultRows.Close()
+
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err :=
resultRows.StructScan(&server.CommonServerProperties); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("server create scanning: %v", err))
+ return
+ }
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("server create: no server was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from server insert"))
+ return
+ }
+
+ userErr, sysErr, errCode := createInterfaces(*server.ID,
server.Interfaces, tx)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server)
+
+ changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created",
*server.HostName, *server.DomainName, *server.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
-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_address_is_service=:ip6_address_is_service,
-ip6_gateway=:ip6_gateway,
-ip_address=:ip_address,
-ip_address_is_service=:ip_address_is_service,
-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,
-xmpp_id=:xmpp_id,
-xmpp_passwd=:xmpp_passwd
-WHERE id=:id RETURNING last_updated`
- return query
+func Create(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ switch {
+ case inf.Version.Major <= 1:
+ createV1(inf, w, r)
+ case inf.Version.Major == 2:
+ createV2(inf, w, r)
+ default:
+ createV3(inf, w, r)
+ }
}
-func deleteQuery() string {
- return `DELETE FROM server WHERE id = :id`
+func Delete(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"},
[]string{"id"})
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ id := inf.IntParams["id"]
+
+ var servers []tc.ServerNullable
+ servers, _, userErr, sysErr, errCode =
getServers(map[string]string{"id": inf.Params["id"]}, inf.Tx, inf.User)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ if len(servers) < 1 {
+ api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no
server exists by id #%d", id), nil)
+ return
+ }
+ if len(servers) > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("there are somehow two servers with id %d - cannot delete", id))
+ return
+ }
+
+ userErr, sysErr, errCode = deleteInterfaces(id, tx)
Review comment:
This seems like it should be handled via `ON DELETE CASCADE` on the
interface/ip_address tables, but I see that they are actually `ON DELETE
RESTRICT`. At this point I don't think it's a big enough deal to require
modifying the db migration.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]