You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by de...@apache.org on 2018/06/26 18:54:51 UTC

[trafficcontrol] branch master updated (75ac91e -> 0a117a6)

This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git.


    from 75ac91e  Update profile_parameter.rst
     new 4c6d7d2  add missing return after error
     new 85e13c1  adds db validation
     new 23aa135  add tenants structs
     new 7b7d75f  adds CRUD for tenants
     new cd7960f  add tenants routes
     new ba9f6f0  add tenants API tests
     new f2118fc  backward compatability
     new 1d822da  fix tenant tests
     new 49fddb2  redo check for deleting tenant with children
     new 0c80978  safety check
     new d1e9cc7  test deleting tenant
     new 35e9f40  detect constraint errors when deleting tenants
     new e043ae3  complete tenant test
     new c7cf5d0  add last_updated
     new 0a117a6  use 1.1 for tenants endpoints

The 15 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 lib/go-tc/tenants.go                               |  57 +--
 lib/go-tc/tovalidate/db.go                         | 125 +++++++
 traffic_ops/client/v13/tenant.go                   |  17 +
 .../api/v13/deliveryservice_requests_test.go       |   1 +
 traffic_ops/testing/api/v13/tc-fixtures.json       | 147 +++++++-
 traffic_ops/testing/api/v13/tenants_test.go        | 142 +++++++
 traffic_ops/testing/api/v13/todb.go                |  28 +-
 .../traffic_ops_golang/api/shared_handlers.go      |   1 +
 traffic_ops/traffic_ops_golang/routes.go           |   8 +
 traffic_ops/traffic_ops_golang/tenant/tenancy.go   | 413 +++++++++++++++++++--
 10 files changed, 867 insertions(+), 72 deletions(-)
 create mode 100644 lib/go-tc/tovalidate/db.go
 create mode 100644 traffic_ops/testing/api/v13/tenants_test.go


[trafficcontrol] 12/15: detect constraint errors when deleting tenants

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 35e9f4040f07b91386d7b518b3e21131ffa0ba08
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Fri Jun 22 11:03:36 2018 -0600

    detect constraint errors when deleting tenants
---
 traffic_ops/traffic_ops_golang/tenant/tenancy.go | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index 9736ba8..05de1c0 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -490,7 +490,29 @@ func (ten *TOTenant) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiEr
 	result, err := tx.NamedExec(deleteQuery(), ten)
 	if err != nil {
 		if pqErr, ok := err.(*pq.Error); ok {
-			log.Infof("deleting tenant: %++v", pqErr)
+			err = fmt.Errorf("pqErr is %++v\n", pqErr)
+			var existing string
+			switch pqErr.Table {
+			case "tenant":
+				existing = "child tenants"
+			case "tm_user":
+				existing = "users"
+			case "deliveryservice":
+				existing = "deliveryservices"
+			case "origin":
+				existing = "origins"
+			default:
+				existing = pqErr.Table
+			}
+
+			// another query to get tenant name for the error message
+			name := strconv.Itoa(*ten.ID)
+			if err := db.QueryRow(`SELECT name FROM tenant WHERE id = $1`, *ten.ID).Scan(&name); err != nil {
+				// use ID as a backup for name the error -- this should never happen
+				log.Debugf("error getting tenant name: %++v", err)
+			}
+
+			err = errors.New("Tenant '" + name + "' has " + existing + ". Please update these " + existing + " and retry.")
 			return err, tc.DataConflictError
 		}
 		log.Errorf("received error: %++v from delete execution", err)


[trafficcontrol] 01/15: add missing return after error

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 4c6d7d29b25ebf39f4658a352f4b57dfd8549c3a
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 13:51:00 2018 -0600

    add missing return after error
---
 traffic_ops/traffic_ops_golang/api/shared_handlers.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index c6f645c..c1cc8d3 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -135,6 +135,7 @@ func ReadHandler(typeRef Reader, db *sqlx.DB) http.HandlerFunc {
 		if err != nil {
 			log.Errorf("unable to get parameters from request: %s", err)
 			handleErrs(http.StatusInternalServerError, err)
+			return
 		}
 
 		user, err := auth.GetCurrentUser(ctx)


[trafficcontrol] 09/15: redo check for deleting tenant with children

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 49fddb2cafa76f4bcd09cbd0ab0fc2e785f00d52
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 16:57:55 2018 -0600

    redo check for deleting tenant with children
---
 traffic_ops/traffic_ops_golang/tenant/tenancy.go | 21 ++++-----------------
 1 file changed, 4 insertions(+), 17 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index 5b706d6..9736ba8 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -486,26 +486,13 @@ func (ten *TOTenant) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiEr
 		return tc.DBError, tc.SystemError
 	}
 
-	// if tenant has children, don't allow deletion
-	parentQ := `SELECT COUNT(*) FROM tenant WHERE parent_id = $1`
-	var count int
-	err = tx.QueryRowx(parentQ, *ten.ID).Scan(&count)
-	if err != nil {
-		log.Errorf("received error: %++v from parent query execution", err)
-		return err, tc.SystemError
-	}
-	if count > 0 {
-		name := "unknown"
-		if ten.Name != nil {
-			name = *ten.Name
-		}
-		log.Errorf("Tenant '%s' has children tenant(s); refusing to delete", name)
-		return fmt.Errorf("Tenant '%s' has children tenant(s):  Please update these tenants and retry.", name), tc.ForbiddenError
-	}
-
 	log.Debugf("about to run exec query: %s with tenant: %++v", deleteQuery(), ten)
 	result, err := tx.NamedExec(deleteQuery(), ten)
 	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			log.Infof("deleting tenant: %++v", pqErr)
+			return err, tc.DataConflictError
+		}
 		log.Errorf("received error: %++v from delete execution", err)
 		return tc.DBError, tc.SystemError
 	}


[trafficcontrol] 08/15: fix tenant tests

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 1d822dab81a79419914d60729cfc4975933a750b
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 15:06:02 2018 -0600

    fix tenant tests
---
 lib/go-tc/tenants.go                           | 19 ++++-------
 traffic_ops/client/v13/tenant.go               | 13 +++++++
 traffic_ops/testing/api/v13/tenants_test.go    | 47 +++++++++++---------------
 traffic_ops/testing/api/v13/traffic_control.go |  2 +-
 4 files changed, 40 insertions(+), 41 deletions(-)

diff --git a/lib/go-tc/tenants.go b/lib/go-tc/tenants.go
index 2448822..e825945 100644
--- a/lib/go-tc/tenants.go
+++ b/lib/go-tc/tenants.go
@@ -19,24 +19,17 @@ package tc
  * under the License.
  */
 
