vitess.io/vitess@v0.16.2/go/test/fuzzing/vtctl_fuzzer.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package fuzzing
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  
    23  	"vitess.io/vitess/go/vt/logutil"
    24  	"vitess.io/vitess/go/vt/topo"
    25  	"vitess.io/vitess/go/vt/topo/memorytopo"
    26  	"vitess.io/vitess/go/vt/vtctl"
    27  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    28  	"vitess.io/vitess/go/vt/vttablet/tmclienttest"
    29  	"vitess.io/vitess/go/vt/wrangler"
    30  )
    31  
    32  func init() {
    33  	tmclienttest.SetProtocol("go.test.fuzzing.vtctl_fuzzer", "fuzzing")
    34  	tmclient.RegisterTabletManagerClientFactory("fuzzing", func() tmclient.TabletManagerClient {
    35  		return nil
    36  	})
    37  }
    38  
    39  func IsDivisibleBy(n int, divisibleby int) bool {
    40  	return (n % divisibleby) == 0
    41  }
    42  
    43  func getCommandType(index int) string {
    44  
    45  	m := map[int]string{
    46  		0:  "GetTablet", // Tablets
    47  		1:  "InitTablet",
    48  		2:  "UpdateTabletAddrs",
    49  		3:  "DeleteTablet",
    50  		4:  "SetReadOnly",
    51  		5:  "SetReadWrite",
    52  		6:  "StartReplication",
    53  		7:  "StopReplication",
    54  		8:  "ChangeTabletType",
    55  		9:  "Ping",
    56  		10: "RefreshState",
    57  		11: "RefreshStateByShard",
    58  		12: "RunHealthCheck",
    59  		13: "IgnoreHealthCheck",
    60  		14: "IgnoreHealthError",
    61  		15: "ExecuteHook",
    62  		16: "ExecuteFetchAsApp",
    63  		17: "ExecuteFetchAsDba",
    64  		18: "VReplicationExec",
    65  		19: "Backup",
    66  		20: "RestoreFromBackup",
    67  		21: "ReparentTablet",
    68  		22: "CreateShard", // Shards
    69  		23: "GetShard",
    70  		24: "ValidateShard",
    71  		25: "ShardReplicationPositions",
    72  		26: "ListShardTablets",
    73  		27: "SetShardIsPrimaryServing",
    74  		28: "SetShardTabletControl",
    75  		29: "UpdateSrvKeyspacePartition",
    76  		30: "SourceShardDelete",
    77  		31: "SourceShardAdd",
    78  		32: "ShardReplicationFix",
    79  		33: "WaitForFilteredReplication",
    80  		34: "RemoveShardCell",
    81  		35: "DeleteShard",
    82  		36: "ListBackups",
    83  		37: "BackupShard",
    84  		38: "RemoveBackup",
    85  		39: "InitShardPrimary",
    86  		40: "PlannedReparentShard",
    87  		41: "EmergencyReparentShard",
    88  		42: "TabletExternallyReparented",
    89  		43: "CreateKeyspace", // Keyspaces
    90  		44: "DeleteKeyspace",
    91  		45: "RemoveKeyspaceCell",
    92  		46: "GetKeyspace",
    93  		47: "GetKeyspaces",
    94  		50: "RebuildKeyspaceGraph",
    95  		51: "ValidateKeyspace",
    96  		52: "Reshard",
    97  		53: "MoveTables",
    98  		55: "CreateLookupVindex",
    99  		56: "ExternalizeVindex",
   100  		57: "Materialize",
   101  		60: "VDiff",
   102  		63: "SwitchTraffic",
   103  		67: "FindAllShardsInKeyspace",
   104  	}
   105  	return m[index]
   106  
   107  }
   108  
   109  /*
   110  In this fuzzer we split the input into 3 chunks:
   111  1: the first byte - Is converted to an int, and
   112  
   113  	that int determines the number of command-line
   114  	calls the fuzzer will make.
   115  
   116  2: The next n bytes where n is equal to the int from
   117  
   118  	the first byte. These n bytes are converted to
   119  	a corresponding command and represent which
   120  	commands will be called.
   121  
   122  3: The rest of the data array should have a length
   123  
   124  	that is divisible by the number of calls.
   125  	This part is split up into equally large chunks,
   126  	and each chunk is used as parameters for the
   127  	corresponding command.
   128  */
   129  func Fuzz(data []byte) int {
   130  
   131  	//  Basic checks
   132  	if len(data) == 0 {
   133  		return -1
   134  	}
   135  	numberOfCalls := int(data[0])
   136  	if numberOfCalls < 3 || numberOfCalls > 10 {
   137  		return -1
   138  	}
   139  	if len(data) < numberOfCalls+numberOfCalls+1 {
   140  		return -1
   141  	}
   142  
   143  	// Define part 2 and 3 of the data array
   144  	commandPart := data[1 : numberOfCalls+1]
   145  	restOfArray := data[numberOfCalls+1:]
   146  
   147  	// Just a small check. It is necessary
   148  	if len(commandPart) != numberOfCalls {
   149  		return -1
   150  	}
   151  
   152  	// Check if restOfArray is divisible by numberOfCalls
   153  	if !IsDivisibleBy(len(restOfArray), numberOfCalls) {
   154  		return -1
   155  	}
   156  
   157  	// At this point we have a data array that can
   158  	// be divided properly. We can now proceed to
   159  	// passing it to Vitess
   160  	ctx := context.Background()
   161  	topo, err := createTopo(ctx)
   162  	if err != nil {
   163  		return -1
   164  	}
   165  	tmc := tmclient.NewTabletManagerClient()
   166  	logger := logutil.NewMemoryLogger()
   167  
   168  	chunkSize := len(restOfArray) / numberOfCalls
   169  	command := 0
   170  	for i := 0; i < len(restOfArray); i = i + chunkSize {
   171  		from := i           // lower
   172  		to := i + chunkSize // upper
   173  
   174  		// Index of command in getCommandType():
   175  		commandIndex := int(commandPart[command]) % 68
   176  		vtCommand := getCommandType(commandIndex)
   177  		commandSlice := []string{vtCommand}
   178  		args := strings.Split(string(restOfArray[from:to]), " ")
   179  
   180  		// Add params to the command
   181  		commandSlice = append(commandSlice, args...)
   182  
   183  		_ = vtctl.RunCommand(ctx, wrangler.New(logger, topo, tmc), commandSlice)
   184  		command++
   185  	}
   186  
   187  	return 1
   188  
   189  }
   190  
   191  func createTopo(ctx context.Context) (*topo.Server, error) {
   192  	ts := memorytopo.NewServer("zone1", "zone2", "zone3")
   193  	return ts, nil
   194  }