This is an automated email from the ASF dual-hosted git repository.
zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 6cab395c8c TO Fixes Region V5 apis to respond with RFC3339 date/time
Format (#7698)
6cab395c8c is described below
commit 6cab395c8c2ca56147a274721ebe79ad71ed2a9b
Author: Kannan.G.B <[email protected]>
AuthorDate: Wed Aug 9 22:29:16 2023 +0530
TO Fixes Region V5 apis to respond with RFC3339 date/time Format (#7698)
* region v5 api
* change log and doc
* region comments addresed
* response error fix
* comments addressed
* comment addressed
* removed stray pipe
---------
Co-authored-by: Zach Hoffman <[email protected]>
---
CHANGELOG.md | 1 +
docs/source/api/v5/regions.rst | 25 +-
docs/source/api/v5/regions_id.rst | 15 +-
lib/go-tc/regions.go | 24 ++
traffic_ops/testing/api/v5/regions_test.go | 38 ++-
traffic_ops/testing/api/v5/traffic_control_test.go | 2 +-
traffic_ops/traffic_ops_golang/region/regions.go | 264 +++++++++++++++++++++
traffic_ops/traffic_ops_golang/routing/routes.go | 8 +-
traffic_ops/v5-client/region.go | 8 +-
9 files changed, 330 insertions(+), 55 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f985538b42..cf9ab93930 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -145,6 +145,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- [#7628](https://github.com/apache/trafficcontrol/pull/7628) *Traffic Ops*
Fixes an issue where certificate chain validation failed based on leading or
trailing whitespace.
- [#7596](https://github.com/apache/trafficcontrol/pull/7596) *Traffic Ops*
Fixes `federation_resolvers` v5 apis to respond with `RFC3339` date/time Format.
- [#7660](https://github.com/apache/trafficcontrol/pull/7660) *Traffic Ops*
Fixes `deliveryServices` v5 apis to respond with `RFC3339` date/time Format.
+- [#7698](https://github.com/apache/trafficcontrol/pull/7698) *Traffic Ops*
Fixes `region` v5 apis to respond with `RFC3339` date/time Format.
- [#7686](https://github.com/apache/trafficcontrol/pull/7686) *Traffic Ops*
Fixes secured parameters being visible when role has proper permissions.
- [#7697](https://github.com/apache/trafficcontrol/pull/7697) *Traffic Ops*
Fixes `iloPassword` and `xmppPassword` checking for priv-level instead of using
permissions.
diff --git a/docs/source/api/v5/regions.rst b/docs/source/api/v5/regions.rst
index 6f13234871..13a8984168 100644
--- a/docs/source/api/v5/regions.rst
+++ b/docs/source/api/v5/regions.rst
@@ -41,8 +41,7 @@ Request Structure
+-----------+----------+---------------------------------------------------------------------------------------------------------------+
| name | no | Filter :term:`Regions` by name
|
+-----------+----------+---------------------------------------------------------------------------------------------------------------+
- | orderby | no | Choose the ordering of the results - must be
the name of one of the fields of the objects in the ``response`` |
- | | | array
|
+ | orderby | no | Choose the ordering of the results - either
one of them "division", "id", "name" |
+-----------+----------+---------------------------------------------------------------------------------------------------------------+
| sortOrder | no | Changes the order of sorting. Either ascending
(default or "asc") or descending ("desc") |
+-----------+----------+---------------------------------------------------------------------------------------------------------------+
@@ -67,9 +66,9 @@ Request Structure
Response Structure
-------------------
:divisionName: The name of the division which contains this region
-:divisionId: The integral, unique identifier of the division which contains
this region
+:division: The integral, unique identifier of the division which contains
this region
:id: An integral, unique identifier for this region
-:lastUpdated: The date and time at which this region was last updated, in
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this region was last updated in
:rfc:`3339` Format
:name: The region name
.. code-block:: http
@@ -92,7 +91,7 @@ Response Structure
"divisionName": "Quebec",
"division": 1,
"id": 2,
- "lastUpdated": "2018-12-05 17:50:58+00",
+ "lastUpdated": "2023-05-25T15:59:33.7096-06:00",
"name": "Montreal"
}
]}
@@ -110,8 +109,7 @@ Creates a new region
Request Structure
-----------------
-:division: The integral, unique identifier of the division which shall
contain the new region\ [1]_
-:divisionName: The name of the division which shall contain the new region\
[1]_
+:division: The integral, unique identifier of the division which shall
contain the new region
:name: The name of the region
.. code-block:: http
@@ -126,19 +124,16 @@ Request Structure
Content-Type: application/json
{
- "name": "Manchester",
- "division": "4",
- "divisionName": "England"
+ "division": 4,
+ "name": "Manchester"
}
-.. [1] The only "division" key that actually matters in the request body is
``division``; ``divisionName`` is not validated and has no effect -
particularly not the effect of re-naming the division - beyond changing the
name in the API response to this request. Subsequent requests will reveal the
true name of the division. Note that if ``divisionName`` is not present in the
request body it will be ``null`` in the response, but again further requests
will show the true division name (prov [...]
-
Response Structure
------------------
:divisionName: The name of the division which contains this region
-:divisionId: The integral, unique identifier of the division which contains
this region
+:division: The integral, unique identifier of the division which contains
this region
:id: An integral, unique identifier for this region
-:lastUpdated: The date and time at which this region was last updated, in
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this region was last updated in
:rfc:`3339` Format
:name: The region name
.. code-block:: http
@@ -166,7 +161,7 @@ Response Structure
"divisionName": "England",
"division": 3,
"id": 5,
- "lastUpdated": "2018-12-06 02:14:45+00",
+ "lastUpdated": "2023-05-25T15:59:33.7096-06:00",
"name": "Manchester"
}}
diff --git a/docs/source/api/v5/regions_id.rst
b/docs/source/api/v5/regions_id.rst
index ed92a29d8c..ca8e8a81d1 100644
--- a/docs/source/api/v5/regions_id.rst
+++ b/docs/source/api/v5/regions_id.rst
@@ -38,8 +38,7 @@ Request Structure
| ID | The integral, unique identifier of the region to update |
+------+---------------------------------------------------------+
-:division: The new integral, unique identifier of the division which shall
contain the region\ [1]_
-:divisionName: The new name of the division which shall contain the region\
[1]_
+:division: The new integral, unique identifier of the division which shall
contain the region
:name: The new name of the region
.. code-block:: http
@@ -54,20 +53,16 @@ Request Structure
Content-Type: application/json
{
- "name": "Leeds",
"division": 3,
- "divisionName": "England"
+ "name": "Leeds"
}
-.. [1] The only "division" key that actually matters in the request body is
``division``; ``divisionName`` is not validated and has no effect -
particularly not the effect of re-naming the division - beyond changing the
name in the API response to this request. Subsequent requests will reveal the
true name of the division. Note that if ``divisionName`` is not present in the
request body it will be ``null`` in the response, but again further requests
will show the true division name (prov [...]
-
-
Response Structure
------------------
:divisionName: The name of the division which contains this region
-:divisionId: The integral, unique identifier of the division which contains
this region
+:division: The integral, unique identifier of the division which contains
this region
:id: An integral, unique identifier for this region
-:lastUpdated: The date and time at which this region was last updated, in
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this region was last updated in
:rfc:`3339` Format
:name: The region name
.. code-block:: http
@@ -95,6 +90,6 @@ Response Structure
"divisionName": "England",
"division": 3,
"id": 5,
- "lastUpdated": "2018-12-06 02:23:40+00",
+ "lastUpdated": "2023-05-26T15:59:33.7096-06:00",
"name": "Leeds"
}}
diff --git a/lib/go-tc/regions.go b/lib/go-tc/regions.go
index fd38082335..5d3a290501 100644
--- a/lib/go-tc/regions.go
+++ b/lib/go-tc/regions.go
@@ -21,6 +21,9 @@ package tc
// RegionsResponse is the type of responses from Traffic Ops to GET requests
// made to its /regions API endpoint.
+
+import "time"
+
type RegionsResponse struct {
Response []Region `json:"response"`
Alerts
@@ -35,6 +38,27 @@ type Region struct {
Name string `json:"name" db:"name"`
}
+// RegionsResponseV5 is an alias for the latest minor version of the major
version 5.
+type RegionsResponseV5 = RegionsResponseV50
+
+// RegionsResponseV50 is response made from /regions API endpoint - in the
latest minor version APIv50.
+type RegionsResponseV50 struct {
+ Response []RegionV5 `json:"response"`
+ Alerts
+}
+
+// RegionV5 is an alias for the latest minor version of the major version 5.
+type RegionV5 = RegionV50
+
+// RegionV50 is a named collection of Physical Locations within a Division in
the latest minor version APIv50.
+type RegionV50 struct {
+ DivisionName string `json:"divisionName"`
+ Division int `json:"division" db:"division"`
+ ID int `json:"id" db:"id"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+ Name string `json:"name" db:"name"`
+}
+
// RegionName is a response to a request to get a region by its name. It
// includes the division that the region is in.
type RegionName struct {
diff --git a/traffic_ops/testing/api/v5/regions_test.go
b/traffic_ops/testing/api/v5/regions_test.go
index e30e7b86fe..f031e542fd 100644
--- a/traffic_ops/testing/api/v5/regions_test.go
+++ b/traffic_ops/testing/api/v5/regions_test.go
@@ -38,7 +38,7 @@ func TestRegions(t *testing.T) {
currentTimeRFC := currentTime.Format(time.RFC1123)
tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
- methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.Region]{
+ methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.RegionV5]{
"GET": {
"NOT MODIFIED when NO CHANGES made": {
ClientSession: TOSession,
@@ -121,10 +121,9 @@ func TestRegions(t *testing.T) {
"POST": {
"NOT FOUND when DIVISION DOESNT EXIST": {
ClientSession: TOSession,
- RequestBody: tc.Region{
- Name: "invalidDivision",
- Division: 99999999,
- DivisionName: "doesntexist",
+ RequestBody: tc.RegionV5{
+ Name: "invalidDivision",
+ Division: 99999999,
},
Expectations:
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
},
@@ -133,10 +132,9 @@ func TestRegions(t *testing.T) {
"OK when VALID request": {
EndpointID: GetRegionID(t,
"cdn-region2"),
ClientSession: TOSession,
- RequestBody: tc.Region{
- Name: "newName",
- Division: GetDivisionID(t,
"cdn-div2")(),
- DivisionName: "cdn-div2",
+ RequestBody: tc.RegionV5{
+ Name: "newName",
+ Division: GetDivisionID(t,
"cdn-div2")(),
},
Expectations:
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
validateRegionsUpdateCreateFields("newName", map[string]interface{}{"Name":
"newName"})),
@@ -145,20 +143,18 @@ func TestRegions(t *testing.T) {
EndpointID: GetRegionID(t,
"region1"),
ClientSession: TOSession,
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince:
{currentTimeRFC}}},
- RequestBody: tc.Region{
- Name: "newName",
- Division: GetDivisionID(t,
"division1")(),
- DivisionName: "division1",
+ RequestBody: tc.RegionV5{
+ Name: "newName",
+ Division: GetDivisionID(t,
"division1")(),
},
Expectations:
utils.CkRequest(utils.HasError(),
utils.HasStatus(http.StatusPreconditionFailed)),
},
"PRECONDITION FAILED when updating with IFMATCH
ETAG Header": {
EndpointID: GetRegionID(t,
"region1"),
ClientSession: TOSession,
- RequestBody: tc.Region{
- Name: "newName",
- Division: GetDivisionID(t,
"division1")(),
- DivisionName: "division1",
+ RequestBody: tc.RegionV5{
+ Name: "newName",
+ Division: GetDivisionID(t,
"division1")(),
},
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfMatch:
{rfc.ETag(currentTime)}}},
Expectations:
utils.CkRequest(utils.HasError(),
utils.HasStatus(http.StatusPreconditionFailed)),
@@ -229,7 +225,7 @@ func TestRegions(t *testing.T) {
func validateRegionsFields(expectedResp map[string]interface{})
utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Regions response to not
be nil.")
- regionResp := resp.([]tc.Region)
+ regionResp := resp.([]tc.RegionV5)
for field, expected := range expectedResp {
for _, region := range regionResp {
switch field {
@@ -264,7 +260,7 @@ func validateRegionsSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Regions response to not
be nil.")
var regionNames []string
- regionResp := resp.([]tc.Region)
+ regionResp := resp.([]tc.RegionV5)
for _, region := range regionResp {
regionNames = append(regionNames, region.Name)
}
@@ -275,7 +271,7 @@ func validateRegionsSort() utils.CkReqFunc {
func validateRegionsDescSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Regions response to not
be nil.")
- regionDescResp := resp.([]tc.Region)
+ regionDescResp := resp.([]tc.RegionV5)
var descSortedList []string
var ascSortedList []string
assert.RequireGreaterOrEqual(t, len(regionDescResp), 2, "Need
at least 2 Regions in Traffic Ops to test desc sort, found: %d",
len(regionDescResp))
@@ -298,7 +294,7 @@ func validateRegionsDescSort() utils.CkReqFunc {
func validateRegionsPagination(paginationParam string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- paginationResp := resp.([]tc.Region)
+ paginationResp := resp.([]tc.RegionV5)
opts := client.NewRequestOptions()
opts.QueryParameters.Set("orderby", "id")
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go
b/traffic_ops/testing/api/v5/traffic_control_test.go
index 1ed3ad0b05..4263f50faa 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -43,7 +43,7 @@ type TrafficControl struct {
Parameters []tc.Parameter
`json:"parameters"`
ProfileParameters []tc.ProfileParameter
`json:"profileParameters"`
PhysLocations []tc.PhysLocationV5
`json:"physLocations"`
- Regions []tc.Region
`json:"regions"`
+ Regions []tc.RegionV5
`json:"regions"`
Roles []tc.RoleV4
`json:"roles"`
Servers []tc.ServerV4
`json:"servers"`
ServerServerCapabilities
[]tc.ServerServerCapability `json:"serverServerCapabilities"`
diff --git a/traffic_ops/traffic_ops_golang/region/regions.go
b/traffic_ops/traffic_ops_golang/region/regions.go
index 79fd9ccbe4..5ce193057d 100644
--- a/traffic_ops/traffic_ops_golang/region/regions.go
+++ b/traffic_ops/traffic_ops_golang/region/regions.go
@@ -20,14 +20,22 @@ package region
*/
import (
+ "database/sql"
+ "encoding/json"
"errors"
"fmt"
"net/http"
"time"
+ "github.com/apache/trafficcontrol/lib/go-log"
"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/dbhelpers"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
+
+ validation "github.com/go-ozzo/ozzo-validation"
)
// we need a type alias to define functions on
@@ -192,3 +200,259 @@ func deleteQuery() string {
query := deleteQueryBase() + ` WHERE id=:id`
return query
}
+
+// Read is the handler for GET requests to Regions of APIv5
+func Read(w http.ResponseWriter, r *http.Request) {
+ var maxTime time.Time
+ var runSecond bool
+
+ inf, sysErr, userErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx
+ if sysErr != nil || userErr != nil {
+ api.HandleErr(w, r, tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "division": {Column: "r.division"},
+ "id": {Column: "r.id", Checker: api.IsInt},
+ "name": {Column: "r.name"},
+ }
+ if _, ok := inf.Params["orderby"]; !ok {
+ inf.Params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ api.HandleErr(w, r, tx.Tx, http.StatusBadRequest,
util.JoinErrs(errs), nil)
+ return
+ }
+ if inf.Config.UseIMS {
+ runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, r.Header,
queryValues, SelectMaxLastUpdatedQuery(where))
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ api.AddLastModifiedHdr(w, maxTime)
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+
+ query := `SELECT r.division, d.name as divisionname, r.id,
r.last_updated, r.name FROM region r
+ JOIN division d ON r.division = d.id` + where +
orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil,
fmt.Errorf("region get: error getting region(s): %w", err))
+ }
+ defer log.Close(rows, "unable to close DB connection")
+
+ typ := tc.RegionV5{}
+ regionList := []tc.RegionV5{}
+ for rows.Next() {
+ if err = rows.Scan(&typ.Division, &typ.DivisionName, &typ.ID,
&typ.LastUpdated, &typ.Name); err != nil {
+ api.HandleErr(w, r, tx.Tx,
http.StatusInternalServerError, nil, fmt.Errorf("error getting region(s): %w",
err))
+ }
+ regionList = append(regionList, typ)
+ }
+
+ api.WriteResp(w, r, regionList)
+ return
+}
+
+// Create region with the passed data for APIv5.
+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
+ rg, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ var exists bool
+ existErr := tx.QueryRow(`SELECT EXISTS(SELECT * from region where name
= $1)`, rg.Name).Scan(&exists)
+
+ if existErr != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if region with name %s exists", existErr,
rg.Name))
+ return
+ }
+ if exists {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("region name '%s' already exists", rg.Name), nil)
+ return
+ }
+
+ query := `INSERT INTO region ( division, name) VALUES ( $1, $2 )
+ RETURNING
+ id,last_updated,
+ (SELECT d.name FROM division d WHERE
d.id = region.division)`
+ err := tx.QueryRow(query, rg.Division, rg.Name).Scan(&rg.ID,
&rg.LastUpdated, &rg.DivisionName)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating region with name: %s", err, rg.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "region is created.")
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, rg)
+ return
+}
+
+// Update a Region for APIv5
+func Update(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"},
[]string{"id"})
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ tx := inf.Tx.Tx
+ rg, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedRegionId := inf.IntParams["id"]
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModified(r.Header, inf.Tx,
requestedRegionId, "region")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ query := `UPDATE region SET division= $1, name= $2 WHERE id= $3
+ RETURNING
+ id,last_updated,
+ (SELECT d.name FROM division d WHERE
d.id = region.division)`
+
+ err := tx.QueryRow(query, rg.Division, rg.Name,
requestedRegionId).Scan(&rg.ID, &rg.LastUpdated, &rg.DivisionName)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("region: %d not found", requestedRegionId), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "region was updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, rg)
+ return
+}
+
+// Delete an Region for APIv5
+func Delete(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()
+
+ requestedRegionId := inf.Params["id"]
+ requestedRegionName := inf.Params["name"]
+
+ var exists bool
+ var noExistsErrMsg string
+
+ if requestedRegionId == "" && requestedRegionName == "" {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("refusing to delete all resources of type Region"), nil)
+ return
+ } else if requestedRegionId != "" && requestedRegionName != "" { //
checking if both id and name are passed
+ existErr := tx.QueryRow(`SELECT EXISTS(SELECT * from region
where id = $1 AND name = $2)`, requestedRegionId,
requestedRegionName).Scan(&exists)
+ noExistsErrMsg = fmt.Sprintf("no region exists by id: %s and
name: %s", requestedRegionId, requestedRegionName)
+ if existErr != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("error: %w, when checking if region with id %s exists",
existErr, requestedRegionId))
+ return
+ }
+ } else if requestedRegionId != "" {
+ existErr := tx.QueryRow(`SELECT EXISTS(SELECT * from region
where id = $1)`, requestedRegionId).Scan(&exists)
+ noExistsErrMsg = fmt.Sprintf("no region exists by id: %s",
requestedRegionId)
+ if existErr != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("error: %w, when checking if region with id %s exists",
existErr, requestedRegionId))
+ return
+ }
+ } else if requestedRegionName != "" {
+ existErr := tx.QueryRow(`SELECT EXISTS(SELECT * from region
where name = $1)`, requestedRegionName).Scan(&exists)
+ noExistsErrMsg = fmt.Sprintf("no region exists by name: %s",
requestedRegionName)
+ if existErr != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, fmt.Errorf("error: %w, when checking if region with name %s exists",
existErr, requestedRegionName))
+ return
+ }
+ if exists {
+ existErr := tx.QueryRow(`SELECT id from region where
name = $1`, requestedRegionName).Scan(&requestedRegionId)
+ if existErr != nil {
+ api.HandleErr(w, r, tx,
http.StatusInternalServerError, nil, fmt.Errorf("error: %w, when checking if
region with name %s exists", existErr, requestedRegionName))
+ return
+ }
+ }
+ }
+
+ if !exists {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf(noExistsErrMsg), nil)
+ return
+ }
+
+ res, err := tx.Exec("DELETE FROM region WHERE id = $1",
requestedRegionId)
+
+ if err != nil {
+ userErr, sysErr, errCode := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ rowsAffected, err := res.RowsAffected()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("determining rows affected for delete region: %w", err))
+ return
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("no rows deleted for region"), nil)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "region was deleted.")
+ api.WriteAlerts(w, r, http.StatusOK, alerts)
+ return
+}
+
+// readAndValidateJsonStruct reads json body and validates json fields
+func readAndValidateJsonStruct(r *http.Request) (tc.RegionV5, error) {
+ var region tc.RegionV5
+ if err := json.NewDecoder(r.Body).Decode(®ion); err != nil {
+ userErr := fmt.Errorf("error decoding POST request body into
RegionV5 struct %w", err)
+ return region, userErr
+ }
+
+ // validate JSON body
+ errs := tovalidate.ToErrors(validation.Errors{
+ "division": validation.Validate(region.Division,
validation.NotNil, validation.Min(0)),
+ "name": validation.Validate(region.Name,
validation.Required),
+ })
+ if len(errs) > 0 {
+ userErr := util.JoinErrs(errs)
+ return region, userErr
+ }
+ return region, nil
+}
+
+// SelectMaxLastUpdatedQuery used for TryIfModifiedSinceQuery()
+func SelectMaxLastUpdatedQuery(where string) string {
+ return `SELECT max(t) from (
+ SELECT max(r.last_updated) as t FROM region r
+JOIN division d ON r.division = d.id ` + where +
+ ` UNION ALL
+ select max(last_updated) as t from last_deleted l where
l.table_name='region') as res`
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go
b/traffic_ops/traffic_ops_golang/routing/routes.go
index f62a77db47..26b5a0c171 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -278,10 +278,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `profiles/name/{new_profile}/copy/{existing_profile}`,
Handler: profile.CopyProfileHandler, RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:CREATE",
"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares:
nil, ID: 40614320931},
//Region: CRUDs
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `regions/?$`, Handler:
api.ReadHandler(®ion.TORegion{}), RequiredPrivLevel: auth.PrivLevelReadOnly,
RequiredPermissions: []string{"REGION:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 41003708531},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `regions/{id}$`, Handler:
api.UpdateHandler(®ion.TORegion{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:UPDATE",
"REGION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
42230822431},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `regions/?$`, Handler:
api.CreateHandler(®ion.TORegion{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:CREATE",
"REGION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
428833448831},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `regions/?$`, Handler:
api.DeleteHandler(®ion.TORegion{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:DELETE"},
Authenticated: Authenticated, Middlewares: nil, ID: 423262675831},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `regions/?$`, Handler: region.Read, RequiredPrivLevel:
auth.PrivLevelReadOnly, RequiredPermissions: []string{"REGION:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 41003708531},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `regions/{id}$`, Handler: region.Update,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"REGION:UPDATE", "REGION:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 42230822431},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `regions/?$`, Handler: region.Create, RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:CREATE",
"REGION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
428833448831},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `regions/?$`, Handler: region.Delete,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"REGION:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID:
423262675831},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `topologies/?$`, Handler:
api.CreateHandler(&topology.TOTopology{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"TOPOLOGY:CREATE",
"TOPOLOGY:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 48714522211},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `topologies/?$`, Handler:
api.ReadHandler(&topology.TOTopology{}), RequiredPrivLevel:
auth.PrivLevelReadOnly, RequiredPermissions: []string{"TOPOLOGY:READ",
"CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
48714522221},
diff --git a/traffic_ops/v5-client/region.go b/traffic_ops/v5-client/region.go
index 7ef85216bf..e7c2edaa12 100644
--- a/traffic_ops/v5-client/region.go
+++ b/traffic_ops/v5-client/region.go
@@ -28,7 +28,7 @@ import (
const apiRegions = "/regions"
// CreateRegion creates the given Region.
-func (to *Session) CreateRegion(region tc.Region, opts RequestOptions)
(tc.Alerts, toclientlib.ReqInf, error) {
+func (to *Session) CreateRegion(region tc.RegionV5, opts RequestOptions)
(tc.Alerts, toclientlib.ReqInf, error) {
if region.Division == 0 && region.DivisionName != "" {
divisionOpts := NewRequestOptions()
divisionOpts.QueryParameters.Set("name", region.DivisionName)
@@ -47,7 +47,7 @@ func (to *Session) CreateRegion(region tc.Region, opts
RequestOptions) (tc.Alert
}
// UpdateRegion replaces the Region identified by ID with the one provided.
-func (to *Session) UpdateRegion(id int, region tc.Region, opts RequestOptions)
(tc.Alerts, toclientlib.ReqInf, error) {
+func (to *Session) UpdateRegion(id int, region tc.RegionV5, opts
RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
route := fmt.Sprintf("%s/%d", apiRegions, id)
var alerts tc.Alerts
reqInf, err := to.put(route, opts, region, &alerts)
@@ -55,8 +55,8 @@ func (to *Session) UpdateRegion(id int, region tc.Region,
opts RequestOptions) (
}
// GetRegions returns all Regions in Traffic Ops.
-func (to *Session) GetRegions(opts RequestOptions) (tc.RegionsResponse,
toclientlib.ReqInf, error) {
- var data tc.RegionsResponse
+func (to *Session) GetRegions(opts RequestOptions) (tc.RegionsResponseV5,
toclientlib.ReqInf, error) {
+ var data tc.RegionsResponseV5
reqInf, err := to.get(apiRegions, opts, &data)
return data, reqInf, err
}