-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(kv): label names to be unique #15647
Changes from all commits
3f1c2e5
df9c5e9
18b1051
d8dce80
7ef860b
018b841
8bd8e08
261bff2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ import ( | |
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/influxdata/influxdb" | ||
"github.com/influxdata/influxdb/kit/tracing" | ||
|
@@ -12,6 +14,7 @@ import ( | |
var ( | ||
labelBucket = []byte("labelsv1") | ||
labelMappingBucket = []byte("labelmappingsv1") | ||
labelIndex = []byte("labelindexv1") | ||
) | ||
|
||
func (s *Service) initializeLabels(ctx context.Context, tx Tx) error { | ||
|
@@ -23,6 +26,10 @@ func (s *Service) initializeLabels(ctx context.Context, tx Tx) error { | |
return err | ||
} | ||
|
||
if _, err := tx.Bucket(labelIndex); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
@@ -85,7 +92,7 @@ func (s *Service) findLabelByID(ctx context.Context, tx Tx, id influxdb.ID) (*in | |
|
||
func filterLabelsFn(filter influxdb.LabelFilter) func(l *influxdb.Label) bool { | ||
return func(label *influxdb.Label) bool { | ||
return (filter.Name == "" || (filter.Name == label.Name)) && | ||
return (filter.Name == "" || (strings.EqualFold(filter.Name, label.Name))) && | ||
((filter.OrgID == nil) || (filter.OrgID != nil && *filter.OrgID == label.OrgID)) | ||
} | ||
} | ||
|
@@ -248,6 +255,19 @@ func (s *Service) deleteLabelMapping(ctx context.Context, tx Tx, m *influxdb.Lab | |
// CreateLabel creates a new label. | ||
func (s *Service) CreateLabel(ctx context.Context, l *influxdb.Label) error { | ||
err := s.kv.Update(ctx, func(tx Tx) error { | ||
if err := l.Validate(); err != nil { | ||
return &influxdb.Error{ | ||
Code: influxdb.EInvalid, | ||
Err: err, | ||
} | ||
} | ||
|
||
l.Name = strings.TrimSpace(l.Name) | ||
|
||
if err := s.uniqueLabelName(ctx, tx, l); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one thing we may want to do here is move the read for unqiueness outside the update. In cloud this would force a TX imagine, which seems like a bad idea for this. Moving this to a separate read before the update seems like a better idea 🤔. Also, why are we trimming white space on line 265? is that a behavior that we share across the kv implementations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re: transactions - I'm just leaving a note here since we chatted about this. This is an even larger issue that may not get addressed in this PR. TX is pretty heavily depended on unfortunately for reading/writing to kv buckets. |
||
return err | ||
} | ||
|
||
l.ID = s.IDGenerator.ID() | ||
|
||
if err := s.putLabel(ctx, tx, l); err != nil { | ||
|
@@ -395,7 +415,34 @@ func (s *Service) updateLabel(ctx context.Context, tx Tx, id influxdb.ID, upd in | |
} | ||
|
||
if upd.Name != "" { | ||
upd.Name = strings.TrimSpace(upd.Name) | ||
|
||
idx, err := tx.Bucket(labelIndex) | ||
if err != nil { | ||
return nil, &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
key, err := labelIndexKey(label) | ||
if err != nil { | ||
return nil, &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
if err := idx.Delete(key); err != nil { | ||
return nil, &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
label.Name = upd.Name | ||
if err := s.uniqueLabelName(ctx, tx, label); err != nil { | ||
return nil, &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
} | ||
|
||
if err := label.Validate(); err != nil { | ||
|
@@ -430,6 +477,26 @@ func (s *Service) putLabel(ctx context.Context, tx Tx, l *influxdb.Label) error | |
} | ||
} | ||
|
||
idx, err := tx.Bucket(labelIndex) | ||
if err != nil { | ||
return &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
key, err := labelIndexKey(l) | ||
if err != nil { | ||
return &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
if err := idx.Put([]byte(key), encodedID); err != nil { | ||
return &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
b, err := tx.Bucket(labelBucket) | ||
if err != nil { | ||
return err | ||
|
@@ -499,7 +566,7 @@ func (s *Service) DeleteLabel(ctx context.Context, id influxdb.ID) error { | |
} | ||
|
||
func (s *Service) deleteLabel(ctx context.Context, tx Tx, id influxdb.ID) error { | ||
_, err := s.findLabelByID(ctx, tx, id) | ||
label, err := s.findLabelByID(ctx, tx, id) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -521,6 +588,26 @@ func (s *Service) deleteLabel(ctx context.Context, tx Tx, id influxdb.ID) error | |
} | ||
} | ||
|
||
idx, err := tx.Bucket(labelIndex) | ||
if err != nil { | ||
return &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
key, err := labelIndexKey(label) | ||
if err != nil { | ||
return &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
if err := idx.Delete(key); err != nil { | ||
return &influxdb.Error{ | ||
Err: err, | ||
} | ||
} | ||
|
||
if err := s.deleteUserResourceMappings(ctx, tx, influxdb.UserResourceMappingFilter{ | ||
ResourceID: id, | ||
ResourceType: influxdb.LabelsResourceType, | ||
|
@@ -530,3 +617,41 @@ func (s *Service) deleteLabel(ctx context.Context, tx Tx, id influxdb.ID) error | |
|
||
return nil | ||
} | ||
|
||
// labelAlreadyExistsError is used when creating a new label with | ||
// a name that has already been used. Label names must be unique. | ||
func labelAlreadyExistsError(lbl *influxdb.Label) error { | ||
return &influxdb.Error{ | ||
Code: influxdb.EConflict, | ||
Msg: fmt.Sprintf("label with name %s already exists", lbl.Name), | ||
} | ||
} | ||
|
||
func labelIndexKey(l *influxdb.Label) ([]byte, error) { | ||
orgID, err := l.OrgID.Encode() | ||
if err != nil { | ||
return nil, &influxdb.Error{ | ||
Code: influxdb.EInvalid, | ||
Err: err, | ||
} | ||
} | ||
|
||
k := make([]byte, influxdb.IDLength+len(l.Name)) | ||
copy(k, orgID) | ||
copy(k[influxdb.IDLength:], []byte(strings.ToLower((l.Name)))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we making names unique case insensitive now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup. label names should be unique & case agnostic. the UI currently handles all this stuff. feels like it's eagerly loading the labels and doing the label name validation in the browser... for this change I added, the casing of the label names in the label bucket shouldn't matter & can be a mix of upper and lower case no problem. this system bucket is going to store the down-cased version of the label name, and we'll be using that to decide if a matching label name string already exists. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. excellent! still feels like a service concern, not necessarily a store concern 🤷♂, not even sure where that fits in here tbh There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed |
||
return k, nil | ||
} | ||
|
||
func (s *Service) uniqueLabelName(ctx context.Context, tx Tx, lbl *influxdb.Label) error { | ||
key, err := labelIndexKey(lbl) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// labels are unique by `organization:label_name` | ||
err = s.unique(ctx, tx, labelIndex, key) | ||
if err == NotUniqueError { | ||
return labelAlreadyExistsError(lbl) | ||
} | ||
return err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this change to maintain this with
utf8
chars? what's the motivation behind this change?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah. our CI linter also seemed to agree with the change.
I had changed things originally to deal w/ finding labels case insensitively like so
strings.ToLower(filter.Name) == strings.ToLower(label.Name)
and CI called foul on that use.