allow to search deleted workload logs

Signed-off-by: huanggze <loganhuang@yunify.com>
This commit is contained in:
huanggze
2019-09-11 10:35:44 +08:00
parent 1badf13538
commit 8d85a56386
4 changed files with 310 additions and 405 deletions

View File

@@ -26,8 +26,19 @@ import (
)
const (
OperationQuery int = iota
OperationStatistics
OperationHistogram
matchPhrase = iota
matchPhrasePrefix
regexpQuery
podNameMaxLength = 63
// max 10 characters + 1 hyphen
replicaSetSuffixMaxLength = 11
// a unique random string as suffix, 5 characters + 1 hyphen
randSuffixLength = 6
fieldPodName = "kubernetes.pod_name"
fieldContainerName = "kubernetes.container_name"
@@ -47,13 +58,6 @@ var (
client Client
)
type Config struct {
Host string
Port string
Index string
VersionMajor string
}
func (cfg *Config) WriteESConfigs() {
mu.Lock()
defer mu.Unlock()
@@ -68,109 +72,6 @@ func (cfg *Config) WriteESConfigs() {
client = NewForConfig(config)
}
type Request struct {
From int64 `json:"from"`
Size int64 `json:"size"`
Sorts []Sort `json:"sort,omitempty"`
MainQuery BoolQuery `json:"query"`
Aggs interface{} `json:"aggs,omitempty"`
MainHighLight MainHighLight `json:"highlight,omitempty"`
}
type Sort struct {
Order Order `json:"time"`
}
type Order struct {
Order string `json:"order"`
}
type BoolQuery struct {
Bool interface{} `json:"bool"`
}
// user filter instead of must
// filter ignores scoring
type BoolFilter struct {
Filter []interface{} `json:"filter"`
}
type BoolShould struct {
Should []interface{} `json:"should"`
MinimumShouldMatch int64 `json:"minimum_should_match"`
}
type RangeQuery struct {
RangeSpec RangeSpec `json:"range"`
}
type RangeSpec struct {
TimeRange TimeRange `json:"time"`
}
type TimeRange struct {
Gte string `json:"gte,omitempty"`
Lte string `json:"lte,omitempty"`
}
type MatchPhrase struct {
MatchPhrase map[string]string `json:"match_phrase"`
}
type MatchPhrasePrefix struct {
MatchPhrasePrefix interface{} `json:"match_phrase_prefix"`
}
type MainHighLight struct {
Fields []interface{} `json:"fields,omitempty"`
FragmentSize int `json:"fragment_size"`
}
type LogHighLightField struct {
FieldContent EmptyField `json:"log"`
}
type NamespaceHighLightField struct {
FieldContent EmptyField `json:"kubernetes.namespace_name.keyword"`
}
type PodHighLightField struct {
FieldContent EmptyField `json:"kubernetes.pod_name.keyword"`
}
type ContainerHighLightField struct {
FieldContent EmptyField `json:"kubernetes.container_name.keyword"`
}
type EmptyField struct {
}
// StatisticsAggs, the struct for `aggs` of type Request, holds a cardinality aggregation for distinct container counting
type StatisticsAggs struct {
ContainerAgg ContainerAgg `json:"containers"`
}
type ContainerAgg struct {
Cardinality AggField `json:"cardinality"`
}
type AggField struct {
Field string `json:"field"`
}
type HistogramAggs struct {
HistogramAgg HistogramAgg `json:"histogram"`
}
type HistogramAgg struct {
DateHistogram DateHistogram `json:"date_histogram"`
}
type DateHistogram struct {
Field string `json:"field"`
Interval string `json:"interval"`
}
func createQueryRequest(param QueryParameters) (int, []byte, error) {
var request Request
var mainBoolQuery BoolFilter
@@ -192,7 +93,7 @@ func createQueryRequest(param QueryParameters) (int, []byte, error) {
mainBoolQuery.Filter = append(mainBoolQuery.Filter, BoolQuery{Bool: boolShould})
}
if param.WorkloadFilter != nil {
boolQuery := makeBoolShould(matchPhrase, fieldPodNameKeyword, param.WorkloadFilter)
boolQuery := makeBoolShould(regexpQuery, fieldPodNameKeyword, param.WorkloadFilter)
mainBoolQuery.Filter = append(mainBoolQuery.Filter, boolQuery)
}
if param.PodFilter != nil {
@@ -204,6 +105,10 @@ func createQueryRequest(param QueryParameters) (int, []byte, error) {
mainBoolQuery.Filter = append(mainBoolQuery.Filter, boolQuery)
}
if param.WorkloadQuery != nil {
boolQuery := makeBoolShould(matchPhrasePrefix, fieldPodName, param.WorkloadQuery)
mainBoolQuery.Filter = append(mainBoolQuery.Filter, boolQuery)
}
if param.PodQuery != nil {
boolQuery := makeBoolShould(matchPhrasePrefix, fieldPodName, param.PodQuery)
mainBoolQuery.Filter = append(mainBoolQuery.Filter, boolQuery)
@@ -278,6 +183,8 @@ func makeBoolShould(queryType int, field string, list []string) BoolQuery {
q = MatchPhrase{MatchPhrase: map[string]string{field: phrase}}
case matchPhrasePrefix:
q = MatchPhrasePrefix{MatchPhrasePrefix: map[string]string{field: phrase}}
case regexpQuery:
q = RegexpQuery{Regexp: map[string]string{field: makePodNameRegexp(phrase)}}
}
should = append(should, q)
@@ -291,127 +198,31 @@ func makeBoolShould(queryType int, field string, list []string) BoolQuery {
}
}
// Fore more info, refer to https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-search-API.html
// Response from the elasticsearch engine
type Response struct {
Status int `json:"status"`
Workspace string `json:"workspace,omitempty"`
Shards Shards `json:"_shards"`
Hits Hits `json:"hits"`
Aggregations json.RawMessage `json:"aggregations"`
func makePodNameRegexp(workloadName string) string {
var regexp string
if len(workloadName) <= podNameMaxLength-replicaSetSuffixMaxLength-randSuffixLength {
// match deployment pods, eg. <deploy>-579dfbcddd-24znw
// replicaset rand string is limited to vowels
// https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/rand/rand.go#L83
regexp += workloadName + "-[bcdfghjklmnpqrstvwxz2456789]{1,10}-[a-z0-9]{5}|"
// match statefulset pods, eg. <sts>-0
regexp += workloadName + "-[0-9]+|"
// match pods of daemonset or job, eg. <ds>-29tdk, <job>-5xqvl
regexp += workloadName + "-[a-z0-9]{5}"
} else if len(workloadName) <= podNameMaxLength-randSuffixLength {
replicaSetSuffixLength := podNameMaxLength - randSuffixLength - len(workloadName)
regexp += fmt.Sprintf("%s%d%s", workloadName+"-[bcdfghjklmnpqrstvwxz2456789]{", replicaSetSuffixLength, "}[a-z0-9]{5}|")
regexp += workloadName + "-[0-9]+|"
regexp += workloadName + "-[a-z0-9]{5}"
} else {
// Rand suffix may overwrites the workload name if the name is too long
// This won't happen for StatefulSet because a statefulset pod will fail to create
regexp += workloadName[:podNameMaxLength-randSuffixLength+1] + "[a-z0-9]{5}|"
regexp += workloadName + "-[0-9]+"
}
return regexp
}
type Shards struct {
Total int64 `json:"total"`
Successful int64 `json:"successful"`
Skipped int64 `json:"skipped"`
Failed int64 `json:"failed"`
}
type Hits struct {
// As of ElasticSearch v7.x, hits.total is changed
Total interface{} `json:"total"`
Hits []Hit `json:"hits"`
}
type Hit struct {
Source Source `json:"_source"`
HighLight HighLight `json:"highlight"`
Sort []int64 `json:"sort"`
}
type Source struct {
Log string `json:"log"`
Time string `json:"time"`
Kubernetes Kubernetes `json:"kubernetes"`
}
type Kubernetes struct {
Namespace string `json:"namespace_name"`
Pod string `json:"pod_name"`
Container string `json:"container_name"`
Host string `json:"host"`
}
type HighLight struct {
LogHighLights []string `json:"log,omitempty" description:"log messages to highlight"`
NamespaceHighLights []string `json:"kubernetes.namespace_name.keyword,omitempty" description:"namespaces to highlight"`
PodHighLights []string `json:"kubernetes.pod_name.keyword,omitempty" description:"pods to highlight"`
ContainerHighLights []string `json:"kubernetes.container_name.keyword,omitempty" description:"containers to highlight"`
}
type LogRecord struct {
Time int64 `json:"time,omitempty" description:"log timestamp"`
Log string `json:"log,omitempty" description:"log message"`
Namespace string `json:"namespace,omitempty" description:"namespace"`
Pod string `json:"pod,omitempty" description:"pod name"`
Container string `json:"container,omitempty" description:"container name"`
Host string `json:"host,omitempty" description:"node id"`
HighLight HighLight `json:"highlight,omitempty" description:"highlighted log fragment"`
}
type ReadResult struct {
Total int64 `json:"total" description:"total number of matched results"`
From int64 `json:"from" description:"the offset from the result set"`
Size int64 `json:"size" description:"the amount of hits to be returned"`
Records []LogRecord `json:"records,omitempty" description:"actual array of results"`
}
// StatisticsResponseAggregations, the struct for `aggregations` of type Reponse, holds return results from the aggregation StatisticsAggs
type StatisticsResponseAggregations struct {
ContainerCount ContainerCount `json:"containers"`
}
type ContainerCount struct {
Value int64 `json:"value"`
}
type HistogramAggregations struct {
HistogramAggregation HistogramAggregation `json:"histogram"`
}
type HistogramAggregation struct {
Histograms []HistogramStatistics `json:"buckets"`
}
type HistogramStatistics struct {
Time int64 `json:"key"`
Count int64 `json:"doc_count"`
}
type HistogramRecord struct {
Time int64 `json:"time" description:"timestamp"`
Count int64 `json:"count" description:"total number of logs at intervals"`
}
type StatisticsResult struct {
Containers int64 `json:"containers" description:"total number of containers"`
Logs int64 `json:"logs" description:"total number of logs"`
}
type HistogramResult struct {
Total int64 `json:"total" description:"total number of logs"`
StartTime int64 `json:"start_time" description:"start time"`
EndTime int64 `json:"end_time" description:"end time"`
Interval string `json:"interval" description:"interval"`
Histograms []HistogramRecord `json:"histograms" description:"actual array of histogram results"`
}
// Wrap elasticsearch response
type QueryResult struct {
Status int `json:"status,omitempty" description:"query status"`
Error string `json:"error,omitempty" description:"debugging information"`
Read *ReadResult `json:"query,omitempty" description:"query results"`
Statistics *StatisticsResult `json:"statistics,omitempty" description:"statistics results"`
Histogram *HistogramResult `json:"histogram,omitempty" description:"histogram results"`
}
const (
OperationQuery int = iota
OperationStatistics
OperationHistogram
)
func calcTimestamp(input string) int64 {
var t time.Time
var err error
@@ -521,38 +332,11 @@ func parseQueryResult(operation int, param QueryParameters, body []byte) *QueryR
return &queryResult
}
type QueryParameters struct {
// when true, indicates the provided `namespaces` or `namespace_query` doesn't match any namespace
NamespaceNotFound bool
// a map of namespace with creation time
NamespaceWithCreationTime map[string]string
// when true, indicates the provided `workloads` or `workload_query` doesn't match any workload
WorkloadNotFound bool
WorkloadFilter []string
PodFilter []string
PodQuery []string
ContainerFilter []string
ContainerQuery []string
LogQuery []string
Operation string
Interval string
StartTime string
EndTime string
Sort string
From int64
Size int64
}
func Query(param QueryParameters) *QueryResult {
var queryResult = new(QueryResult)
if param.NamespaceNotFound || param.WorkloadNotFound {
if param.NamespaceNotFound {
queryResult = new(QueryResult)
queryResult.Status = http.StatusOK
switch param.Operation {