blob: bc2abc904c75562e5c276d5e9dd1d3c49dccdf80 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 diagnose
import (
"bytes"
"context"
"fmt"
"github.com/apache/servicecomb-service-center/client"
"github.com/apache/servicecomb-service-center/pkg/dump"
"github.com/apache/servicecomb-service-center/scctl/etcd"
"github.com/apache/servicecomb-service-center/scctl/pkg/cmd"
"github.com/spf13/cobra"
"go.etcd.io/etcd/api/v3/mvccpb"
clientv3 "go.etcd.io/etcd/client/v3"
)
const (
service = "service"
instance = "instance"
)
const (
greater = iota
mismatch
less
)
var typeMap = map[string]string{
service: "/cse-sr/ms/files/",
instance: "/cse-sr/inst/files/",
}
type etcdResponse map[string][]*mvccpb.KeyValue
func CommandFunc(_ *cobra.Command, args []string) {
// initialize sc/etcd clients
scClient, err := client.NewSCClient(cmd.ScClientConfig)
if err != nil {
cmd.StopAndExit(cmd.ExitError, err)
}
etcdClient, err := etcd.NewEtcdClient(EtcdClientConfig)
if err != nil {
cmd.StopAndExit(cmd.ExitError, err)
}
defer etcdClient.Close()
// query etcd
etcdResp, err := getEtcdResponse(context.Background(), etcdClient)
if err != nil {
cmd.StopAndExit(cmd.ExitError, err)
}
// query sc
cache, scErr := scClient.GetScCache(context.Background())
if scErr != nil {
cmd.StopAndExit(cmd.ExitError, scErr)
}
// diagnose go...
details, err := diagnose(cache, etcdResp)
if err != nil {
fmt.Println(details) // stdout
cmd.StopAndExit(cmd.ExitError, err) // stderr
}
}
func getEtcdResponse(ctx context.Context, etcdClient *clientv3.Client) (etcdResponse, error) {
etcdResp := make(etcdResponse)
for t, prefix := range typeMap {
if err := setResponse(ctx, etcdClient, t, prefix, etcdResp); err != nil {
return nil, err
}
}
return etcdResp, nil
}
func setResponse(ctx context.Context, etcdClient *clientv3.Client, key, prefix string, etcdResp etcdResponse) error {
resp, err := etcdClient.Get(ctx, prefix, clientv3.WithPrefix())
if err != nil {
return err
}
etcdResp[key] = resp.Kvs
return nil
}
func diagnose(cache *dump.Cache, etcdResp etcdResponse) (details string, err error) {
var (
service = ServiceCompareHolder{Cache: cache.Microservices, Kvs: etcdResp[service]}
instance = InstanceCompareHolder{Cache: cache.Instances, Kvs: etcdResp[instance]}
)
sr := service.Compare()
ir := instance.Compare()
var (
b bytes.Buffer
full bytes.Buffer
)
writeResult(&b, &full, sr, ir)
if b.Len() > 0 {
return full.String(), fmt.Errorf("error: %s", b.String())
}
return "", nil
}
func writeResult(b *bytes.Buffer, full *bytes.Buffer, rss ...*CompareResult) {
g, m, l := make(map[string][]string), make(map[string][]string), make(map[string][]string)
for _, rs := range rss {
for t, arr := range rs.Results {
switch t {
case greater:
g[rs.Name] = arr
case mismatch:
m[rs.Name] = arr
case less:
l[rs.Name] = arr
}
}
}
i := 0
if s := len(g); s > 0 {
i++
header := fmt.Sprintf("%d. found in cache but not in etcd ", i)
b.WriteString(header)
full.WriteString(header)
writeBody(full, g)
}
if s := len(m); s > 0 {
i++
header := fmt.Sprintf("%d. found different between cache and etcd ", i)
b.WriteString(header)
full.WriteString(header)
writeBody(full, m)
}
if s := len(l); s > 0 {
i++
header := fmt.Sprintf("%d. found in etcd but not in cache ", i)
b.WriteString(header)
full.WriteString(header)
writeBody(full, l)
}
if l := b.Len(); l > 0 {
b.Truncate(l - 1)
full.Truncate(full.Len() - 1)
}
}
func writeBody(b *bytes.Buffer, r map[string][]string) {
b.WriteString("\b, details:\n")
for t, v := range r {
writeSection(b, t)
b.WriteString(fmt.Sprint(v))
b.WriteRune('\n')
}
}
func writeSection(b *bytes.Buffer, t string) {
b.WriteString(" ")
b.WriteString(t)
b.WriteString(": ")
}