shamrickus commented on code in PR #7691:
URL: https://github.com/apache/trafficcontrol/pull/7691#discussion_r1283312667


##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -396,18 +502,84 @@ func (topology *TOTopology) GetAuditName() string {
        return topology.Name
 }
 
+// Create is the handler for creating a new topology entity in api version 5.0.
+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()
+       tx := inf.Tx.Tx
+       var topology tc.TopologyV5
+       if err := json.NewDecoder(r.Body).Decode(&topology); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       alerts, userErr, sysErr := ValidateTopology(topology, inf)
+       if userErr != nil || sysErr != nil {
+               code := http.StatusBadRequest
+               if sysErr != nil {
+                       code = http.StatusInternalServerError
+               }
+               api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
+               return
+       }
+
+       legacyNodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode := 
checkIfTopologyCanBeAlteredByCurrentUser(inf, legacyNodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       err := tx.QueryRow(insertQuery(), topology.Name, 
topology.Description).Scan(&topology.Name, &topology.Description, 
&topology.LastUpdated)
+       if err != nil {
+               userErr, sysErr, statusCode := api.ParseDBError(err)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+       }
+       topology.LastUpdated, err = 
util.ConvertTimeFormat(*topology.LastUpdated, time.RFC3339)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("could not convert last updated time into rfc3339 format: %s", 
err.Error()))
+               return
+       }
+
+       if userErr, sysErr, statusCode := addNodes(tx, topology.Name, 
topology.Nodes); userErr != nil || sysErr != nil {
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+       }
+
+       if userErr, sysErr, statusCode := addParents(tx, topology.Nodes); 
userErr != nil || sysErr != nil {

Review Comment:
   These variables are already declared and don't need to be redeclared.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -88,6 +90,111 @@ func (topology *TOTopology) GetType() string {
        return "topology"
 }
 
+// DowngradeTopologyNodes downgrades v5 topology nodes into legacy topology 
node structures.
+func DowngradeTopologyNodes(nodes []tc.TopologyNodeV5) []tc.TopologyNode {
+       var legacyNodes []tc.TopologyNode

Review Comment:
   We know the length of `legacyNodes` at this point, so we could `make` them 
here to do the memory allocs at once.



##########
traffic_ops/traffic_ops_golang/topology/validation.go:
##########
@@ -54,6 +56,45 @@ func checkForSelfParents(nodes []tc.TopologyNode, index int) 
error {
        return nil
 }
 
+// checkForEdgeParents returns an error if an index given in the parents 
array, adds a warning + returns a nil error if

Review Comment:
   The first part of this comment `returns an error if an index given in the 
parents array, ...` doesn't make sense.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -419,6 +591,286 @@ func (topology *TOTopology) Create() (error, error, int) {
        return nil, nil, 0
 }
 
