mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
213 lines
5.7 KiB
Go
213 lines
5.7 KiB
Go
// Copyright 2022 The etcd Authors
|
|
//
|
|
// Licensed 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 report
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/anishathalye/porcupine"
|
|
"go.uber.org/zap"
|
|
|
|
"go.etcd.io/etcd/tests/v3/robustness/model"
|
|
)
|
|
|
|
type ClientReport struct {
|
|
ClientID int
|
|
KeyValue []porcupine.Operation
|
|
Watch []model.WatchOperation
|
|
}
|
|
|
|
func (r ClientReport) SuccessfulOperations() int {
|
|
count := 0
|
|
for _, op := range r.KeyValue {
|
|
resp := op.Output.(model.MaybeEtcdResponse)
|
|
if resp.Error == "" {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (r ClientReport) WatchEventCount() int {
|
|
count := 0
|
|
for _, op := range r.Watch {
|
|
for _, resp := range op.Responses {
|
|
count += len(resp.Events)
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func persistClientReports(t *testing.T, lg *zap.Logger, path string, reports []ClientReport) {
|
|
sort.Slice(reports, func(i, j int) bool {
|
|
return reports[i].ClientID < reports[j].ClientID
|
|
})
|
|
for _, r := range reports {
|
|
clientDir := filepath.Join(path, fmt.Sprintf("client-%d", r.ClientID))
|
|
err := os.MkdirAll(clientDir, 0700)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(r.Watch) != 0 {
|
|
persistWatchOperations(t, lg, filepath.Join(clientDir, "watch.json"), r.Watch)
|
|
}
|
|
if len(r.KeyValue) != 0 {
|
|
persistKeyValueOperations(t, lg, filepath.Join(clientDir, "operations.json"), r.KeyValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func LoadClientReports(path string) ([]ClientReport, error) {
|
|
files, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
reports := []ClientReport{}
|
|
for _, file := range files {
|
|
if file.IsDir() && strings.HasPrefix(file.Name(), "client-") {
|
|
idString := strings.Replace(file.Name(), "client-", "", 1)
|
|
id, err := strconv.Atoi(idString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to extract clientID from directory: %q", file.Name())
|
|
}
|
|
r, err := loadClientReport(filepath.Join(path, file.Name()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.ClientID = id
|
|
reports = append(reports, r)
|
|
}
|
|
}
|
|
sort.Slice(reports, func(i, j int) bool {
|
|
return reports[i].ClientID < reports[j].ClientID
|
|
})
|
|
return reports, nil
|
|
}
|
|
|
|
func loadClientReport(path string) (report ClientReport, err error) {
|
|
report.Watch, err = loadWatchOperations(filepath.Join(path, "watch.json"))
|
|
if err != nil {
|
|
return report, err
|
|
}
|
|
report.KeyValue, err = loadKeyValueOperations(filepath.Join(path, "operations.json"))
|
|
if err != nil {
|
|
return report, err
|
|
}
|
|
return report, nil
|
|
}
|
|
|
|
func loadWatchOperations(path string) (operations []model.WatchOperation, err error) {
|
|
_, err = os.Stat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
|
|
}
|
|
file, err := os.OpenFile(path, os.O_RDONLY, 0755)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
|
|
}
|
|
defer file.Close()
|
|
decoder := json.NewDecoder(file)
|
|
for decoder.More() {
|
|
var watch model.WatchOperation
|
|
err = decoder.Decode(&watch)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode watch operation, err: %w", err)
|
|
}
|
|
operations = append(operations, watch)
|
|
}
|
|
return operations, nil
|
|
}
|
|
|
|
func loadKeyValueOperations(path string) (operations []porcupine.Operation, err error) {
|
|
_, err = os.Stat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
|
|
}
|
|
file, err := os.OpenFile(path, os.O_RDONLY, 0755)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err)
|
|
}
|
|
defer file.Close()
|
|
decoder := json.NewDecoder(file)
|
|
for decoder.More() {
|
|
var operation struct {
|
|
ClientID int
|
|
Input model.EtcdRequest
|
|
Call int64
|
|
Output model.MaybeEtcdResponse
|
|
Return int64
|
|
}
|
|
err = decoder.Decode(&operation)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode watch operation, err: %w", err)
|
|
}
|
|
operations = append(operations, porcupine.Operation{
|
|
ClientId: operation.ClientID,
|
|
Input: operation.Input,
|
|
Call: operation.Call,
|
|
Output: operation.Output,
|
|
Return: operation.Return,
|
|
})
|
|
}
|
|
return operations, nil
|
|
}
|
|
|
|
func persistWatchOperations(t *testing.T, lg *zap.Logger, path string, responses []model.WatchOperation) {
|
|
lg.Info("Saving watch operations", zap.String("path", path))
|
|
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
|
if err != nil {
|
|
t.Errorf("Failed to save watch operations: %v", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
encoder := json.NewEncoder(file)
|
|
for _, resp := range responses {
|
|
err := encoder.Encode(resp)
|
|
if err != nil {
|
|
t.Errorf("Failed to encode operation: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func persistKeyValueOperations(t *testing.T, lg *zap.Logger, path string, operations []porcupine.Operation) {
|
|
lg.Info("Saving operation history", zap.String("path", path))
|
|
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
|
if err != nil {
|
|
t.Errorf("Failed to save operation history: %v", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
encoder := json.NewEncoder(file)
|
|
for _, op := range operations {
|
|
err := encoder.Encode(op)
|
|
if err != nil {
|
|
t.Errorf("Failed to encode operation: %v", err)
|
|
}
|
|
}
|
|
}
|