Skip to content

Commit

Permalink
feature: bcs-storage support custom resources and labelSelector for k…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexAi27 committed Apr 7, 2021
1 parent 78db5bf commit 9e546b8
Show file tree
Hide file tree
Showing 8 changed files with 638 additions and 14 deletions.
110 changes: 110 additions & 0 deletions bcs-services/bcs-storage/storage/actions/lib/restreq.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,27 @@ package lib
import (
"strconv"
"strings"
"time"

"github.com/emicklei/go-restful"
"go.mongodb.org/mongo-driver/bson/primitive"

"github.com/Tencent/bk-bcs/bcs-common/common/blog"
"github.com/Tencent/bk-bcs/bcs-common/pkg/odm/operator"
)

const (
defaultMaxMemory = 32 << 20 // 32 MB
labelSelectorTag = "labelSelector"
labelSelectorPrefix = "data.metadata.labels."
extraTag = "extra"
fieldTag = "field"
offsetTag = "offset"
limitTag = "limit"
updateTimeQueryTag = "updateTimeBefore"
updateTimeTag = "updateTime"
)

// GetQueryParamString get string from rest query parameter
func GetQueryParamString(req *restful.Request, key string) string {
return req.QueryParameter(key)
Expand Down Expand Up @@ -55,6 +69,102 @@ func GetQueryParamInt64(req *restful.Request, key string, defaultValue int64) (i
return strconv.ParseInt(s, 10, 64)
}

func buildLeafCondition(key, value, sep string, op operator.Operator) *operator.Condition {
valueList := strings.Split(value, sep)
if len(valueList) == 1 && strings.TrimSpace(valueList[0]) == "" {
return operator.NewLeafCondition(operator.Ext, key)
}
return operator.NewLeafCondition(op, operator.M{key: valueList})
}

func buildSelectorCondition(prefix string, valueList []string) *operator.Condition {
valueStr := strings.Join(valueList, ",")
selector := &Selector{Prefix: prefix, SelectorStr: valueStr}
conds := selector.GetAllConditions()
// TODO error deal
if conds == nil {
return operator.NewLeafCondition(operator.Tr, operator.M{})
}
return operator.NewBranchCondition(operator.And, conds...)
// kvList := strings.Split(valueStr, ",")
// conds := make([]*operator.Condition, 0)
// for _, expr := range kvList {
// // nequal expression
// exprList := strings.Split(expr, "!=")
// if len(exprList) == 2 {
// conds = append(conds, buildLeafCondition(prefix+exprList[0], exprList[1], "|", operator.Nin))
// continue
// }
// // equal expression
// exprList = strings.Split(expr, "=")
// if len(exprList) == 2 {
// conds = append(conds, buildLeafCondition(prefix+exprList[0], exprList[1], "|", operator.In))
// continue
// }
// // exist expression
// conds = append(conds, operator.NewLeafCondition(operator.Ext, prefix+expr))
// }
// return operator.NewBranchCondition(operator.And, conds...)
}

func buildNormalCondition(key string, valueList []string) *operator.Condition {
if len(valueList) == 0 {
return operator.NewLeafCondition(operator.Ext, key)
}
if len(valueList) == 1 && strings.TrimSpace(valueList[0]) == "" {
return operator.NewLeafCondition(operator.Ext, key)
}
conds := make([]*operator.Condition, 0)
for _, value := range valueList {
conds = append(conds, buildLeafCondition(key, value, ",", operator.In))
}
return operator.NewBranchCondition(operator.And, conds...)
}

// GetCustomCondition get custom condition from req url and parameter
func GetCustomCondition(req *restful.Request) *operator.Condition {
if req.Request.Form == nil {
req.Request.ParseMultipartForm(defaultMaxMemory)
}
if req.Request.Form == nil || len(req.Request.Form) == 0 {
return nil
}
conds := make([]*operator.Condition, 0)
// empty Condition
rootCondition := operator.NewLeafCondition(operator.Tr, operator.M{})
for key, valueList := range req.Request.Form {
switch key {
// labelSelector=tag1=val1,tag2+in+(v1,v2),tag3+notin+(v1,v2),tag4+!=+(v1,v2),tag5
case labelSelectorTag:
conds = append(conds, buildSelectorCondition(labelSelectorPrefix, valueList))
case extraTag, fieldTag, limitTag, offsetTag:
break
// updateTimeBefore=
case updateTimeQueryTag:
var t time.Time
ts, err := strconv.ParseInt(valueList[0], 10, 64)
if err != nil {
var innerErr error
t, innerErr = time.Parse("2006-01-02T15:04:05.000Z", valueList[0])
if innerErr != nil {
blog.Errorf("Unrecognized update time (%s) format, neither timestamp in seconds format nor time expression like 2006-01-02T15:04:05.000Z", valueList[0])
break
}
} else {
t = time.Unix(int64(ts), 0)
}
conds = append(conds, operator.NewLeafCondition(operator.Lt, operator.M{updateTimeTag: t}))
// col1=val1,val2
default:
conds = append(conds, buildNormalCondition(key, valueList))
}
}
if len(conds) != 0 {
rootCondition = operator.NewBranchCondition(operator.And, conds...)
}
return rootCondition
}

// FormatTime format time
func FormatTime(data []operator.M, needTimeFormatList []string) {
// Some time-field need to be format before return
Expand Down
200 changes: 200 additions & 0 deletions bcs-services/bcs-storage/storage/actions/lib/selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Tencent is pleased to support the open source community by making Blueking Container Service available.,
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* 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.
*/

package lib

import (
"github.com/Tencent/bk-bcs/bcs-common/common/blog"
"github.com/Tencent/bk-bcs/bcs-common/pkg/odm/operator"
)

var opCharacterSet = []byte{'=', '!'}
var specialCharacterSet = []byte{',', '+', '(', ')'}

type Selector struct {
Prefix string
SelectorStr string
cursor int
conditions []*operator.Condition
}

// TODO: error operation
func (s *Selector) GetNextCondition() *operator.Condition {
if s.cursor >= len(s.SelectorStr) {
return nil
}
// get key
key := s.getWord(false)
if key == "" {
return nil
}
if s.Prefix != "" {
key = s.Prefix + key
}
// get op
op := s.getOperator()
blog.Infof("getOperator: %s", op)
// get value
var cond *operator.Condition
switch op {
case operator.Ext:
cond = operator.NewLeafCondition(op, operator.M{key: true})
case operator.Eq, operator.Ne:
// get Value
value := s.getWord(false)
if value == "" {
return nil
}
cond = operator.NewLeafCondition(op, operator.M{key: value})
case operator.In, operator.Nin:
wordList := s.getWordList()
if len(wordList) == 0 {
return nil
}
cond = operator.NewLeafCondition(op, operator.M{key: wordList})
}
s.conditions = append(s.conditions, cond)
blog.Infof("GetNextCondition: %+v", cond)
// get expression end (, or end of string)
s.getCharacter(',')
return cond
}

func (s *Selector) GetAllConditions() []*operator.Condition {
if s.conditions != nil {
return s.conditions
}
var cond *operator.Condition
for {
cond = s.GetNextCondition()
if cond == nil {
break
}
}
return s.conditions
}

func (s *Selector) Clear() {
s.cursor = 0
s.conditions = nil
}

func (s *Selector) getWord(isOperator bool) string {
if s.cursor >= len(s.SelectorStr) {
return ""
}
var word = make([]byte, 0)
for {
if s.cursor >= len(s.SelectorStr) {
break
}
c := s.SelectorStr[s.cursor]
if s.cursor >= len(s.SelectorStr) ||
s.inSpecialCharacterSet(c) ||
(!isOperator && s.inOPCharacterSet(c)) {
blog.Infof("break word: %v", c)
break
}
word = append(word, c)
s.cursor++
}
blog.Infof("getWord %s", string(word))
return string(word)
}

func (s *Selector) getWordList() []string {
// list start
if !s.getCharacter('(') {
return nil
}

wordList := make([]string, 0)
for {
word := s.getWord(false)
if word == "" {
return nil
}
wordList = append(wordList, word)
if !s.getCharacter(',') {
break
}
}

// list end
if !s.getCharacter(')') {
return nil
}
blog.Infof("getWordList %+v", wordList)
return wordList
}

func (s *Selector) getOperator() operator.Operator {
if s.cursor >= len(s.SelectorStr) {
return operator.Ext
}
if s.getCharacter(',') {
return operator.Ext
}
if s.getCharacter('=') {
return operator.Eq
}
if s.getCharacter('+') {
opstr := s.getWord(true)
var op operator.Operator
switch opstr {
case "in":
op = operator.In
case "notin":
op = operator.Nin
case "=", "==":
op = operator.Eq
case "!=":
op = operator.Ne
default:
// getOperator failed
return operator.Tr
}
if s.getCharacter('+') {
return op
}
}
return operator.Tr
}

func (s *Selector) getCharacter(standard byte) bool {
if s.cursor >= len(s.SelectorStr) {
return false
}
if s.SelectorStr[s.cursor] == standard {
s.cursor++
return true
}
return false
}

func (s *Selector) inSpecialCharacterSet(c byte) bool {
for _, sc := range specialCharacterSet {
if c == sc {
return true
}
}
return false
}

func (s *Selector) inOPCharacterSet(c byte) bool {
for _, sc := range opCharacterSet {
if c == sc {
return true
}
}
return false
}
Loading

0 comments on commit 9e546b8

Please sign in to comment.