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 14d88e5 Consistent profile name restrictions (#6018)
14d88e5 is described below
commit 14d88e5cd8d1b84746d0869043dd3c86f3d3f8c7
Author: ocket8888 <[email protected]>
AuthorDate: Wed Aug 11 12:58:32 2021 -0600
Consistent profile name restrictions (#6018)
* Added restriction to TO to disallow creating/updating Profiles with names
containing spaces
* Remove unnecessary event listener
* Add unit test coverage for new constraint
* Add client/API integration testing coverage for new contstraint
* Update CHANGELOG
* Update documentation
---
CHANGELOG.md | 1 +
docs/source/overview/profiles_and_parameters.rst | 5 ++
lib/go-tc/profiles.go | 17 +++++-
traffic_ops/testing/api/v4/profiles_test.go | 62 ++++++++++++++++++----
traffic_ops/traffic_ops_golang/profile/copy.go | 13 +++++
traffic_ops/traffic_ops_golang/profile/profiles.go | 26 ++++++++-
.../traffic_ops_golang/profile/profiles_test.go | 18 ++++++-
.../modules/form/profile/form.profile.tpl.html | 2 +-
8 files changed, 128 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 551bdcb..9c07e6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -96,6 +96,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- [#5984](https://github.com/apache/trafficcontrol/issues/5894) -
`/servers/{{ID}}/deliveryservices` returns incorrect response for the requested
API version
- [#6027](https://github.com/apache/trafficcontrol/issues/6027) - Collapsed DB
migrations
- [#6066](https://github.com/apache/trafficcontrol/issues/6066) - Fixed
missing/incorrect indices on some tables
+- [#5576](https://github.com/apache/trafficcontrol/issues/5576) - Inconsistent
Profile Name restrictions
### Changed
- Updated the Traffic Ops Python client to 3.0
diff --git a/docs/source/overview/profiles_and_parameters.rst
b/docs/source/overview/profiles_and_parameters.rst
index 154cd9d..c92b2546 100644
--- a/docs/source/overview/profiles_and_parameters.rst
+++ b/docs/source/overview/profiles_and_parameters.rst
@@ -56,6 +56,11 @@ Name
""""
Ostensibly this is simply the Profile's name. However, the name of a Profile
has drastic consequences for how Traffic Control treats it. Particularly, the
name of a Profile is heavily conflated with its Type_. These relationships are
discussed further in the Type_ section, on a Type-by-Type basis.
+The Name of a Profile may not contain spaces.
+
+.. versionchanged:: ATCv6
+ In older versions of :abbr:`ATC (Apache Traffic Control)`, Profile
Names were allowed to contain spaces. The :ref:`to-api` will reject creation or
update of Profiles that have spaces in their Names as of :abbr:`ATC (Apache
Traffic Control)` version 6, so legacy Profiles will need to be updated to meet
this constraint before they can be modified.
+
.. _profile-routing-disabled:
Routing Disabled
diff --git a/lib/go-tc/profiles.go b/lib/go-tc/profiles.go
index 23b8362..0565147 100644
--- a/lib/go-tc/profiles.go
+++ b/lib/go-tc/profiles.go
@@ -24,6 +24,7 @@ import (
"errors"
"fmt"
"strconv"
+ "strings"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
@@ -145,7 +146,21 @@ func (profileImport *ProfileImportRequest) Validate(tx
*sql.Tx) error {
// Profile fields are valid
errs := tovalidate.ToErrors(validation.Errors{
- "name": validation.Validate(profile.Name,
validation.Required),
+ "name": validation.Validate(profile.Name, validation.By(
+ func(value interface{}) error {
+ name, ok := value.(*string)
+ if !ok {
+ return fmt.Errorf("wrong type, need:
string, got: %T", value)
+ }
+ if name == nil || *name == "" {
+ return errors.New("required and cannot
be blank")
+ }
+ if strings.Contains(*name, " ") {
+ return errors.New("cannot contain
spaces")
+ }
+ return nil
+ },
+ )),
"description": validation.Validate(profile.Description,
validation.Required),
"cdnName": validation.Validate(profile.CDNName,
validation.Required),
"type": validation.Validate(profile.Type,
validation.Required),
diff --git a/traffic_ops/testing/api/v4/profiles_test.go
b/traffic_ops/testing/api/v4/profiles_test.go
index 71da5e5..0d84916 100644
--- a/traffic_ops/testing/api/v4/profiles_test.go
+++ b/traffic_ops/testing/api/v4/profiles_test.go
@@ -32,25 +32,25 @@ import (
func TestProfiles(t *testing.T) {
WithObjs(t, []TCObj{CDNs, Types, Profiles, Parameters}, func() {
- CreateBadProfiles(t)
- UpdateTestProfiles(t)
+ t.Run("Attempt to create invalid Profiles", CreateBadProfiles)
+ t.Run("Basic update of properties", UpdateTestProfiles)
currentTime := time.Now().UTC().Add(-5 * time.Second)
time := currentTime.Format(time.RFC1123)
var header http.Header
header = make(map[string][]string)
header.Set(rfc.IfUnmodifiedSince, time)
- UpdateTestProfilesWithHeaders(t, header)
- GetTestProfilesIMS(t)
- GetTestProfiles(t)
- GetTestProfilesWithParameters(t)
- ImportProfile(t)
- CopyProfile(t)
+ t.Run("Try to update a Profile with If-Unmodified-Since set to
5 seconds ago - presumably before its creation", testPreconditionFailed(header))
+ t.Run("Support for If-Modified-Since in GET",
GetTestProfilesIMS)
+ t.Run("Basic GET request for /profiles and export endpoint",
GetTestProfiles)
+ t.Run("Check 'parameters' property returned in GET requsets",
GetTestProfilesWithParameters)
+ t.Run("Import endpoint basic operation", ImportProfile)
+ t.Run("Copy endpoint basic operation", CopyProfile)
header = make(map[string][]string)
etag := rfc.ETag(currentTime)
header.Set(rfc.IfMatch, etag)
- UpdateTestProfilesWithHeaders(t, header)
- GetTestPaginationSupportProfiles(t)
- CUDProfileWithLocks(t)
+ t.Run("Try to update a Profile with If-Match set",
testPreconditionFailed(header))
+ t.Run("Verify pagination query string parameters support",
GetTestPaginationSupportProfiles)
+ t.Run("Verify Profile endpoints are locked by CDN locks",
CUDProfileWithLocks)
})
}
@@ -178,6 +178,12 @@ func CUDProfileWithLocks(t *testing.T) {
}
}
+func testPreconditionFailed(h http.Header) func(*testing.T) {
+ return func(t *testing.T) {
+ UpdateTestProfilesWithHeaders(t, h)
+ }
+}
+
func UpdateTestProfilesWithHeaders(t *testing.T, header http.Header) {
if len(testData.Profiles) < 1 {
t.Fatal("Need at least one Profile to test updating a Profile
with HTTP headers")
@@ -254,6 +260,14 @@ func CopyProfile(t *testing.T) {
err string
}{
{
+ description: "new profile name contains spaces",
+ profile: tc.ProfileCopy{
+ ExistingName: "EDGE1",
+ Name: "Profile Copy",
+ },
+ err: "cannot contain spaces",
+ },
+ {
description: "copy profile",
profile: tc.ProfileCopy{
Name: "profile-2",
@@ -332,7 +346,11 @@ func CopyProfile(t *testing.T) {
func CreateTestProfiles(t *testing.T) {
opts := client.NewRequestOptions()
+ var cdnID int
+ var typ string
for _, pr := range testData.Profiles {
+ cdnID = pr.CDNID
+ typ = pr.Type
resp, _, err := TOSession.CreateProfile(pr,
client.RequestOptions{})
if err != nil {
t.Errorf("could not create Profile '%s': %v - alerts:
%+v", pr.Name, err, resp.Alerts)
@@ -388,6 +406,19 @@ func CreateTestProfiles(t *testing.T) {
}
}
+
+ p := tc.Profile{
+ CDNID: cdnID,
+ Description: "test Profile creation with a name that contains
spaces",
+ Name: "A Profile that has spaces in its name",
+ Type: typ,
+ }
+ resp, _, err := TOSession.CreateProfile(p, client.RequestOptions{})
+ if err == nil {
+ t.Error("Expected an error trying to create a Profile with a
Name that has spaces in it")
+ } else if !alertsHaveError(resp.Alerts, "cannot contain spaces") {
+ t.Errorf("Expected an error about spaces in the Profile name,
got: %v - alerts: %+v", err, resp.Alerts)
+ }
}
// Note this test will break if certain changes are made to the content and/or
@@ -567,6 +598,15 @@ func ImportProfile(t *testing.T) {
Name: *newParam.Name,
Value: *newParam.Value,
})
+
+ *profile.Name = "Test Profile Import"
+ importReq.Profile = profile
+ importResp, _, err = TOSession.ImportProfile(importReq,
client.RequestOptions{})
+ if err == nil {
+ t.Error("Expected an error importing a Profile with a space in
its name")
+ } else if !alertsHaveError(importResp.Alerts.Alerts, "cannot contain
spaces") {
+ t.Errorf("Expected an error about the Profile name containing
spaces, got: %v - alerts: %+v", err, importResp.Alerts)
+ }
}
func GetTestProfilesWithParameters(t *testing.T) {
diff --git a/traffic_ops/traffic_ops_golang/profile/copy.go
b/traffic_ops/traffic_ops_golang/profile/copy.go
index 40669cf..f7d6d58 100644
--- a/traffic_ops/traffic_ops_golang/profile/copy.go
+++ b/traffic_ops/traffic_ops_golang/profile/copy.go
@@ -24,6 +24,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strings"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
@@ -71,6 +72,18 @@ func CopyProfileHandler(w http.ResponseWriter, r
*http.Request) {
}
func copyProfile(inf *api.APIInfo, p *tc.ProfileCopy) errorDetails {
+ if inf == nil || p == nil {
+ return errorDetails{
+ sysErr: errors.New("copyProfile received nil APIInfo
or ProfileCopy reference"),
+ errCode: http.StatusInternalServerError,
+ }
+ }
+ if strings.Contains(p.Name, " ") {
+ return errorDetails{
+ userErr: errors.New("new Profile name cannot contain
spaces"),
+ errCode: http.StatusBadRequest,
+ }
+ }
// check if the newProfile already exists
ok, err := tc.ProfileExistsByName(p.Name, inf.Tx.Tx)
if ok {
diff --git a/traffic_ops/traffic_ops_golang/profile/profiles.go
b/traffic_ops/traffic_ops_golang/profile/profiles.go
index a89016a..6e12387 100644
--- a/traffic_ops/traffic_ops_golang/profile/profiles.go
+++ b/traffic_ops/traffic_ops_golang/profile/profiles.go
@@ -1,3 +1,8 @@
+// Package profile includes logic and handlers for Profile-related API
+// endpoints, including /profiles, /profiles/name/{{name}}/parameters,
+// /profiles/{{ID}}/parameters,
+// /profiles/name/{{New Profile Name}}/copy/{{existing Profile Name}},
+// /profiles/import, and /profiles/{{ID}}/export.
package profile
/*
@@ -21,9 +26,10 @@ package profile
import (
"errors"
-
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
+ "fmt"
"net/http"
"strconv"
+ "strings"
"time"
"github.com/apache/trafficcontrol/lib/go-log"
@@ -34,11 +40,13 @@ import (
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/parameter"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
validation "github.com/go-ozzo/ozzo-validation"
"github.com/jmoiron/sqlx"
)
+// Supported (non-pagination) query string parameters for /profiles.
const (
CDNQueryParam = "cdn"
DescriptionQueryParam = "description"
@@ -96,7 +104,21 @@ func (prof *TOProfile) GetType() string {
func (prof *TOProfile) Validate() error {
errs := validation.Errors{
- NameQueryParam: validation.Validate(prof.Name,
validation.Required),
+ NameQueryParam: validation.Validate(prof.Name, validation.By(
+ func(value interface{}) error {
+ name, ok := value.(*string)
+ if !ok {
+ return fmt.Errorf("wrong type, need:
string, got: %T", value)
+ }
+ if name == nil || *name == "" {
+ return errors.New("required and cannot
be blank")
+ }
+ if strings.Contains(*name, " ") {
+ return errors.New("cannot contain
spaces")
+ }
+ return nil
+ },
+ )),
DescriptionQueryParam: validation.Validate(prof.Description,
validation.Required),
CDNQueryParam: validation.Validate(prof.CDNID,
validation.Required),
TypeQueryParam: validation.Validate(prof.Type,
validation.Required),
diff --git a/traffic_ops/traffic_ops_golang/profile/profiles_test.go
b/traffic_ops/traffic_ops_golang/profile/profiles_test.go
index 4a25ded..26a98dc 100644
--- a/traffic_ops/traffic_ops_golang/profile/profiles_test.go
+++ b/traffic_ops/traffic_ops_golang/profile/profiles_test.go
@@ -22,6 +22,7 @@ package profile
import (
"errors"
"reflect"
+ "strings"
"testing"
"time"
@@ -141,11 +142,26 @@ func TestValidate(t *testing.T) {
expected := util.JoinErrsStr(test.SortErrors([]error{
errors.New("'cdn' cannot be blank"),
errors.New("'description' cannot be blank"),
- errors.New("'name' cannot be blank"),
+ errors.New("'name' required and cannot be blank"),
errors.New("'type' cannot be blank"),
}))
if !reflect.DeepEqual(expected, errs) {
t.Errorf("expected %++v, got %++v", expected, errs)
}
+
+ p.CDNID = new(int)
+ *p.CDNID = 1
+ p.Description = new(string)
+ *p.Description = "description"
+ p.Name = new(string)
+ *p.Name = "A name with spaces"
+ p.Type = new(string)
+ *p.Type = "type"
+
+ err := p.Validate()
+ if !strings.Contains(err.Error(), "cannot contain spaces") {
+ t.Error("Expected an error about the Profile name containing
spaces")
+ }
+
}
diff --git
a/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html
b/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html
index 5538c0a..e281f48 100644
--- a/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html
+++ b/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html
@@ -51,7 +51,7 @@ under the License.
<div class="form-group" ng-class="{'has-error':
hasError(profileForm.name), 'has-feedback': hasError(profileForm.name)}">
<label class="control-label col-md-2 col-sm-2 col-xs-12">Name
*</label>
<div class="col-md-10 col-sm-10 col-xs-12">
- <input name="name" type="text" class="form-control"
ng-model="profile.name" ng-pattern="/^\S*$/" required autofocus>
+ <input name="name" type="text" class="form-control"
ng-model="profile.name" pattern="\S+" required autofocus>
<small class="input-error"
ng-show="hasPropertyError(profileForm.name, 'required')">Required</small>
<small class="input-error"
ng-show="hasPropertyError(profileForm.name, 'maxlength')">Too Long</small>
<small class="input-error"
ng-show="hasPropertyError(profileForm.name, 'pattern')">No spaces</small>