+func readTopologies(r *http.Request, useIMS bool) ([]interface{}, error, 
error, int, *time.Time) {
+       var maxTime time.Time
+       var runSecond bool
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               //api.HandleErr(w, r, tx, errCode, userErr, sysErr)

Review Comment:
   Commented line should be removed.



##########
traffic_ops/testing/api/v5/topologies_test.go:
##########
@@ -20,17 +20,16 @@ package v5
  */
 
 import (
-       "net/http"
-       "net/url"
-       "testing"
-       "time"
-
        "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util/assert"
        "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
        "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
        client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
+       "net/http"

Review Comment:
   These imports didn't need to be moved.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -88,6 +90,111 @@ func (topology *TOTopology) GetType() string {
        return "topology"
 }
 
+// DowngradeTopologyNodes downgrades v5 topology nodes into legacy topology 
node structures.
+func DowngradeTopologyNodes(nodes []tc.TopologyNodeV5) []tc.TopologyNode {
+       var legacyNodes []tc.TopologyNode
+       for _, n := range nodes {
+               var legacyNode tc.TopologyNode
+               legacyNode.Id = n.Id
+               legacyNode.Cachegroup = n.Cachegroup
+               legacyNode.Parents = make([]int, 0)

Review Comment:
   We know the length of `Parents` at this point.



##########
traffic_ops/testing/api/v5/topologies_test.go:
##########
@@ -470,42 +469,42 @@ func TestTopologies(t *testing.T) {
                                        topology := testCase.RequestBody
 
                                        switch method {
-                                       case "GET", "GET AFTER CHANGES":
-                                               t.Run(name, func(t *testing.T) {
-                                                       resp, reqInf, err := 
testCase.ClientSession.GetTopologies(testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
-                                                       }
-                                               })
+                                       //case "GET", "GET AFTER CHANGES":

Review Comment:
   This test should either not be commented out or the the commented out line 
should be removed.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -21,8 +21,10 @@ package topology
 
 import (
        "database/sql"
+       "encoding/json"
        "errors"
        "fmt"
+       "github.com/apache/trafficcontrol/lib/go-rfc"

Review Comment:
   Local imports should go below system imports



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -419,6 +591,286 @@ func (topology *TOTopology) Create() (error, error, int) {
        return nil, nil, 0
 }
 
+func readTopologies(r *http.Request, useIMS bool) ([]interface{}, error, 
error, int, *time.Time) {
+       var maxTime time.Time
+       var runSecond bool
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               //api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return nil, userErr, sysErr, errCode, &maxTime
+       }
+       defer inf.Close()
+
+       interfaces := make([]interface{}, 0)
+
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       }
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, 
&maxTime
+       }
+
+       if useIMS {
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, 
r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       return []interface{}{}, nil, nil, 
http.StatusNotModified, &maxTime
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       // Case where we need to run the second query
+       query := selectQuery() + where + orderBy + pagination
+       rows, err := inf.Tx.NamedQuery(query, queryValues)
+       if err != nil {
+               //api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("topology read: querying: "+err.Error()))
+               return nil, nil, errors.New("topology read: querying: " + 
err.Error()), http.StatusInternalServerError, &maxTime
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       topologies := map[string]*tc.TopologyV5{}
+       indices := map[int]int{}
+       for index := 0; rows.Next(); index++ {
+               var (
+                       name, description string
+                       lastUpdated       time.Time
+               )
+               topologyNode := tc.TopologyNodeV5{}
+               topologyNode.Parents = []int{}
+               var parents pq.Int64Array
+               if err = rows.Scan(
+                       &name,
+                       &description,
+                       &lastUpdated,
+                       &topologyNode.Id,
+                       &topologyNode.Cachegroup,
+                       &parents,
+               ); err != nil {
+                       //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("topology read: scanning: 
"+err.Error()))

Review Comment:
   Commented line should be removed.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -396,18 +502,84 @@ func (topology *TOTopology) GetAuditName() string {
        return topology.Name
 }
 
+// Create is the handler for creating a new topology entity in api version 5.0.
+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()
+       tx := inf.Tx.Tx
+       var topology tc.TopologyV5
+       if err := json.NewDecoder(r.Body).Decode(&topology); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       alerts, userErr, sysErr := ValidateTopology(topology, inf)
+       if userErr != nil || sysErr != nil {
+               code := http.StatusBadRequest
+               if sysErr != nil {
+                       code = http.StatusInternalServerError
+               }
+               api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
+               return
+       }
+
+       legacyNodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode := 
checkIfTopologyCanBeAlteredByCurrentUser(inf, legacyNodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       err := tx.QueryRow(insertQuery(), topology.Name, 
topology.Description).Scan(&topology.Name, &topology.Description, 
&topology.LastUpdated)
+       if err != nil {
+               userErr, sysErr, statusCode := api.ParseDBError(err)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+       }
+       topology.LastUpdated, err = 
util.ConvertTimeFormat(*topology.LastUpdated, time.RFC3339)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("could not convert last updated time into rfc3339 format: %s", 
err.Error()))
+               return
+       }
+
+       if userErr, sysErr, statusCode := addNodes(tx, topology.Name, 
topology.Nodes); userErr != nil || sysErr != nil {

Review Comment:
   These variables are already declared and don't need to be redeclared.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -396,18 +502,84 @@ func (topology *TOTopology) GetAuditName() string {
        return topology.Name
 }
 
+// Create is the handler for creating a new topology entity in api version 5.0.
+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()
+       tx := inf.Tx.Tx
+       var topology tc.TopologyV5
+       if err := json.NewDecoder(r.Body).Decode(&topology); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       alerts, userErr, sysErr := ValidateTopology(topology, inf)
+       if userErr != nil || sysErr != nil {
+               code := http.StatusBadRequest
+               if sysErr != nil {
+                       code = http.StatusInternalServerError
+               }
+               api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
+               return
+       }
+
+       legacyNodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode := 
checkIfTopologyCanBeAlteredByCurrentUser(inf, legacyNodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       err := tx.QueryRow(insertQuery(), topology.Name, 
topology.Description).Scan(&topology.Name, &topology.Description, 
&topology.LastUpdated)
+       if err != nil {
+               userErr, sysErr, statusCode := api.ParseDBError(err)

Review Comment:
   These variables are already declared and don't need to be redeclared.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -419,6 +591,286 @@ func (topology *TOTopology) Create() (error, error, int) {
        return nil, nil, 0
 }
 
+func readTopologies(r *http.Request, useIMS bool) ([]interface{}, error, 
error, int, *time.Time) {
+       var maxTime time.Time
+       var runSecond bool
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               //api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return nil, userErr, sysErr, errCode, &maxTime
+       }
+       defer inf.Close()
+
+       interfaces := make([]interface{}, 0)
+
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       }
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, 
&maxTime
+       }
+
+       if useIMS {
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, 
r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       return []interface{}{}, nil, nil, 
http.StatusNotModified, &maxTime
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       // Case where we need to run the second query
+       query := selectQuery() + where + orderBy + pagination
+       rows, err := inf.Tx.NamedQuery(query, queryValues)
+       if err != nil {
+               //api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("topology read: querying: "+err.Error()))
+               return nil, nil, errors.New("topology read: querying: " + 
err.Error()), http.StatusInternalServerError, &maxTime
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       topologies := map[string]*tc.TopologyV5{}
+       indices := map[int]int{}
+       for index := 0; rows.Next(); index++ {
+               var (
+                       name, description string
+                       lastUpdated       time.Time
+               )
+               topologyNode := tc.TopologyNodeV5{}
+               topologyNode.Parents = []int{}
+               var parents pq.Int64Array
+               if err = rows.Scan(
+                       &name,
+                       &description,
+                       &lastUpdated,
+                       &topologyNode.Id,
+                       &topologyNode.Cachegroup,
+                       &parents,
+               ); err != nil {
+                       //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("topology read: scanning: 
"+err.Error()))
+                       return nil, nil, errors.New("topology read: scanning: " 
+ err.Error()), http.StatusInternalServerError, &maxTime
+               }
+               for _, id := range parents {
+                       topologyNode.Parents = append(topologyNode.Parents, 
int(id))
+               }
+               indices[topologyNode.Id] = index
+               if _, exists := topologies[name]; !exists {
+                       topology := tc.TopologyV5{Nodes: []tc.TopologyNodeV5{}}
+                       topologies[name] = &topology
+                       topology.Name = name
+                       topology.Description = description
+                       topology.LastUpdated, err = 
util.ConvertTimeFormat(lastUpdated, time.RFC3339)
+                       if err != nil {
+                               //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("couldn't convert last updated 
time to rfc3339 format: "+err.Error()))
+                               return nil, nil, errors.New("couldn't convert 
last updated time to rfc3339 format: " + err.Error()), 
http.StatusInternalServerError, &maxTime
+                       }
+               }
+               topologies[name].Nodes = append(topologies[name].Nodes, 
topologyNode)
+       }
+
+       for _, topology := range topologies {
+               nodeMap := map[int]int{}
+               for index, node := range topology.Nodes {
+                       nodeMap[node.Id] = index
+               }
+               for _, node := range topology.Nodes {
+                       for parentIndex := 0; parentIndex < len(node.Parents); 
parentIndex++ {
+                               node.Parents[parentIndex] = 
nodeMap[node.Parents[parentIndex]]
+                       }
+               }
+               interfaces = append(interfaces, *topology)
+       }
+       return interfaces, nil, nil, http.StatusOK, &maxTime
+}
+
+// Read is the handler for reading topologies in api version 5.0.
+func Read(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, nil, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       interfaces, userErr, sysErr, statusCode, maxTime := readTopologies(r, 
inf.Config.UseIMS)
+       if statusCode == http.StatusNotModified {
+               api.AddLastModifiedHdr(w, *maxTime)
+               w.WriteHeader(http.StatusNotModified)
+               return
+       }
+       if api.SetLastModifiedHeader(r, inf.Config.UseIMS) {
+               date := maxTime.Format(rfc.LastModifiedFormat)
+               w.Header().Add(rfc.LastModified, date)
+       }
+       api.WriteResp(w, r, interfaces)
+       return
+}
+
+// Delete is the handler for removing a topology entity in api version 5.0.
+func Delete(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, []string{"name"}, 
nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       tx := inf.Tx.Tx
+
+       topologies, userErr, sysErr, statusCode, _ := readTopologies(r, false)
+       if len(topologies) != 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("cannot find exactly 1 topology with the query string provided"), 
nil)
+               return
+       }
+
+       topology := topologies[0].(tc.TopologyV5)
+       nodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode = 
checkIfTopologyCanBeAlteredByCurrentUser(inf, nodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       where, _, _, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, 
map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       })
+       if len(errs) > 0 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
util.JoinErrs(errs), nil)
+               return
+       }
+
+       query := deleteQueryBase() + where
+       result, err := inf.Tx.NamedExec(query, queryValues)
+       if err != nil {
+               userErr, sysErr, statusCode = api.ParseDBError(err)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+       }
+
+       if rowsAffected, err := result.RowsAffected(); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("deleting topology: getting rows affected: "+err.Error()))
+               return
+       } else if rowsAffected < 1 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
errors.New("no topology with that key found"), nil)
+               return
+       } else if rowsAffected > 1 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("topology delete affected too many rows: %d", rowsAffected))
+               return
+       }
+
+       alertsObject := tc.CreateAlerts(tc.SuccessLevel, "topology was 
deleted.")
+       api.WriteAlertsObj(w, r, http.StatusOK, alertsObject, topology)
+
+       changeLogMsg := fmt.Sprintf("TOPOLOGY: %s, ACTION: Deleted topology, 
keys: {name: %s }", topology.Name, topology.Name)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+}
+
+// Update is the handler for modifying a topology entity in api version 5.0.
+func Update(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, []string{"name"}, 
nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       tx := inf.Tx.Tx
+
+       topologies, userErr, sysErr, statusCode, _ := readTopologies(r, false)
+       if len(topologies) != 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("cannot find exactly 1 topology with the query string provided"), 
nil)
+               return
+       }
+       var topology tc.TopologyV5
+       if err := json.NewDecoder(r.Body).Decode(&topology); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       alerts, userErr, sysErr := ValidateTopology(topology, inf)
+       if userErr != nil || sysErr != nil {
+               code := http.StatusBadRequest
+               if sysErr != nil {
+                       code = http.StatusInternalServerError
+               }
+               api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
+               return
+       }
+
+       requestedName := inf.Params["name"]
+       // check if the entity was already updated
+       userErr, sysErr, statusCode = api.CheckIfUnModifiedByName(r.Header, 
inf.Tx, requestedName, "topology")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+       nodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode = 
checkIfTopologyCanBeAlteredByCurrentUser(inf, nodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       oldTopology := topologies[0].(tc.TopologyV5)
+
+       if err := removeParents(tx, oldTopology.Name); err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
err)
+               return
+       }
+
+       var oldNodes, newNodes = map[string]int{}, map[string]int{}
+       for index, node := range oldTopology.Nodes {
+               oldNodes[node.Cachegroup] = index
+       }
+       for index, node := range topology.Nodes {
+               newNodes[node.Cachegroup] = index
+       }
+       var toRemove []string
+       for cachegroupName := range oldNodes {
+               if _, exists := newNodes[cachegroupName]; !exists {
+                       toRemove = append(toRemove, cachegroupName)
+               } else {
+                       topology.Nodes[newNodes[cachegroupName]].Id = 
oldTopology.Nodes[oldNodes[cachegroupName]].Id
+               }
+       }
+
+       if len(toRemove) > 0 {
+               if err := removeNodes(inf.Tx.Tx, oldTopology.Name, &toRemove); 
err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, err)
+                       return
+               }
+       }
+
+       if userErr, sysErr, statusCode := setTopologyDetails(tx, &topology); 
userErr != nil || sysErr != nil {

Review Comment:
   These vars don't need to be redeclared.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -419,6 +591,286 @@ func (topology *TOTopology) Create() (error, error, int) {
        return nil, nil, 0
 }
 