-// TenantResponse is the response for a request for a single tenant
-type TenantResponse struct {
-	Response Tenant        `json:"response"`
-	Alerts   []TenantAlert `json:"alerts"`
+// GetTenantsResponse is the response for a request for a group of tenants
+type GetTenantsResponse struct {
+	Response []Tenant `json:"response"`
 }
 
-// GetTenantResponse here to maintain backward compatability
-type GetTenantResponse TenantResponse
-
-// TenantResponse is the response for a request for a group of tenants
-type TenantsResponse struct {
-	Response []Tenant      `json:"response"`
+// TenantResponse is the response to a create/update
+type TenantResponse struct {
+	Response Tenant        `json:"response"`
 	Alerts   []TenantAlert `json:"alerts"`
 }
 
-// GetTenantsResponse here to maintain backward compatability
-type GetTenantsResponse TenantsResponse
-
 // Tenant ...
 type Tenant struct {
 	Active      bool      `json:"active"`
diff --git a/traffic_ops/client/v13/tenant.go b/traffic_ops/client/v13/tenant.go
index 123828e..27bd31a 100644
--- a/traffic_ops/client/v13/tenant.go
+++ b/traffic_ops/client/v13/tenant.go
@@ -17,6 +17,7 @@ package v13
 
 import (
 	"encoding/json"
+	"net/url"
 
 	tc "github.com/apache/trafficcontrol/lib/go-tc"
 )
@@ -43,6 +44,18 @@ func (to *Session) Tenant(id string) (*tc.Tenant, ReqInf, error) {
 	return &data.Response[0], reqInf, nil
 }
 
+// TenantByName gets the Tenant for the name it's passed
+func (to *Session) TenantByName(name string) (*tc.Tenant, ReqInf, error) {
+	var data tc.GetTenantsResponse
+	query := tenantsEp() + "?name=" + url.QueryEscape(name)
+	reqInf, err := get(to, query, &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response[0], reqInf, nil
+}
+
 // CreateTenant creates the Tenant it's passed
 func (to *Session) CreateTenant(t *tc.Tenant) (*tc.TenantResponse, error) {
 	var data tc.TenantResponse
diff --git a/traffic_ops/testing/api/v13/tenants_test.go b/traffic_ops/testing/api/v13/tenants_test.go
index feb205c..519b088 100644
--- a/traffic_ops/testing/api/v13/tenants_test.go
+++ b/traffic_ops/testing/api/v13/tenants_test.go
@@ -16,9 +16,8 @@ package v13
 */
 
 import (
+	"strconv"
 	"testing"
-
-	tc "github.com/apache/trafficcontrol/lib/go-tc"
 )
 
 func TestTenants(t *testing.T) {
@@ -33,14 +32,14 @@ func CreateTestTenants(t *testing.T) {
 	for _, ten := range testData.Tenants {
 		// testData does not define ParentID -- look up by name and fill in
 		if ten.ParentID == 0 {
-			parents, _, err := TOSession.GetTenantByName(ten.ParentName)
+			parent, _, err := TOSession.TenantByName(ten.ParentName)
 			if err != nil {
 				t.Errorf("parent tenant %s: %++v", ten.ParentName, err)
 				continue
 			}
-			ten.ParentID = parents[0].ID
+			ten.ParentID = parent.ID
 		}
-		resp, _, err := TOSession.CreateTenant(ten)
+		resp, err := TOSession.CreateTenant(&ten)
 		t.Logf("response: %++v", resp)
 
 		if err != nil {
@@ -50,7 +49,7 @@ func CreateTestTenants(t *testing.T) {
 }
 
 func GetTestTenants(t *testing.T) {
-	resp, _, err := TOSession.GetTenants()
+	resp, _, err := TOSession.Tenants()
 	if err != nil {
 		t.Errorf("cannot GET all tenants: %v - %v\n", err, resp)
 		return
@@ -62,13 +61,13 @@ func GetTestTenants(t *testing.T) {
 	}
 
 	for _, ten := range testData.Tenants {
-		resp, _, err := TOSession.GetTenantByName(ten.Name)
+		resp, _, err := TOSession.TenantByName(ten.Name)
 		if err != nil {
 			t.Errorf("cannot GET Tenant by name: %v - %v\n", err, resp)
 			continue
 		}
-		if len(resp) != 1 {
-			t.Errorf("expected 1 tenant for %s,  got %d", ten.Name, len(resp))
+		if resp.Name != ten.Name {
+			t.Errorf("expected tenant %s,  got %s", ten.Name, resp.Name)
 			continue
 		}
 	}
@@ -79,30 +78,28 @@ func UpdateTestTenants(t *testing.T) {
 	// Retrieve the Tenant by name so we can get the id for the Update
 	name := "tenant2"
 	parentName := "tenant1"
-	resp, _, err := TOSession.GetTenantByName(name)
+	modTenant, _, err := TOSession.TenantByName(name)
 	if err != nil {
 		t.Errorf("cannot GET Tenant by name: %s - %v\n", name, err)
 	}
-	modTenant := resp[0]
-	resp, _, err = TOSession.GetTenantByName(parentName)
+
+	newParent, _, err := TOSession.TenantByName(parentName)
 	if err != nil {
-		t.Errorf("cannot GET Tenant by name: %s - %v\n", name, err)
+		t.Errorf("cannot GET Tenant by name: %s - %v\n", parentName, err)
 	}
-	newParent := resp[0]
 	modTenant.ParentID = newParent.ID
-	var alert tc.Alerts
-	alert, _, err = TOSession.UpdateTenantByID(modTenant.ID, modTenant)
+
+	resp, err := TOSession.UpdateTenant(strconv.Itoa(modTenant.ID), modTenant)
 	if err != nil {
-		t.Errorf("cannot UPDATE Tenant by id: %v - %v\n", err, alert)
+		t.Errorf("cannot UPDATE Tenant by id: %v\n", err)
 	}
 
 	// Retrieve the Tenant to check Tenant parent name got updated
-	resp, _, err = TOSession.GetTenantByID(modTenant.ID)
+	respTenant, _, err := TOSession.Tenant(strconv.Itoa(modTenant.ID))
 	if err != nil {
 		t.Errorf("cannot GET Tenant by name: %v - %v\n", name, err)
 	}
 	t.Logf("modified: %++v", resp)
-	respTenant := resp[0]
 	if respTenant.ParentName != parentName {
 		t.Errorf("results do not match actual: %s, expected: %s\n", respTenant.ParentName, parentName)
 	}
@@ -113,24 +110,20 @@ func DeleteTestTenants(t *testing.T) {
 
 	for _, ten := range testData.Tenants {
 		// Retrieve the Tenant by name so we can get the id for the Update
-		resp, _, err := TOSession.GetTenantByName(ten.Name)
+		respTenant, _, err := TOSession.TenantByName(ten.Name)
 		if err != nil {
 			t.Errorf("cannot GET Tenant by name: %v - %v\n", ten.Name, err)
 		}
-		respTenant := resp[0]
 
-		delResp, _, err := TOSession.DeleteTenantByID(respTenant.ID)
+		delResp, err := TOSession.DeleteTenant(strconv.Itoa(respTenant.ID))
 		if err != nil {
 			t.Errorf("cannot DELETE Tenant by name: %v - %v\n", err, delResp)
 		}
 
 		// Retrieve the Tenant to see if it got deleted
-		Tenants, _, err := TOSession.GetTenantByName(ten.Name)
+		_, _, err = TOSession.TenantByName(ten.Name)
 		if err != nil {
-			t.Errorf("error deleting Tenant name: %s\n", err.Error())
-		}
-		if len(Tenants) > 0 {
-			t.Errorf("expected Tenant name: %s to be deleted\n", ten.Name)
+			t.Errorf("tenant not deleted: %v\n", err)
 		}
 	}
 }
diff --git a/traffic_ops/testing/api/v13/traffic_control.go b/traffic_ops/testing/api/v13/traffic_control.go
index bbe7d13..98fcdfc 100644
--- a/traffic_ops/testing/api/v13/traffic_control.go
+++ b/traffic_ops/testing/api/v13/traffic_control.go
@@ -39,6 +39,6 @@ type TrafficControl struct {
 	Roles                          []v13.Role                          `json:"roles"`
 	Servers                        []v13.Server                        `json:"servers"`
 	Statuses                       []v12.Status                        `json:"statuses"`
-	Tenants                        []v13.Tenant                        `json:"tenants"`
+	Tenants                        []v12.Tenant                        `json:"tenants"`
 	Types                          []v12.Type                          `json:"types"`
 }


[trafficcontrol] 07/15: backward compatability

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit f2118fce50d7844959f89c84dd63d5b89f1be672
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 14:33:19 2018 -0600

    backward compatability
---
 lib/go-tc/tenants.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/go-tc/tenants.go b/lib/go-tc/tenants.go
index c53e707..2448822 100644
--- a/lib/go-tc/tenants.go
+++ b/lib/go-tc/tenants.go
@@ -19,18 +19,24 @@ package tc
  * under the License.
  */
 
-// TenantResponse ...
+// TenantResponse is the response for a request for a single tenant
 type TenantResponse struct {
 	Response Tenant        `json:"response"`
 	Alerts   []TenantAlert `json:"alerts"`
 }
 
-// TenantResponse ...
+// GetTenantResponse here to maintain backward compatability
+type GetTenantResponse TenantResponse
+
+// TenantResponse is the response for a request for a group of tenants
 type TenantsResponse struct {
 	Response []Tenant      `json:"response"`
 	Alerts   []TenantAlert `json:"alerts"`
 }
 
+// GetTenantsResponse here to maintain backward compatability
+type GetTenantsResponse TenantsResponse
+
 // Tenant ...
 type Tenant struct {
 	Active      bool      `json:"active"`


[trafficcontrol] 02/15: adds db validation

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 85e13c1a32bf439e5b7c9b1a40da4e70c1a3d097
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 14:19:12 2018 -0600

    adds db validation
---
 lib/go-tc/tovalidate/db.go | 125 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 125 insertions(+)

diff --git a/lib/go-tc/tovalidate/db.go b/lib/go-tc/tovalidate/db.go
new file mode 100644
index 0000000..dd14b9b
--- /dev/null
+++ b/lib/go-tc/tovalidate/db.go
@@ -0,0 +1,125 @@
+package tovalidate
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"database/sql"
+	"errors"
+	"fmt"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+	validation "github.com/go-ozzo/ozzo-validation"
+	"github.com/jmoiron/sqlx"
+)
+
+// DBExistsRule checks if value is in column in this table
+// TODO: DBExistsRule ?   what about DBUniqueRule?
+type DBExistsRule struct {
+	db      *sqlx.DB
+	table   string
+	column  string
+	message string
+}
+
+// NewDBExistsRule is a validation rule that checks if the given value is in the column in this table
+func NewDBExistsRule(db *sqlx.DB, table string, column string) *DBExistsRule {
+	return &DBExistsRule{
+		db:      db,
+		table:   table,
+		column:  column,
+		message: fmt.Sprintf("No rows with value in %s.%s", table, column),
+	}
+}
+
+// Validate checks if the given value is valid or not.
+func (r *DBExistsRule) Validate(value interface{}) error {
+	if r.db == nil {
+		return nil
+	}
+	value, isNil := validation.Indirect(value)
+	if isNil || validation.IsEmpty(value) {
+		return nil
+	}
+
+	query := `SELECT COUNT(*) FROM ` + r.table + ` WHERE ` + r.column + `= $1`
+	row := r.db.QueryRow(query, value)
+	var cnt int
+	err := row.Scan(&cnt)
+	log.Debugln("**** QUERY **** ", query)
+	log.Debugf(" value %d err %++v", cnt, err)
+	if err != nil {
+		return errors.New(r.message)
+	}
+	return nil
+}
+
+// Error sets the error message for the rule.
+func (r *DBExistsRule) Error(message string) *DBExistsRule {
+	r.message = message
+	return r
+}
+
+// DBUniqueRule checks if value is in column in this table
+// TODO: DBUniqueRule ?   what about DBUniqueRule?
+type DBUniqueRule struct {
+	db      *sqlx.DB
+	table   string
+	column  string
+	idCheck func(int) bool
+	message string
+}
+
+// NewDBUniqueRule is a validation rule that checks if the given value is in the column in this table
+func NewDBUniqueRule(db *sqlx.DB, table string, column string, idCheck func(int) bool) *DBUniqueRule {
+	return &DBUniqueRule{
+		db:      db,
+		table:   table,
+		column:  column,
+		idCheck: idCheck,
+		message: column + ` must be unique in ` + table,
+	}
+}
+
+// Validate returns an error if the value already exists in the table in this column
+func (r *DBUniqueRule) Validate(value interface{}) error {
+	if r.db == nil {
+		return nil
+	}
+	value, isNil := validation.Indirect(value)
+	if isNil || validation.IsEmpty(value) {
+		return nil
+	}
+
+	query := `SELECT id FROM ` + r.table
+	row := r.db.QueryRowx(query, map[string]interface{}{r.column: value})
+	var id int
+	err := row.Scan(&id)
+	// ok if no rows found or only one belongs to row being updated
+	if err == sql.ErrNoRows || r.idCheck(id) {
+		return nil
+	}
+	return errors.New(r.message)
+}
+
+// Error sets the error message for the rule.
+func (r *DBUniqueRule) Error(message string) *DBUniqueRule {
+	r.message = message
+	return r
+}


[trafficcontrol] 11/15: test deleting tenant

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit d1e9cc7c500586b3008f92eedd28e23f70d09419
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 16:58:55 2018 -0600

    test deleting tenant
---
 traffic_ops/testing/api/v13/tenants_test.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/traffic_ops/testing/api/v13/tenants_test.go b/traffic_ops/testing/api/v13/tenants_test.go
index 519b088..dc08086 100644
--- a/traffic_ops/testing/api/v13/tenants_test.go
+++ b/traffic_ops/testing/api/v13/tenants_test.go
@@ -25,7 +25,7 @@ func TestTenants(t *testing.T) {
 	CreateTestTenants(t)
 	UpdateTestTenants(t)
 	GetTestTenants(t)
-	//DeleteTestTenants(t)
+	DeleteTestTenants(t)
 }
 
 func CreateTestTenants(t *testing.T) {
@@ -117,7 +117,7 @@ func DeleteTestTenants(t *testing.T) {
 
 		delResp, err := TOSession.DeleteTenant(strconv.Itoa(respTenant.ID))
 		if err != nil {
-			t.Errorf("cannot DELETE Tenant by name: %v - %v\n", err, delResp)
+			t.Errorf("cannot DELETE Tenant: %v - %v\n", err, delResp)
 		}
 
 		// Retrieve the Tenant to see if it got deleted


[trafficcontrol] 10/15: safety check

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 0c809780c9c97185fe81773c7e8ca91aa65c5755
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 16:58:32 2018 -0600

    safety check
---
 traffic_ops/client/v13/tenant.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/traffic_ops/client/v13/tenant.go b/traffic_ops/client/v13/tenant.go
index 27bd31a..ac9bd4c 100644
--- a/traffic_ops/client/v13/tenant.go
+++ b/traffic_ops/client/v13/tenant.go
@@ -53,7 +53,11 @@ func (to *Session) TenantByName(name string) (*tc.Tenant, ReqInf, error) {
 		return nil, reqInf, err
 	}
 
-	return &data.Response[0], reqInf, nil
+	var ten *tc.Tenant
+	if len(data.Response) > 0 {
+		ten = &data.Response[0]
+	}
+	return ten, reqInf, nil
 }
 
 // CreateTenant creates the Tenant it's passed


[trafficcontrol] 13/15: complete tenant test

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit e043ae3b18e35dca7c6198135d3714162b7e212f
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Fri Jun 22 13:45:34 2018 -0600

    complete tenant test
---
 traffic_ops/testing/api/v13/tenants_test.go | 43 +++++++++++++++++++----------
 1 file changed, 28 insertions(+), 15 deletions(-)

diff --git a/traffic_ops/testing/api/v13/tenants_test.go b/traffic_ops/testing/api/v13/tenants_test.go
index dc08086..99bbbf2 100644
--- a/traffic_ops/testing/api/v13/tenants_test.go
+++ b/traffic_ops/testing/api/v13/tenants_test.go
@@ -17,6 +17,7 @@ package v13
 
 import (
 	"strconv"
+	"strings"
 	"testing"
 )
 
@@ -108,22 +109,34 @@ func UpdateTestTenants(t *testing.T) {
 
 func DeleteTestTenants(t *testing.T) {
 
-	for _, ten := range testData.Tenants {
-		// Retrieve the Tenant by name so we can get the id for the Update
-		respTenant, _, err := TOSession.TenantByName(ten.Name)
-		if err != nil {
-			t.Errorf("cannot GET Tenant by name: %v - %v\n", ten.Name, err)
-		}
+	t1 := "tenant1"
+	tenant1, _, err := TOSession.TenantByName(t1)
 
-		delResp, err := TOSession.DeleteTenant(strconv.Itoa(respTenant.ID))
-		if err != nil {
-			t.Errorf("cannot DELETE Tenant: %v - %v\n", err, delResp)
-		}
+	if err != nil {
+		t.Errorf("cannot GET Tenant by name: %v - %v\n", t1, err)
+	}
 
-		// Retrieve the Tenant to see if it got deleted
-		_, _, err = TOSession.TenantByName(ten.Name)
-		if err != nil {
-			t.Errorf("tenant not deleted: %v\n", err)
-		}
+	_, err = TOSession.DeleteTenant(strconv.Itoa(tenant1.ID))
+	if err == nil {
+		t.Errorf("%s has child tenants -- should not be able to delete", t1)
 	}
+	expected := "Tenant 'tenant1' has child tenants"
+	if !strings.Contains(err.Error(), expected) {
+		t.Errorf("expected error: %s;  got %s", expected, err.Error())
+	}
+
+	t2 := "tenant2"
+	tenant2, _, err := TOSession.TenantByName(t2)
+	_, err = TOSession.DeleteTenant(strconv.Itoa(tenant2.ID))
+	if err != nil {
+		t.Errorf("error deleting tenant %s: %v", t2, err)
+	}
+
+	// Now should be able to delete t1
+	tenant1, _, err = TOSession.TenantByName(t1)
+	_, err = TOSession.DeleteTenant(strconv.Itoa(tenant1.ID))
+	if err != nil {
+		t.Errorf("error deleting tenant %s: %v", t1, err)
+	}
+
 }


[trafficcontrol] 14/15: add last_updated

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit c7cf5d042c86b2987b6c51f57172e37ebeb651b1
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Fri Jun 22 13:59:26 2018 -0600

    add last_updated
---
 traffic_ops/traffic_ops_golang/tenant/tenancy.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index 05de1c0..44f1363 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -542,6 +542,7 @@ func selectQuery() string {
 t.active AS active,
 t.name AS name,
 t.id AS id,
+t.last_updated AS last_updated,
 t.parent_id AS parent_id,
 p.name AS parent_name
 


[trafficcontrol] 04/15: adds CRUD for tenants

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 7b7d75f534ad2e3cd8ee2244a6fc197e67c831f8
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 14:04:29 2018 -0600

    adds CRUD for tenants
---
 traffic_ops/traffic_ops_golang/tenant/tenancy.go | 403 +++++++++++++++++++++--
 1 file changed, 373 insertions(+), 30 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index 177a32d..5b706d6 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -24,25 +24,27 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"strconv"
 
 	"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/auth"
-
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+	validation "github.com/go-ozzo/ozzo-validation"
 	"github.com/jmoiron/sqlx"
+	"github.com/lib/pq"
 )
 
-type Tenant struct {
-	ID       int
-	Name     string
-	Active   bool
-	ParentID int
-}
+// TOTenant provides a local type against which to define methods
+type TOTenant tc.TenantNullable
 
+// DeliveryServiceTenantInfo provides only deliveryservice info needed here
 type DeliveryServiceTenantInfo tc.DeliveryServiceNullable
 
-// returns true if the user has tenant access on this deliveryservice
+// IsTenantAuthorized returns true if the user has tenant access on this tenant
 func (dsInfo DeliveryServiceTenantInfo) IsTenantAuthorized(user *auth.CurrentUser, tx *sql.Tx) (bool, error) {
 	if dsInfo.TenantID == nil {
 		return false, errors.New("TenantID is nil")
@@ -62,15 +64,16 @@ func GetDeliveryServiceTenantInfo(xmlID string, tx *sql.Tx) (*DeliveryServiceTen
 	return &ds, nil
 }
 
-// Check checks that the given user has access to the given XMLID. Returns a user error, system error, and the HTTP status code to be returned to the user if an error occurred. On success, the user error and system error will both be nil, and the error code should be ignored.
+// Check checks that the given user has access to the given XMLID. Returns a user error, system error,
+// and the HTTP status code to be returned to the user if an error occurred. On success, the user error
+// and system error will both be nil, and the error code should be ignored.
 func Check(user *auth.CurrentUser, XMLID string, tx *sql.Tx) (error, error, int) {
 	dsInfo, err := GetDeliveryServiceTenantInfo(XMLID, tx)
 	if err != nil {
 		if dsInfo == nil {
 			return nil, errors.New("deliveryservice lookup failure: " + err.Error()), http.StatusInternalServerError
-		} else {
-			return errors.New("no such deliveryservice: '" + XMLID + "'"), nil, http.StatusBadRequest
 		}
+		return errors.New("no such deliveryservice: '" + XMLID + "'"), nil, http.StatusBadRequest
 	}
 	hasAccess, err := dsInfo.IsTenantAuthorized(user, tx)
 	if err != nil {
@@ -86,35 +89,33 @@ func Check(user *auth.CurrentUser, XMLID string, tx *sql.Tx) (error, error, int)
 // NOTE: This method does not use the use_tenancy parameter and if this method is being used
 // to control tenancy the parameter must be checked. The method IsResourceAuthorizedToUser checks the use_tenancy parameter
 // and should be used for this purpose in most cases.
-func GetUserTenantList(user auth.CurrentUser, db *sqlx.DB) ([]Tenant, error) {
-	query := `WITH RECURSIVE q AS (SELECT id, name, active, parent_id FROM tenant WHERE id = $1
-	UNION SELECT t.id, t.name, t.active, t.parent_id  FROM tenant t JOIN q ON q.id = t.parent_id)
-	SELECT id, name, active, parent_id FROM q;`
+func GetUserTenantList(user auth.CurrentUser, db *sqlx.DB) ([]TOTenant, error) {
+	query := `WITH RECURSIVE q AS (SELECT id, name, active, parent_id, last_updated FROM tenant WHERE id = $1
+	UNION SELECT t.id, t.name, t.active, t.parent_id, t.last_updated  FROM tenant t JOIN q ON q.id = t.parent_id)
+	SELECT id, name, active, parent_id, last_updated FROM q;`
 
 	log.Debugln("\nQuery: ", query)
 
-	var tenantID int
-	var name string
-	var active bool
-	var parentID *int
-
+	var (
+		tenantID, parentID int
+		name               string
+		active             bool
+		lastUpdated        tc.TimeNoMod
+	)
 	rows, err := db.Query(query, user.TenantID)
 	if err != nil {
 		return nil, err
 	}
 	defer rows.Close()
 
-	tenants := []Tenant{}
+	tenants := []TOTenant{}
 
 	for rows.Next() {
-		if err := rows.Scan(&tenantID, &name, &active, &parentID); err != nil {
+		if err := rows.Scan(&tenantID, &name, &active, &parentID, &lastUpdated); err != nil {
 			return nil, err
 		}
-		if parentID != nil {
-			tenants = append(tenants, Tenant{ID: tenantID, Name: name, Active: active, ParentID: *parentID})
-		} else {
-			tenants = append(tenants, Tenant{ID: tenantID, Name: name, Active: active, ParentID: -1})
-		}
+
+		tenants = append(tenants, TOTenant{ID: &tenantID, Name: &name, Active: &active, ParentID: &parentID})
 	}
 
 	return tenants, nil
@@ -189,10 +190,8 @@ func IsResourceAuthorizedToUser(resourceTenantID int, user auth.CurrentUser, db
 		}
 		if active && tenantID == resourceTenantID {
 			return true, nil
-		} else {
-			fmt.Printf("default")
-			return false, nil
 		}
+		return false, nil
 	}
 }
 
@@ -228,3 +227,347 @@ func IsResourceAuthorizedToUserTx(resourceTenantID int, user *auth.CurrentUser,
 		}
 	}
 }
+
+// GetRefType allows shared handlers to decode JSON to the tenant type
+// Part of the Identifier interface
+func GetRefType() *TOTenant {
+	return &TOTenant{}
+}
+
+// GetID wraps the ID member with null checking
+// Part of the Identifier interface
+func (ten TOTenant) GetID() (int, bool) {
+	if ten.ID == nil {
+		return 0, false
+	}
+	return *ten.ID, true
+}
+
+// GetKeyFieldsInfo identifies types of the key fields
+func (ten TOTenant) GetKeyFieldsInfo() []api.KeyFieldInfo {
+	return []api.KeyFieldInfo{{"id", api.GetIntKey}}
+}
+
+// GetKeys returns values of keys
+func (ten TOTenant) GetKeys() (map[string]interface{}, bool) {
+	var id int
+	if ten.ID != nil {
+		id = *ten.ID
+	}
+	return map[string]interface{}{"id": id}, true
+}
+
+// GetAuditName returns a unique identifier
+// Part of the Identifier interface
+func (ten TOTenant) GetAuditName() string {
+	if ten.Name != nil {
+		return *ten.Name
+	}
+	id, _ := ten.GetID()
+	return strconv.Itoa(id)
+}
+
+// GetType returns the name of the type for messages
+// Part of the Identifier interface
+func (ten TOTenant) GetType() string {
+	return "tenant"
+}
+
+// SetKeys allows CreateHandler to assign id once object is created.
+// Part of the Identifier interface
+func (ten *TOTenant) SetKeys(keys map[string]interface{}) {
+	i, _ := keys["id"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here.
+	ten.ID = &i
+}
+
+// Validate fulfills the api.Validator interface
+func (ten TOTenant) Validate(db *sqlx.DB) []error {
+	errs := validation.Errors{
+		"name":     validation.Validate(ten.Name, validation.Required),
+		"active":   validation.Validate(ten.Active), // only validate it's boolean
+		"parentId": validation.Validate(ten.ParentID, validation.Required, validation.Min(1)),
+	}
+	return tovalidate.ToErrors(errs)
+}
+
+// Create implements the Creator interface
+//all implementations of Creator should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a tenant with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+//The insert sql returns the id and lastUpdated values of the newly inserted tenant and have
+//to be added to the struct
+func (ten *TOTenant) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+	resultRows, err := tx.NamedQuery(insertQuery(), ten)
+	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+			if eType == tc.DataConflictError {
+				return errors.New("a tenant with " + err.Error()), eType
+			}
+			return err, eType
+		}
+		log.Errorf("received non pq error: %++v from create execution", err)
+		return tc.DBError, tc.SystemError
+	}
+	defer resultRows.Close()
+
+	var id int
+	var lastUpdated tc.TimeNoMod
+	rowsAffected := 0
+	for resultRows.Next() {
+		rowsAffected++
+		if err := resultRows.Scan(&id, &lastUpdated); err != nil {
+			log.Error.Printf("could not scan id from insert: %s\n", err)
+			return tc.DBError, tc.SystemError
+		}
+	}
+	if rowsAffected == 0 {
+		err = errors.New("no tenant was inserted, no id was returned")
+		log.Errorln(err)
+		return tc.DBError, tc.SystemError
+	} else if rowsAffected > 1 {
+		err = errors.New("too many ids returned from tenant insert")
+		log.Errorln(err)
+		return tc.DBError, tc.SystemError
+	}
+	ten.SetKeys(map[string]interface{}{"id": id})
+	ten.LastUpdated = &lastUpdated
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+// Read implements the tc.Reader interface
+func (ten *TOTenant) Read(db *sqlx.DB, parameters map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+	var rows *sqlx.Rows
+
+	// Query Parameters to Database Query column mappings
+	// see the fields mapped in the SQL query
+	queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+		"active":      dbhelpers.WhereColumnInfo{Column: "t.active", Checker: nil},
+		"id":          dbhelpers.WhereColumnInfo{Column: "t.id", Checker: api.IsInt},
+		"name":        dbhelpers.WhereColumnInfo{Column: "t.name", Checker: nil},
+		"parent_id":   dbhelpers.WhereColumnInfo{Column: "t.parentID", Checker: api.IsInt},
+		"parent_name": dbhelpers.WhereColumnInfo{Column: "p.name", Checker: api.IsInt},
+	}
+	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+	if len(errs) > 0 {
+		return nil, errs, tc.DataConflictError
+	}
+
+	query := selectQuery() + where + orderBy
+	log.Debugln("Query is ", query)
+
+	rows, err := db.NamedQuery(query, queryValues)
+	if err != nil {
+		log.Errorf("Error querying tenants: %v", err)
+		return nil, []error{tc.DBError}, tc.SystemError
+	}
+	defer rows.Close()
+
+	tenants := []interface{}{}
+	for rows.Next() {
+		var s TOTenant
+		if err = rows.StructScan(&s); err != nil {
+			log.Errorf("error parsing Tenant rows: %v", err)
+			return nil, []error{tc.DBError}, tc.SystemError
+		}
+		tenants = append(tenants, s)
+	}
+
+	return tenants, []error{}, tc.NoError
+}
+
+//The TOTenant implementation of the Updater interface
+//all implementations of Updater should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a tenant with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+func (ten *TOTenant) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+	log.Debugf("about to run exec query: %s with tenant: %++v", updateQuery(), ten)
+	resultRows, err := tx.NamedQuery(updateQuery(), ten)
+	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+			if eType == tc.DataConflictError {
+				return errors.New("a tenant with " + err.Error()), eType
+			}
+			return err, eType
+		}
+		log.Errorf("received error: %++v from update execution", err)
+		return tc.DBError, tc.SystemError
+	}
+	defer resultRows.Close()
+
+	var lastUpdated tc.TimeNoMod
+	rowsAffected := 0
+	for resultRows.Next() {
+		rowsAffected++
+		if err := resultRows.Scan(&lastUpdated); err != nil {
+			log.Error.Printf("could not scan lastUpdated from insert: %s\n", err)
+			return tc.DBError, tc.SystemError
+		}
+	}
+	log.Debugf("lastUpdated: %++v", lastUpdated)
+	ten.LastUpdated = &lastUpdated
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return errors.New("no tenant found with this id"), tc.DataMissingError
+		}
+		return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
+	}
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+//Delete implements the Deleter interface
+//all implementations of Deleter should use transactions and return the proper errorType
+func (ten *TOTenant) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	if ten.ID == nil {
+		// should never happen...
+		return errors.New("invalid tenant: id is nil"), tc.SystemError
+	}
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+
+	// if tenant has children, don't allow deletion
+	parentQ := `SELECT COUNT(*) FROM tenant WHERE parent_id = $1`
+	var count int
+	err = tx.QueryRowx(parentQ, *ten.ID).Scan(&count)
+	if err != nil {
+		log.Errorf("received error: %++v from parent query execution", err)
+		return err, tc.SystemError
+	}
+	if count > 0 {
+		name := "unknown"
+		if ten.Name != nil {
+			name = *ten.Name
+		}
+		log.Errorf("Tenant '%s' has children tenant(s); refusing to delete", name)
+		return fmt.Errorf("Tenant '%s' has children tenant(s):  Please update these tenants and retry.", name), tc.ForbiddenError
+	}
+
+	log.Debugf("about to run exec query: %s with tenant: %++v", deleteQuery(), ten)
+	result, err := tx.NamedExec(deleteQuery(), ten)
+	if err != nil {
+		log.Errorf("received error: %++v from delete execution", err)
+		return tc.DBError, tc.SystemError
+	}
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return tc.DBError, tc.SystemError
+	}
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return errors.New("no tenant with that id found"), tc.DataMissingError
+		}
+		return fmt.Errorf("this delete affected too many rows: %d", rowsAffected), tc.SystemError
+	}
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+func selectQuery() string {
+	query := `SELECT
+t.active AS active,
+t.name AS name,
+t.id AS id,
+t.parent_id AS parent_id,
+p.name AS parent_name
+
+FROM tenant AS t
+LEFT OUTER JOIN tenant AS p
+ON t.parent_id = p.id`
+	return query
+}
+
+func updateQuery() string {
+	query := `UPDATE
+tenant SET
+active=:active,
+name=:name,
+parent_id=:parent_id
+
+WHERE id=:id RETURNING last_updated`
+	return query
+}
+
+func insertQuery() string {
+	query := `INSERT INTO tenant (
+name,
+active,
+parent_id
+) VALUES (
+:name,
+:active,
+:parent_id
+) RETURNING id,last_updated`
+	return query
+}
+
+func deleteQuery() string {
+	query := `DELETE FROM tenant
+WHERE id=:id`
+	return query
+}


[trafficcontrol] 15/15: use 1.1 for tenants endpoints

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 0a117a649dedc2982e18e71dbd167ce31ec953ec
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Fri Jun 22 14:48:30 2018 -0600

    use 1.1 for tenants endpoints
---
 traffic_ops/traffic_ops_golang/routes.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 13aed57..789a2ec 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -282,11 +282,11 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.1, http.MethodDelete, `profileparameters/{profileId}/{parameterId}$`, api.DeleteHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
 
 		//Tenants
-		{1.2, http.MethodGet, `tenants/?(\.json)?$`, api.ReadHandler(tenant.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
-		{1.2, http.MethodGet, `tenants/{id}$`, api.ReadHandler(tenant.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
-		{1.2, http.MethodPut, `tenants/{id}$`, api.UpdateHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
-		{1.2, http.MethodPost, `tenants/?$`, api.CreateHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
-		{1.2, http.MethodDelete, `tenants/{id}$`, api.DeleteHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodGet, `tenants/?(\.json)?$`, api.ReadHandler(tenant.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `tenants/{id}$`, api.ReadHandler(tenant.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodPut, `tenants/{id}$`, api.UpdateHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodPost, `tenants/?$`, api.CreateHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodDelete, `tenants/{id}$`, api.DeleteHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
 
 		//CRConfig
 		{1.1, http.MethodGet, `cdns/{cdn}/snapshot/?$`, crconfig.SnapshotGetHandler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},


[trafficcontrol] 05/15: add tenants routes

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit cd7960f656b4ce5f9cb3f36b6506f9384ee2e7e4
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 13:57:39 2018 -0600

    add tenants routes
---
 traffic_ops/traffic_ops_golang/routes.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 01cdb92..13aed57 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -57,6 +57,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/staticdnsentry"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/status"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/types"
 
 	"github.com/basho/riak-go-client"
@@ -280,6 +281,13 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.1, http.MethodPost, `parameterprofile/?$`, profileparameter.PostParamProfile(d.DB.DB), auth.PrivLevelOperations, Authenticated, nil},
 		{1.1, http.MethodDelete, `profileparameters/{profileId}/{parameterId}$`, api.DeleteHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
 
+		//Tenants
+		{1.2, http.MethodGet, `tenants/?(\.json)?$`, api.ReadHandler(tenant.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.2, http.MethodGet, `tenants/{id}$`, api.ReadHandler(tenant.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.2, http.MethodPut, `tenants/{id}$`, api.UpdateHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.2, http.MethodPost, `tenants/?$`, api.CreateHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.2, http.MethodDelete, `tenants/{id}$`, api.DeleteHandler(tenant.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
 		//CRConfig
 		{1.1, http.MethodGet, `cdns/{cdn}/snapshot/?$`, crconfig.SnapshotGetHandler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},
 		{1.1, http.MethodGet, `cdns/{cdn}/snapshot/new/?$`, crconfig.Handler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},


[trafficcontrol] 06/15: add tenants API tests

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit ba9f6f09e39ea9d568be798074bede3d1f15ede2
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 14:12:18 2018 -0600

    add tenants API tests
---
 .../api/v13/deliveryservice_requests_test.go       |   1 +
 traffic_ops/testing/api/v13/tc-fixtures.json       | 147 ++++++++++++++++++++-
 traffic_ops/testing/api/v13/tenants_test.go        | 136 +++++++++++++++++++
 traffic_ops/testing/api/v13/todb.go                |  28 ++--
 traffic_ops/testing/api/v13/traffic_control.go     |   2 +-
 5 files changed, 292 insertions(+), 22 deletions(-)

diff --git a/traffic_ops/testing/api/v13/deliveryservice_requests_test.go b/traffic_ops/testing/api/v13/deliveryservice_requests_test.go
index 6d974ae..1b6589a 100644
--- a/traffic_ops/testing/api/v13/deliveryservice_requests_test.go
+++ b/traffic_ops/testing/api/v13/deliveryservice_requests_test.go
@@ -39,6 +39,7 @@ func TestDeliveryServiceRequests(t *testing.T) {
 	GetTestDeliveryServiceRequests(t)
 	UpdateTestDeliveryServiceRequests(t)
 	DeleteTestDeliveryServiceRequests(t)
+
 	DeleteTestTypes(t)
 	DeleteTestCDNs(t)
 
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json b/traffic_ops/testing/api/v13/tc-fixtures.json
index c41eb8b..21f61f9 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -1043,18 +1043,13 @@
     "tenants": [
         {
             "active": true,
-            "name": "root",
-            "parentTenantName": null
-        },
-        {
-            "active": true,
             "name": "tenant1",
-            "parentTenantName": "root"
+            "parentName": "root"
         },
         {
             "active": false,
             "name": "tenant2",
-            "parentTenantName": "root"
+            "parentName": "root"
         }
     ],
     "types": [
@@ -1250,5 +1245,143 @@
             "name": "TRAFFIC_MONITOR",
             "useInTable": "server"
         }
+    ],
+    "users": [
+        {
+            "addressLine1": "address of admin",
+            "addressLine2": "",
+            "city": "Anywhere",
+            "company": "Comcast",
+            "country": "USA",
+            "email": "admin@example.com",
+            "fullName": "Fred the admin",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "810-555-9876",
+            "postalCode": "55443",
+            "publicSshKey": "",
+            "role": 4,
+            "rolename": "admin",
+            "stateOrProvince": "LA",
+            "tenant": "root",
+            "uid": 0,
+            "username": "adminuser"
+        },
+        {
+            "addressLine1": "address of disallowed",
+            "addressLine2": "place",
+            "city": "somewhere",
+            "company": "else",
+            "country": "UK",
+            "email": "disallowed@example.com",
+            "fullName": "Me me",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "",
+            "postalCode": "",
+            "publicSshKey": "",
+            "registrationSent": "",
+            "role": 1,
+            "roleName": "disallowed",
+            "stateOrProvince": "",
+            "tenant": "root",
+            "tenant": 3,
+            "uid": 0,
+            "username": "disalloweduser"
+        },
+        {
+            "addressLine1": "address of read-only",
+            "addressLine2": "",
+            "city": "",
+            "company": "",
+            "country": "",
+            "email": "readonly@example.com",
+            "fullName": "",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "",
+            "postalCode": "",
+            "publicSshKey": "",
+            "registrationSent": "",
+            "role": 2,
+            "roleName": "read-only user",
+            "stateOrProvince": "",
+            "tenant": "root",
+            "tenant": 3,
+            "uid": 0,
+            "username": "readuser"
+        },
+        {
+            "addressLine1": "address of operations",
+            "addressLine2": "",
+            "city": "",
+            "company": "",
+            "country": "",
+            "email": "operations@example.com",
+            "fullName": "",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "",
+            "postalCode": "",
+            "publicSshKey": "",
+            "registrationSent": "",
+            "role": 3,
+            "roleName": "operations",
+            "stateOrProvince": "",
+            "tenant": "root",
+            "tenant": 4,
+            "uid": 0,
+            "username": "operationsuser"
+        },
+        {
+            "addressLine1": "",
+            "addressLine2": "",
+            "city": "",
+            "company": "",
+            "country": "",
+            "email": "portal@example.com",
+            "fullName": "",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "",
+            "postalCode": "",
+            "publicSshKey": "",
+            "registrationSent": "",
+            "role": 5,
+            "roleName": "portal",
+            "stateOrProvince": "",
+            "tenant": "root",
+            "tenant": 2,
+            "uid": 0,
+            "username": "portaluser"
+        },
+        {
+            "addressLine1": "",
+            "addressLine2": "",
+            "city": "",
+            "company": "",
+            "country": "",
+            "email": "federation@example.com",
+            "fullName": "",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "",
+            "postalCode": "",
+            "publicSshKey": "",
+            "registrationSent": "",
+            "role": 7,
+            "roleName": "federation",
+            "stateOrProvince": "",
+            "tenant": "root",
+            "tenant": 4,
+            "uid": 0,
+            "username": "federationuser"
+        }
     ]
 }
diff --git a/traffic_ops/testing/api/v13/tenants_test.go b/traffic_ops/testing/api/v13/tenants_test.go
new file mode 100644
index 0000000..feb205c
--- /dev/null
+++ b/traffic_ops/testing/api/v13/tenants_test.go
@@ -0,0 +1,136 @@
+package v13
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+	"testing"
+
+	tc "github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+func TestTenants(t *testing.T) {
+
+	CreateTestTenants(t)
+	UpdateTestTenants(t)
+	GetTestTenants(t)
+	//DeleteTestTenants(t)
+}
+
+func CreateTestTenants(t *testing.T) {
+	for _, ten := range testData.Tenants {
+		// testData does not define ParentID -- look up by name and fill in
+		if ten.ParentID == 0 {
+			parents, _, err := TOSession.GetTenantByName(ten.ParentName)
+			if err != nil {
+				t.Errorf("parent tenant %s: %++v", ten.ParentName, err)
+				continue
+			}
+			ten.ParentID = parents[0].ID
+		}
+		resp, _, err := TOSession.CreateTenant(ten)
+		t.Logf("response: %++v", resp)
+
+		if err != nil {
+			t.Errorf("could not CREATE tenant %s: %v\n", ten.Name, err)
+		}
+	}
+}
+
+func GetTestTenants(t *testing.T) {
+	resp, _, err := TOSession.GetTenants()
+	if err != nil {
+		t.Errorf("cannot GET all tenants: %v - %v\n", err, resp)
+		return
+	}
+
+	// expect root and badTenant (defined in todb.go) + all defined in testData.Tenants
+	if len(resp) != 2+len(testData.Tenants) {
+		t.Errorf("expected %d tenants,  got %d", 2+len(testData.Tenants), len(resp))
+	}
+
+	for _, ten := range testData.Tenants {
+		resp, _, err := TOSession.GetTenantByName(ten.Name)
+		if err != nil {
+			t.Errorf("cannot GET Tenant by name: %v - %v\n", err, resp)
+			continue
+		}
+		if len(resp) != 1 {
+			t.Errorf("expected 1 tenant for %s,  got %d", ten.Name, len(resp))
+			continue
+		}
+	}
+}
+
+func UpdateTestTenants(t *testing.T) {
+
+	// Retrieve the Tenant by name so we can get the id for the Update
+	name := "tenant2"
+	parentName := "tenant1"
+	resp, _, err := TOSession.GetTenantByName(name)
+	if err != nil {
+		t.Errorf("cannot GET Tenant by name: %s - %v\n", name, err)
+	}
+	modTenant := resp[0]
+	resp, _, err = TOSession.GetTenantByName(parentName)
+	if err != nil {
+		t.Errorf("cannot GET Tenant by name: %s - %v\n", name, err)
+	}
+	newParent := resp[0]
+	modTenant.ParentID = newParent.ID
+	var alert tc.Alerts
+	alert, _, err = TOSession.UpdateTenantByID(modTenant.ID, modTenant)
+	if err != nil {
+		t.Errorf("cannot UPDATE Tenant by id: %v - %v\n", err, alert)
+	}
+
+	// Retrieve the Tenant to check Tenant parent name got updated
+	resp, _, err = TOSession.GetTenantByID(modTenant.ID)
+	if err != nil {
+		t.Errorf("cannot GET Tenant by name: %v - %v\n", name, err)
+	}
+	t.Logf("modified: %++v", resp)
+	respTenant := resp[0]
+	if respTenant.ParentName != parentName {
+		t.Errorf("results do not match actual: %s, expected: %s\n", respTenant.ParentName, parentName)
+	}
+
+}
+
+func DeleteTestTenants(t *testing.T) {
+
+	for _, ten := range testData.Tenants {
+		// Retrieve the Tenant by name so we can get the id for the Update
+		resp, _, err := TOSession.GetTenantByName(ten.Name)
+		if err != nil {
+			t.Errorf("cannot GET Tenant by name: %v - %v\n", ten.Name, err)
+		}
+		respTenant := resp[0]
+
+		delResp, _, err := TOSession.DeleteTenantByID(respTenant.ID)
+		if err != nil {
+			t.Errorf("cannot DELETE Tenant by name: %v - %v\n", err, delResp)
+		}
+
+		// Retrieve the Tenant to see if it got deleted
+		Tenants, _, err := TOSession.GetTenantByName(ten.Name)
+		if err != nil {
+			t.Errorf("error deleting Tenant name: %s\n", err.Error())
+		}
+		if len(Tenants) > 0 {
+			t.Errorf("expected Tenant name: %s to be deleted\n", ten.Name)
+		}
+	}
+}
diff --git a/traffic_ops/testing/api/v13/todb.go b/traffic_ops/testing/api/v13/todb.go
index 0081ee8..0ebea91 100644
--- a/traffic_ops/testing/api/v13/todb.go
+++ b/traffic_ops/testing/api/v13/todb.go
@@ -49,12 +49,6 @@ func OpenConnection() (*sql.DB, error) {
 func SetupTestData(*sql.DB) error {
 	var err error
 
-	err = SetupTenants(db)
-	if err != nil {
-		fmt.Printf("\nError setting up tenants %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err)
-		os.Exit(1)
-	}
-
 	err = SetupRoles(db)
 	if err != nil {
 		fmt.Printf("\nError setting up roles %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err)
@@ -73,6 +67,12 @@ func SetupTestData(*sql.DB) error {
 		os.Exit(1)
 	}
 
+	err = SetupTenants(db)
+	if err != nil {
+		fmt.Printf("\nError setting up tenant %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err)
+		os.Exit(1)
+	}
+
 	err = SetupTmusers(db)
 	if err != nil {
 		fmt.Printf("\nError setting up tm_user %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err)
@@ -136,12 +136,12 @@ func SetupTmusers(db *sql.DB) error {
 
 	// Creates users in different tenants
 	sqlStmt := `
-INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Disallowed + `','` + encryptedPassword + `','` + encryptedPassword + `', 1, 3);
-INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.ReadOnly + `','` + encryptedPassword + `','` + encryptedPassword + `', 2, 3);
-INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Operations + `','` + encryptedPassword + `','` + encryptedPassword + `', 3, 3);
+INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Disallowed + `','` + encryptedPassword + `','` + encryptedPassword + `', 1, 2);
+INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.ReadOnly + `','` + encryptedPassword + `','` + encryptedPassword + `', 2, 2);
+INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Operations + `','` + encryptedPassword + `','` + encryptedPassword + `', 3, 2);
 INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Admin + `','` + encryptedPassword + `','` + encryptedPassword + `', 4, 2);
-INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Portal + `','` + encryptedPassword + `','` + encryptedPassword + `', 5, 3);
-INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Federation + `','` + encryptedPassword + `','` + encryptedPassword + `', 6, 3);
+INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Portal + `','` + encryptedPassword + `','` + encryptedPassword + `', 5, 2);
+INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Federation + `','` + encryptedPassword + `','` + encryptedPassword + `', 6, 2);
 `
 	err = execSQL(db, sqlStmt, "tm_user")
 	if err != nil {
@@ -153,11 +153,11 @@ INSERT INTO tm_user (username, local_passwd, confirm_local_passwd, role, tenant_
 // SetupTenants ...
 func SetupTenants(db *sql.DB) error {
 
+	// TODO: root tenant must be present in initial database.  "badtenant" is needed for now so tests can be done
+	// with a tenant outside the user's tenant.  That should be removed once User API tests are in place rather than the SetupUsers defined above.
 	sqlStmt := `
 INSERT INTO tenant (id, name, active, parent_id, last_updated) VALUES (1, 'root', true, null, '2018-01-19 19:01:21.327262');
-INSERT INTO tenant (id, name, active, parent_id, last_updated) VALUES (2, 'grandparent tenant', true, 1, '2018-01-19 19:01:21.327262');
-INSERT INTO tenant (id, name, active, parent_id, last_updated) VALUES (3, 'parent tenant', true, 2, '2018-01-19 19:01:21.327262');
-INSERT INTO tenant (id, name, active, parent_id, last_updated) VALUES (4, 'child tenant', true, 3, '2018-01-19 19:01:21.327262');
+INSERT INTO tenant (id, name, active, parent_id, last_updated) VALUES (2, 'badtenant', true, 1, '2018-01-19 19:01:21.327262');
 `
 	err := execSQL(db, sqlStmt, "tenant")
 	if err != nil {
diff --git a/traffic_ops/testing/api/v13/traffic_control.go b/traffic_ops/testing/api/v13/traffic_control.go
index 98fcdfc..bbe7d13 100644
--- a/traffic_ops/testing/api/v13/traffic_control.go
+++ b/traffic_ops/testing/api/v13/traffic_control.go
@@ -39,6 +39,6 @@ type TrafficControl struct {
 	Roles                          []v13.Role                          `json:"roles"`
 	Servers                        []v13.Server                        `json:"servers"`
 	Statuses                       []v12.Status                        `json:"statuses"`
-	Tenants                        []v12.Tenant                        `json:"tenants"`
+	Tenants                        []v13.Tenant                        `json:"tenants"`
 	Types                          []v12.Type                          `json:"types"`
 }


[trafficcontrol] 03/15: add tenants structs

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 23aa135a32d47145bd614dafd925964d8936a17d
Author: Dan Kirkwood <da...@apache.org>
AuthorDate: Thu Jun 21 14:11:04 2018 -0600

    add tenants structs
---
 lib/go-tc/tenants.go | 62 +++++++++++++++++++++++++++++++++-------------------
 1 file changed, 39 insertions(+), 23 deletions(-)

diff --git a/lib/go-tc/tenants.go b/lib/go-tc/tenants.go
index 232d617..c53e707 100644
--- a/lib/go-tc/tenants.go
+++ b/lib/go-tc/tenants.go
@@ -1,24 +1,23 @@
 package tc
 
 /*
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-// GetTenantsResponse ...
-type GetTenantsResponse struct {
-	Response []Tenant `json:"response"`
-}
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 
 // TenantResponse ...
 type TenantResponse struct {
@@ -26,13 +25,30 @@ type TenantResponse struct {
 	Alerts   []TenantAlert `json:"alerts"`
 }
 
+// TenantResponse ...
+type TenantsResponse struct {
+	Response []Tenant      `json:"response"`
+	Alerts   []TenantAlert `json:"alerts"`
+}
+
 // Tenant ...
 type Tenant struct {
-	ID         int    `json:"id,omitempty"`
-	Name       string `json:"name,omitempty"`
-	Active     bool   `json:"active,omitempty"`
-	ParentID   int    `json:"parentId"`
-	ParentName string `json:"parentName,omitempty"`
+	Active      bool      `json:"active"`
+	ID          int       `json:"id"`
+	LastUpdated TimeNoMod `json:"lastUpdated" db:"last_updated"`
+	Name        string    `json:"name"`
+	ParentID    int       `json:"parentId"`
+	ParentName  string    `json:"parentName,omitempty" db:"parent_name"`
+}
+
+// TenantNullable ...
+type TenantNullable struct {
+	ID          *int       `json:"id" db:"id"`
+	Name        *string    `json:"name" db:"name"`
+	Active      *bool      `json:"active" db:"active"`
+	LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"`
+	ParentID    *int       `json:"parentId" db:"parent_id"`
+	ParentName  *string    `json:"parentName,omitempty" db:"parent_name"`
 }
 
 // DeleteTenantResponse ...