blob: 7d501263092b1db6c114c8dda49024403640e72a [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"
"fmt"
"github.com/apache/servicecomb-service-center/pkg/client/etcd"
"github.com/apache/servicecomb-service-center/pkg/client/sc"
"github.com/apache/servicecomb-service-center/scctl/pkg/cmd"
"github.com/apache/servicecomb-service-center/server/admin/model"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
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 DiagnoseCommandFunc(_ *cobra.Command, args []string) {
// initialize sc/etcd clients
scClient, err := sc.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()
if scErr != nil {
cmd.StopAndExit(cmd.ExitError, scErr)
}
// diagnose go...
err, details := 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 *model.Cache, etcdResp etcdResponse) (err error, details string) {
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 fmt.Errorf("error: %s", b.String()), full.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(": ")
}