package bolt import ( "bytes" "context" "encoding/json" bolt "github.com/coreos/bbolt" influxdb "github.com/influxdata/influxdb" ) var ( labelBucket = []byte("labelsv1") labelMappingBucket = []byte("labelmappingsv1") ) func (c *Client) initializeLabels(ctx context.Context, tx *bolt.Tx) error { if _, err := tx.CreateBucketIfNotExists([]byte(labelBucket)); err != nil { return err } if _, err := tx.CreateBucketIfNotExists([]byte(labelMappingBucket)); err != nil { return err } return nil } // FindLabelByID finds a label by its ID func (c *Client) FindLabelByID(ctx context.Context, id influxdb.ID) (*influxdb.Label, error) { var l *influxdb.Label err := c.db.View(func(tx *bolt.Tx) error { label, pe := c.findLabelByID(ctx, tx, id) if pe != nil { return pe } l = label return nil }) if err != nil { return nil, &influxdb.Error{ Op: getOp(influxdb.OpFindLabelByID), Err: err, } } return l, nil } func (c *Client) findLabelByID(ctx context.Context, tx *bolt.Tx, id influxdb.ID) (*influxdb.Label, *influxdb.Error) { encodedID, err := id.Encode() if err != nil { return nil, &influxdb.Error{ Err: err, } } var l influxdb.Label v := tx.Bucket(labelBucket).Get(encodedID) if len(v) == 0 { return nil, &influxdb.Error{ Code: influxdb.ENotFound, Msg: influxdb.ErrLabelNotFound, } } if err := json.Unmarshal(v, &l); err != nil { return nil, &influxdb.Error{ Err: err, } } return &l, nil } func filterLabelsFn(filter influxdb.LabelFilter) func(l *influxdb.Label) bool { return func(label *influxdb.Label) bool { return (filter.Name == "" || (filter.Name == label.Name)) } } // FindLabels returns a list of labels that match a filter. func (c *Client) FindLabels(ctx context.Context, filter influxdb.LabelFilter, opt ...influxdb.FindOptions) ([]*influxdb.Label, error) { ls := []*influxdb.Label{} err := c.db.View(func(tx *bolt.Tx) error { labels, err := c.findLabels(ctx, tx, filter) if err != nil { return err } ls = labels return nil }) if err != nil { return nil, err } return ls, nil } func (c *Client) findLabels(ctx context.Context, tx *bolt.Tx, filter influxdb.LabelFilter) ([]*influxdb.Label, error) { ls := []*influxdb.Label{} filterFn := filterLabelsFn(filter) err := c.forEachLabel(ctx, tx, func(l *influxdb.Label) bool { if filterFn(l) { ls = append(ls, l) } return true }) if err != nil { return nil, err } return ls, nil } // func filterMappingsFn(filter influxdb.LabelMappingFilter) func(m *influxdb.LabelMapping) bool { // return func(mapping *influxdb.LabelMapping) bool { // return (filter.ResourceID.String() == mapping.ResourceID.String()) && // (filter.LabelID == nil || filter.LabelID == mapping.LabelID) // } // } func decodeLabelMappingKey(key []byte) (resourceID influxdb.ID, labelID influxdb.ID, err error) { if len(key) != 2*influxdb.IDLength { return 0, 0, &influxdb.Error{Code: influxdb.EInvalid, Msg: "malformed label mapping key (please report this error)"} } if err := (&resourceID).Decode(key[:influxdb.IDLength]); err != nil { return 0, 0, &influxdb.Error{Code: influxdb.EInvalid, Msg: "bad resource id", Err: influxdb.ErrInvalidID} } if err := (&labelID).Decode(key[influxdb.IDLength:]); err != nil { return 0, 0, &influxdb.Error{Code: influxdb.EInvalid, Msg: "bad label id", Err: influxdb.ErrInvalidID} } return resourceID, labelID, nil } func (c *Client) FindResourceLabels(ctx context.Context, filter influxdb.LabelMappingFilter) ([]*influxdb.Label, error) { if !filter.ResourceID.Valid() { return nil, &influxdb.Error{Code: influxdb.EInvalid, Msg: "filter requires a valid resource id", Err: influxdb.ErrInvalidID} } ls := []*influxdb.Label{} err := c.db.View(func(tx *bolt.Tx) error { cur := tx.Bucket(labelMappingBucket).Cursor() prefix, err := filter.ResourceID.Encode() if err != nil { return err } for k, _ := cur.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = cur.Next() { _, id, err := decodeLabelMappingKey(k) if err != nil { return err } l, err := c.findLabelByID(ctx, tx, id) if l == nil && err != nil { // TODO(jm): return error instead of continuing once orphaned mappings are fixed // (see https://github.com/influxdata/influxdb/issues/11278) continue } ls = append(ls, l) } return nil }) if err != nil { return nil, &influxdb.Error{ Err: err, Op: getOp(influxdb.OpFindLabelMapping), } } return ls, nil } // CreateLabelMapping creates a new mapping between a resource and a label. func (c *Client) CreateLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { _, err := c.FindLabelByID(ctx, m.LabelID) if err != nil { return &influxdb.Error{ Err: err, Op: getOp(influxdb.OpCreateLabel), } } err = c.db.Update(func(tx *bolt.Tx) error { return c.putLabelMapping(ctx, tx, m) }) if err != nil { return &influxdb.Error{ Err: err, Op: getOp(influxdb.OpCreateLabel), } } return nil } // DeleteLabelMapping deletes a label mapping. func (c *Client) DeleteLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { err := c.db.Update(func(tx *bolt.Tx) error { return c.deleteLabelMapping(ctx, tx, m) }) if err != nil { return &influxdb.Error{ Op: getOp(influxdb.OpDeleteLabelMapping), Err: err, } } return nil } func (c *Client) deleteLabelMapping(ctx context.Context, tx *bolt.Tx, m *influxdb.LabelMapping) error { key, err := labelMappingKey(m) if err != nil { return &influxdb.Error{ Err: err, } } if err := tx.Bucket(labelMappingBucket).Delete(key); err != nil { return &influxdb.Error{ Err: err, } } return nil } // CreateLabel creates a new label. func (c *Client) CreateLabel(ctx context.Context, l *influxdb.Label) error { err := c.db.Update(func(tx *bolt.Tx) error { l.ID = c.IDGenerator.ID() return c.putLabel(ctx, tx, l) }) if err != nil { return &influxdb.Error{ Err: err, Op: getOp(influxdb.OpCreateLabel), } } return nil } // PutLabel creates a label from the provided struct, without generating a new ID. func (c *Client) PutLabel(ctx context.Context, l *influxdb.Label) error { return c.db.Update(func(tx *bolt.Tx) error { var err error pe := c.putLabel(ctx, tx, l) if pe != nil { err = pe } return err }) } func labelMappingKey(m *influxdb.LabelMapping) ([]byte, error) { lid, err := m.LabelID.Encode() if err != nil { return nil, &influxdb.Error{ Code: influxdb.EInvalid, Err: err, } } rid, err := m.ResourceID.Encode() if err != nil { return nil, &influxdb.Error{ Code: influxdb.EInvalid, Err: err, } } key := make([]byte, len(rid)+len(lid)) copy(key, rid) copy(key[len(rid):], lid) return key, nil } func (c *Client) forEachLabel(ctx context.Context, tx *bolt.Tx, fn func(*influxdb.Label) bool) error { cur := tx.Bucket(labelBucket).Cursor() for k, v := cur.First(); k != nil; k, v = cur.Next() { l := &influxdb.Label{} if err := json.Unmarshal(v, l); err != nil { return err } if !fn(l) { break } } return nil } // UpdateLabel updates a label. func (c *Client) UpdateLabel(ctx context.Context, id influxdb.ID, upd influxdb.LabelUpdate) (*influxdb.Label, error) { var label *influxdb.Label err := c.db.Update(func(tx *bolt.Tx) error { labelResponse, pe := c.updateLabel(ctx, tx, id, upd) if pe != nil { return &influxdb.Error{ Err: pe, Op: getOp(influxdb.OpUpdateLabel), } } label = labelResponse return nil }) return label, err } func (c *Client) updateLabel(ctx context.Context, tx *bolt.Tx, id influxdb.ID, upd influxdb.LabelUpdate) (*influxdb.Label, error) { label, err := c.findLabelByID(ctx, tx, id) if err != nil { return nil, err } if label.Properties == nil { label.Properties = make(map[string]string) } for k, v := range upd.Properties { if v == "" { delete(label.Properties, k) } else { label.Properties[k] = v } } if upd.Name != "" { label.Name = upd.Name } if err := label.Validate(); err != nil { return nil, &influxdb.Error{ Code: influxdb.EInvalid, Err: err, } } if err := c.putLabel(ctx, tx, label); err != nil { return nil, &influxdb.Error{ Err: err, } } return label, nil } // set a label and overwrite any existing label func (c *Client) putLabel(ctx context.Context, tx *bolt.Tx, l *influxdb.Label) error { v, err := json.Marshal(l) if err != nil { return &influxdb.Error{ Err: err, } } encodedID, err := l.ID.Encode() if err != nil { return &influxdb.Error{ Err: err, } } if err := tx.Bucket(labelBucket).Put(encodedID, v); err != nil { return &influxdb.Error{ Err: err, } } return nil } // PutLabelMapping writes a label mapping to boltdb func (c *Client) PutLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { return c.db.Update(func(tx *bolt.Tx) error { var err error pe := c.putLabelMapping(ctx, tx, m) if pe != nil { err = pe } return err }) } func (c *Client) putLabelMapping(ctx context.Context, tx *bolt.Tx, m *influxdb.LabelMapping) error { v, err := json.Marshal(m) if err != nil { return &influxdb.Error{ Err: err, } } key, err := labelMappingKey(m) if err != nil { return &influxdb.Error{ Err: err, } } if err := tx.Bucket(labelMappingBucket).Put(key, v); err != nil { return &influxdb.Error{ Err: err, } } return nil } // DeleteLabel deletes a label. func (c *Client) DeleteLabel(ctx context.Context, id influxdb.ID) error { err := c.db.Update(func(tx *bolt.Tx) error { return c.deleteLabel(ctx, tx, id) }) if err != nil { return &influxdb.Error{ Op: getOp(influxdb.OpDeleteLabel), Err: err, } } return nil } func (c *Client) deleteLabel(ctx context.Context, tx *bolt.Tx, id influxdb.ID) error { _, err := c.findLabelByID(ctx, tx, id) if err != nil { return err } encodedID, idErr := id.Encode() if idErr != nil { return &influxdb.Error{ Err: idErr, } } return tx.Bucket(labelBucket).Delete(encodedID) } // func (c *Client) deleteLabels(ctx context.Context, tx *bolt.Tx, filter influxdb.LabelFilter) error { // ls, err := c.findLabels(ctx, tx, filter) // if err != nil { // return err // } // for _, l := range ls { // encodedID, idErr := l.ID.Encode() // if idErr != nil { // return &influxdb.Error{ // Err: idErr, // } // } // // if err = tx.Bucket(labelBucket).Delete(encodedID); err != nil { // return err // } // } // return nil // }