+func readTopologies(r *http.Request, useIMS bool) ([]interface{}, error, 
error, int, *time.Time) {
+       var maxTime time.Time
+       var runSecond bool
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               //api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return nil, userErr, sysErr, errCode, &maxTime
+       }
+       defer inf.Close()
+
+       interfaces := make([]interface{}, 0)
+
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       }
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, 
&maxTime
+       }
+
+       if useIMS {
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, 
r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       return []interface{}{}, nil, nil, 
http.StatusNotModified, &maxTime
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       // Case where we need to run the second query
+       query := selectQuery() + where + orderBy + pagination
+       rows, err := inf.Tx.NamedQuery(query, queryValues)
+       if err != nil {
+               //api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("topology read: querying: "+err.Error()))
+               return nil, nil, errors.New("topology read: querying: " + 
err.Error()), http.StatusInternalServerError, &maxTime
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       topologies := map[string]*tc.TopologyV5{}
+       indices := map[int]int{}
+       for index := 0; rows.Next(); index++ {
+               var (
+                       name, description string
+                       lastUpdated       time.Time
+               )
+               topologyNode := tc.TopologyNodeV5{}
+               topologyNode.Parents = []int{}
+               var parents pq.Int64Array
+               if err = rows.Scan(
+                       &name,
+                       &description,
+                       &lastUpdated,
+                       &topologyNode.Id,
+                       &topologyNode.Cachegroup,
+                       &parents,
+               ); err != nil {
+                       //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("topology read: scanning: 
"+err.Error()))
+                       return nil, nil, errors.New("topology read: scanning: " 
+ err.Error()), http.StatusInternalServerError, &maxTime
+               }
+               for _, id := range parents {
+                       topologyNode.Parents = append(topologyNode.Parents, 
int(id))
+               }
+               indices[topologyNode.Id] = index
+               if _, exists := topologies[name]; !exists {
+                       topology := tc.TopologyV5{Nodes: []tc.TopologyNodeV5{}}
+                       topologies[name] = &topology
+                       topology.Name = name
+                       topology.Description = description
+                       topology.LastUpdated, err = 
util.ConvertTimeFormat(lastUpdated, time.RFC3339)
+                       if err != nil {
+                               //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("couldn't convert last updated 
time to rfc3339 format: "+err.Error()))

Review Comment:
   Commented line should be removed.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -419,6 +591,286 @@ func (topology *TOTopology) Create() (error, error, int) {
        return nil, nil, 0
 }
 
+func readTopologies(r *http.Request, useIMS bool) ([]interface{}, error, 
error, int, *time.Time) {
+       var maxTime time.Time
+       var runSecond bool
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               //api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return nil, userErr, sysErr, errCode, &maxTime
+       }
+       defer inf.Close()
+
+       interfaces := make([]interface{}, 0)
+
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       }
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, 
&maxTime
+       }
+
+       if useIMS {
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, 
r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       return []interface{}{}, nil, nil, 
http.StatusNotModified, &maxTime
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       // Case where we need to run the second query
+       query := selectQuery() + where + orderBy + pagination
+       rows, err := inf.Tx.NamedQuery(query, queryValues)
+       if err != nil {
+               //api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("topology read: querying: "+err.Error()))
+               return nil, nil, errors.New("topology read: querying: " + 
err.Error()), http.StatusInternalServerError, &maxTime
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       topologies := map[string]*tc.TopologyV5{}
+       indices := map[int]int{}
+       for index := 0; rows.Next(); index++ {
+               var (
+                       name, description string
+                       lastUpdated       time.Time
+               )
+               topologyNode := tc.TopologyNodeV5{}
+               topologyNode.Parents = []int{}
+               var parents pq.Int64Array
+               if err = rows.Scan(
+                       &name,
+                       &description,
+                       &lastUpdated,
+                       &topologyNode.Id,
+                       &topologyNode.Cachegroup,
+                       &parents,
+               ); err != nil {
+                       //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("topology read: scanning: 
"+err.Error()))
+                       return nil, nil, errors.New("topology read: scanning: " 
+ err.Error()), http.StatusInternalServerError, &maxTime
+               }
+               for _, id := range parents {
+                       topologyNode.Parents = append(topologyNode.Parents, 
int(id))
+               }
+               indices[topologyNode.Id] = index
+               if _, exists := topologies[name]; !exists {
+                       topology := tc.TopologyV5{Nodes: []tc.TopologyNodeV5{}}
+                       topologies[name] = &topology
+                       topology.Name = name
+                       topology.Description = description
+                       topology.LastUpdated, err = 
util.ConvertTimeFormat(lastUpdated, time.RFC3339)
+                       if err != nil {
+                               //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("couldn't convert last updated 
time to rfc3339 format: "+err.Error()))
+                               return nil, nil, errors.New("couldn't convert 
last updated time to rfc3339 format: " + err.Error()), 
http.StatusInternalServerError, &maxTime
+                       }
+               }
+               topologies[name].Nodes = append(topologies[name].Nodes, 
topologyNode)
+       }
+
+       for _, topology := range topologies {
+               nodeMap := map[int]int{}
+               for index, node := range topology.Nodes {
+                       nodeMap[node.Id] = index
+               }
+               for _, node := range topology.Nodes {
+                       for parentIndex := 0; parentIndex < len(node.Parents); 
parentIndex++ {
+                               node.Parents[parentIndex] = 
nodeMap[node.Parents[parentIndex]]
+                       }
+               }
+               interfaces = append(interfaces, *topology)
+       }
+       return interfaces, nil, nil, http.StatusOK, &maxTime
+}
+
+// Read is the handler for reading topologies in api version 5.0.
+func Read(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, nil, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       interfaces, userErr, sysErr, statusCode, maxTime := readTopologies(r, 
inf.Config.UseIMS)
+       if statusCode == http.StatusNotModified {
+               api.AddLastModifiedHdr(w, *maxTime)
+               w.WriteHeader(http.StatusNotModified)
+               return
+       }
+       if api.SetLastModifiedHeader(r, inf.Config.UseIMS) {
+               date := maxTime.Format(rfc.LastModifiedFormat)
+               w.Header().Add(rfc.LastModified, date)
+       }
+       api.WriteResp(w, r, interfaces)
+       return
+}
+
+// Delete is the handler for removing a topology entity in api version 5.0.
+func Delete(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, []string{"name"}, 
nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       tx := inf.Tx.Tx
+
+       topologies, userErr, sysErr, statusCode, _ := readTopologies(r, false)
+       if len(topologies) != 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("cannot find exactly 1 topology with the query string provided"), 
nil)
+               return
+       }
+
+       topology := topologies[0].(tc.TopologyV5)
+       nodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode = 
checkIfTopologyCanBeAlteredByCurrentUser(inf, nodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       where, _, _, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, 
map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       })
+       if len(errs) > 0 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
util.JoinErrs(errs), nil)
+               return
+       }
+
+       query := deleteQueryBase() + where
+       result, err := inf.Tx.NamedExec(query, queryValues)
+       if err != nil {
+               userErr, sysErr, statusCode = api.ParseDBError(err)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+       }
+
+       if rowsAffected, err := result.RowsAffected(); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("deleting topology: getting rows affected: "+err.Error()))
+               return
+       } else if rowsAffected < 1 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
errors.New("no topology with that key found"), nil)
+               return
+       } else if rowsAffected > 1 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("topology delete affected too many rows: %d", rowsAffected))
+               return
+       }
+
+       alertsObject := tc.CreateAlerts(tc.SuccessLevel, "topology was 
deleted.")
+       api.WriteAlertsObj(w, r, http.StatusOK, alertsObject, topology)
+
+       changeLogMsg := fmt.Sprintf("TOPOLOGY: %s, ACTION: Deleted topology, 
keys: {name: %s }", topology.Name, topology.Name)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+}
+
+// Update is the handler for modifying a topology entity in api version 5.0.
+func Update(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, []string{"name"}, 
nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       tx := inf.Tx.Tx
+
+       topologies, userErr, sysErr, statusCode, _ := readTopologies(r, false)
+       if len(topologies) != 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("cannot find exactly 1 topology with the query string provided"), 
nil)
+               return
+       }
+       var topology tc.TopologyV5
+       if err := json.NewDecoder(r.Body).Decode(&topology); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       alerts, userErr, sysErr := ValidateTopology(topology, inf)
+       if userErr != nil || sysErr != nil {
+               code := http.StatusBadRequest
+               if sysErr != nil {
+                       code = http.StatusInternalServerError
+               }
+               api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
+               return
+       }
+
+       requestedName := inf.Params["name"]
+       // check if the entity was already updated
+       userErr, sysErr, statusCode = api.CheckIfUnModifiedByName(r.Header, 
inf.Tx, requestedName, "topology")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+       nodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode = 
checkIfTopologyCanBeAlteredByCurrentUser(inf, nodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       oldTopology := topologies[0].(tc.TopologyV5)
+
+       if err := removeParents(tx, oldTopology.Name); err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
err)
+               return
+       }
+
+       var oldNodes, newNodes = map[string]int{}, map[string]int{}
+       for index, node := range oldTopology.Nodes {
+               oldNodes[node.Cachegroup] = index
+       }
+       for index, node := range topology.Nodes {
+               newNodes[node.Cachegroup] = index
+       }
+       var toRemove []string
+       for cachegroupName := range oldNodes {
+               if _, exists := newNodes[cachegroupName]; !exists {
+                       toRemove = append(toRemove, cachegroupName)
+               } else {
+                       topology.Nodes[newNodes[cachegroupName]].Id = 
oldTopology.Nodes[oldNodes[cachegroupName]].Id
+               }
+       }
+
+       if len(toRemove) > 0 {
+               if err := removeNodes(inf.Tx.Tx, oldTopology.Name, &toRemove); 
err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, err)
+                       return
+               }
+       }
+
+       if userErr, sysErr, statusCode := setTopologyDetails(tx, &topology); 
userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       if userErr, sysErr, statusCode := addNodes(tx, topology.Name, 
topology.Nodes); userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+       if userErr, sysErr, statusCode := addParents(tx, topology.Nodes); 
userErr != nil || sysErr != nil {

Review Comment:
   These vars don't need to be redeclared.



##########
traffic_ops/traffic_ops_golang/topology/topologies.go:
##########
@@ -419,6 +591,286 @@ func (topology *TOTopology) Create() (error, error, int) {
        return nil, nil, 0
 }
 
+func readTopologies(r *http.Request, useIMS bool) ([]interface{}, error, 
error, int, *time.Time) {
+       var maxTime time.Time
+       var runSecond bool
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               //api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return nil, userErr, sysErr, errCode, &maxTime
+       }
+       defer inf.Close()
+
+       interfaces := make([]interface{}, 0)
+
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       }
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, 
&maxTime
+       }
+
+       if useIMS {
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, 
r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       return []interface{}{}, nil, nil, 
http.StatusNotModified, &maxTime
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       // Case where we need to run the second query
+       query := selectQuery() + where + orderBy + pagination
+       rows, err := inf.Tx.NamedQuery(query, queryValues)
+       if err != nil {
+               //api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("topology read: querying: "+err.Error()))
+               return nil, nil, errors.New("topology read: querying: " + 
err.Error()), http.StatusInternalServerError, &maxTime
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       topologies := map[string]*tc.TopologyV5{}
+       indices := map[int]int{}
+       for index := 0; rows.Next(); index++ {
+               var (
+                       name, description string
+                       lastUpdated       time.Time
+               )
+               topologyNode := tc.TopologyNodeV5{}
+               topologyNode.Parents = []int{}
+               var parents pq.Int64Array
+               if err = rows.Scan(
+                       &name,
+                       &description,
+                       &lastUpdated,
+                       &topologyNode.Id,
+                       &topologyNode.Cachegroup,
+                       &parents,
+               ); err != nil {
+                       //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("topology read: scanning: 
"+err.Error()))
+                       return nil, nil, errors.New("topology read: scanning: " 
+ err.Error()), http.StatusInternalServerError, &maxTime
+               }
+               for _, id := range parents {
+                       topologyNode.Parents = append(topologyNode.Parents, 
int(id))
+               }
+               indices[topologyNode.Id] = index
+               if _, exists := topologies[name]; !exists {
+                       topology := tc.TopologyV5{Nodes: []tc.TopologyNodeV5{}}
+                       topologies[name] = &topology
+                       topology.Name = name
+                       topology.Description = description
+                       topology.LastUpdated, err = 
util.ConvertTimeFormat(lastUpdated, time.RFC3339)
+                       if err != nil {
+                               //api.HandleErr(w, r, tx, 
http.StatusInternalServerError, nil, errors.New("couldn't convert last updated 
time to rfc3339 format: "+err.Error()))
+                               return nil, nil, errors.New("couldn't convert 
last updated time to rfc3339 format: " + err.Error()), 
http.StatusInternalServerError, &maxTime
+                       }
+               }
+               topologies[name].Nodes = append(topologies[name].Nodes, 
topologyNode)
+       }
+
+       for _, topology := range topologies {
+               nodeMap := map[int]int{}
+               for index, node := range topology.Nodes {
+                       nodeMap[node.Id] = index
+               }
+               for _, node := range topology.Nodes {
+                       for parentIndex := 0; parentIndex < len(node.Parents); 
parentIndex++ {
+                               node.Parents[parentIndex] = 
nodeMap[node.Parents[parentIndex]]
+                       }
+               }
+               interfaces = append(interfaces, *topology)
+       }
+       return interfaces, nil, nil, http.StatusOK, &maxTime
+}
+
+// Read is the handler for reading topologies in api version 5.0.
+func Read(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, nil, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       interfaces, userErr, sysErr, statusCode, maxTime := readTopologies(r, 
inf.Config.UseIMS)
+       if statusCode == http.StatusNotModified {
+               api.AddLastModifiedHdr(w, *maxTime)
+               w.WriteHeader(http.StatusNotModified)
+               return
+       }
+       if api.SetLastModifiedHeader(r, inf.Config.UseIMS) {
+               date := maxTime.Format(rfc.LastModifiedFormat)
+               w.Header().Add(rfc.LastModified, date)
+       }
+       api.WriteResp(w, r, interfaces)
+       return
+}
+
+// Delete is the handler for removing a topology entity in api version 5.0.
+func Delete(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, []string{"name"}, 
nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       tx := inf.Tx.Tx
+
+       topologies, userErr, sysErr, statusCode, _ := readTopologies(r, false)
+       if len(topologies) != 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("cannot find exactly 1 topology with the query string provided"), 
nil)
+               return
+       }
+
+       topology := topologies[0].(tc.TopologyV5)
+       nodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode = 
checkIfTopologyCanBeAlteredByCurrentUser(inf, nodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       where, _, _, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, 
map[string]dbhelpers.WhereColumnInfo{
+               "name":        {Column: "t.name"},
+               "description": {Column: "t.description"},
+               "lastUpdated": {Column: "t.last_updated"},
+       })
+       if len(errs) > 0 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
util.JoinErrs(errs), nil)
+               return
+       }
+
+       query := deleteQueryBase() + where
+       result, err := inf.Tx.NamedExec(query, queryValues)
+       if err != nil {
+               userErr, sysErr, statusCode = api.ParseDBError(err)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+       }
+
+       if rowsAffected, err := result.RowsAffected(); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("deleting topology: getting rows affected: "+err.Error()))
+               return
+       } else if rowsAffected < 1 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
errors.New("no topology with that key found"), nil)
+               return
+       } else if rowsAffected > 1 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("topology delete affected too many rows: %d", rowsAffected))
+               return
+       }
+
+       alertsObject := tc.CreateAlerts(tc.SuccessLevel, "topology was 
deleted.")
+       api.WriteAlertsObj(w, r, http.StatusOK, alertsObject, topology)
+
+       changeLogMsg := fmt.Sprintf("TOPOLOGY: %s, ACTION: Deleted topology, 
keys: {name: %s }", topology.Name, topology.Name)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+}
+
+// Update is the handler for modifying a topology entity in api version 5.0.
+func Update(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, statusCode := api.NewInfo(r, []string{"name"}, 
nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       tx := inf.Tx.Tx
+
+       topologies, userErr, sysErr, statusCode, _ := readTopologies(r, false)
+       if len(topologies) != 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("cannot find exactly 1 topology with the query string provided"), 
nil)
+               return
+       }
+       var topology tc.TopologyV5
+       if err := json.NewDecoder(r.Body).Decode(&topology); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       alerts, userErr, sysErr := ValidateTopology(topology, inf)
+       if userErr != nil || sysErr != nil {
+               code := http.StatusBadRequest
+               if sysErr != nil {
+                       code = http.StatusInternalServerError
+               }
+               api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
+               return
+       }
+
+       requestedName := inf.Params["name"]
+       // check if the entity was already updated
+       userErr, sysErr, statusCode = api.CheckIfUnModifiedByName(r.Header, 
inf.Tx, requestedName, "topology")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+       nodes := DowngradeTopologyNodes(topology.Nodes)
+       userErr, sysErr, statusCode = 
checkIfTopologyCanBeAlteredByCurrentUser(inf, nodes)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       oldTopology := topologies[0].(tc.TopologyV5)
+
+       if err := removeParents(tx, oldTopology.Name); err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
err)
+               return
+       }
+
+       var oldNodes, newNodes = map[string]int{}, map[string]int{}
+       for index, node := range oldTopology.Nodes {
+               oldNodes[node.Cachegroup] = index
+       }
+       for index, node := range topology.Nodes {
+               newNodes[node.Cachegroup] = index
+       }
+       var toRemove []string
+       for cachegroupName := range oldNodes {
+               if _, exists := newNodes[cachegroupName]; !exists {
+                       toRemove = append(toRemove, cachegroupName)
+               } else {
+                       topology.Nodes[newNodes[cachegroupName]].Id = 
oldTopology.Nodes[oldNodes[cachegroupName]].Id
+               }
+       }
+
+       if len(toRemove) > 0 {
+               if err := removeNodes(inf.Tx.Tx, oldTopology.Name, &toRemove); 
err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, err)
+                       return
+               }
+       }
+
+       if userErr, sysErr, statusCode := setTopologyDetails(tx, &topology); 
userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       if userErr, sysErr, statusCode := addNodes(tx, topology.Name, 
topology.Nodes); userErr != nil || sysErr != nil {

Review Comment:
   These vars don't need to be redeclared.



##########
traffic_ops/testing/api/v5/topologies_test.go:
##########
@@ -470,42 +469,42 @@ func TestTopologies(t *testing.T) {
                                        topology := testCase.RequestBody
 
                                        switch method {
-                                       case "GET", "GET AFTER CHANGES":
-                                               t.Run(name, func(t *testing.T) {
-                                                       resp, reqInf, err := 
testCase.ClientSession.GetTopologies(testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
-                                                       }
-                                               })
+                                       //case "GET", "GET AFTER CHANGES":
+                                       //      t.Run(name, func(t *testing.T) {
+                                       //              resp, reqInf, err := 
testCase.ClientSession.GetTopologies(testCase.RequestOpts)
+                                       //              for _, check := range 
testCase.Expectations {
+                                       //                      check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                       //              }
+                                       //      })
                                        case "POST":
                                                t.Run(name, func(t *testing.T) {
                                                        resp, reqInf, err := 
testCase.ClientSession.CreateTopology(topology, testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
                                                                check(t, 
reqInf, resp.Response, resp.Alerts, err)
                                                        }
                                                })
-                                       case "PUT":
-                                               t.Run(name, func(t *testing.T) {
-                                                       if _, ok := 
testCase.RequestOpts.QueryParameters["name"]; !ok {
-                                                               t.Fatalf("Query 
Parameter: \"name\" is required for PUT method tests.")
-                                                       }
-                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateTopology(testCase.RequestOpts.QueryParameters["name"][0],
 topology, testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
-                                                       }
-                                               })
-                                       case "DELETE":
-                                               t.Run(name, func(t *testing.T) {
-                                                       if _, ok := 
testCase.RequestOpts.QueryParameters["name"]; !ok {
-                                                               t.Fatalf("Query 
Parameter: \"name\" is required for DELETE method tests.")
-                                                       }
-                                                       alerts, reqInf, err := 
testCase.ClientSession.DeleteTopology(testCase.RequestOpts.QueryParameters["name"][0],
 testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, nil, alerts, err)
-                                                       }
-                                               })
+                                       //case "PUT":

Review Comment:
   This test should either not be commented out or the the commented out line 
should be removed.



-- 
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.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to