diff --git a/clientv3/integration/txn_test.go b/clientv3/integration/txn_test.go index b5cdea7e64f6..1634a65da1b9 100644 --- a/clientv3/integration/txn_test.go +++ b/clientv3/integration/txn_test.go @@ -41,7 +41,7 @@ func TestTxnError(t *testing.T) { t.Fatalf("expected %v, got %v", rpctypes.ErrDuplicateKey, err) } - ops := make([]clientv3.Op, v3rpc.MaxOpsPerTxn+10) + ops := make([]clientv3.Op, v3rpc.MaxTxnOps+10) for i := range ops { ops[i] = clientv3.OpPut(fmt.Sprintf("foo%d", i), "") } diff --git a/embed/config.go b/embed/config.go index e3926f66cb4c..0d88a9688d48 100644 --- a/embed/config.go +++ b/embed/config.go @@ -40,6 +40,7 @@ const ( DefaultName = "default" DefaultMaxSnapshots = 5 DefaultMaxWALs = 5 + DefaultMaxTxnOps = uint(128) DefaultListenPeerURLs = "http://localhost:2380" DefaultListenClientURLs = "http://localhost:2379" @@ -85,6 +86,7 @@ type Config struct { TickMs uint `json:"heartbeat-interval"` ElectionMs uint `json:"election-timeout"` QuotaBackendBytes int64 `json:"quota-backend-bytes"` + MaxTxnOps uint `json:"max-txn-ops"` // clustering @@ -172,6 +174,7 @@ func NewConfig() *Config { MaxWalFiles: DefaultMaxWALs, Name: DefaultName, SnapCount: etcdserver.DefaultSnapCount, + MaxTxnOps: DefaultMaxTxnOps, TickMs: 100, ElectionMs: 1000, LPUrls: []url.URL{*lpurl}, diff --git a/embed/etcd.go b/embed/etcd.go index f77dee0e0656..18b2c905d7e6 100644 --- a/embed/etcd.go +++ b/embed/etcd.go @@ -139,6 +139,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { ElectionTicks: cfg.ElectionTicks(), AutoCompactionRetention: cfg.AutoCompactionRetention, QuotaBackendBytes: cfg.QuotaBackendBytes, + MaxTxnOps: cfg.MaxTxnOps, StrictReconfigCheck: cfg.StrictReconfigCheck, ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth, AuthToken: cfg.AuthToken, diff --git a/etcdmain/config.go b/etcdmain/config.go index b8732200ae60..88bae686c928 100644 --- a/etcdmain/config.go +++ b/etcdmain/config.go @@ -138,6 +138,7 @@ func newConfig() *config { fs.UintVar(&cfg.TickMs, "heartbeat-interval", cfg.TickMs, "Time (in milliseconds) of a heartbeat interval.") fs.UintVar(&cfg.ElectionMs, "election-timeout", cfg.ElectionMs, "Time (in milliseconds) for an election to timeout.") fs.Int64Var(&cfg.QuotaBackendBytes, "quota-backend-bytes", cfg.QuotaBackendBytes, "Raise alarms when backend size exceeds the given quota. 0 means use the default quota.") + fs.UintVar(&cfg.MaxTxnOps, "max-txn-ops", cfg.MaxTxnOps, "Maximum operations per txn that etcd server allows; defaults to 128.") // clustering fs.Var(flags.NewURLsValue(embed.DefaultInitialAdvertisePeerURLs), "initial-advertise-peer-urls", "List of this member's peer URLs to advertise to the rest of the cluster.") diff --git a/etcdmain/help.go b/etcdmain/help.go index cd9282a3192d..02e6707ddade 100644 --- a/etcdmain/help.go +++ b/etcdmain/help.go @@ -66,6 +66,8 @@ member flags: comma-separated whitelist of origins for CORS (cross-origin resource sharing). --quota-backend-bytes '0' raise alarms when backend size exceeds the given quota (0 defaults to low space quota). + --max-txn-ops '128' + maximum operations per txn that etcd server allows; defaults to 128. clustering flags: diff --git a/etcdserver/api/v3rpc/key.go b/etcdserver/api/v3rpc/key.go index 8acae5983c06..68232e5dfeab 100644 --- a/etcdserver/api/v3rpc/key.go +++ b/etcdserver/api/v3rpc/key.go @@ -27,19 +27,20 @@ import ( var ( plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "etcdserver/api/v3rpc") - - // Max operations per txn list. For example, Txn.Success can have at most 128 operations, - // and Txn.Failure can have at most 128 operations. - MaxOpsPerTxn = 128 ) type kvServer struct { hdr header kv etcdserver.RaftKV + // maxTxnOps is the max operations per txn. + // e.g suppose maxOpsPerTxn = 128. + // Txn.Success can have at most 128 operations, + // and Txn.Failure can have at most 128 operations. + maxTxnOps uint } func NewKVServer(s *etcdserver.EtcdServer) pb.KVServer { - return &kvServer{hdr: newHeader(s), kv: s} + return &kvServer{hdr: newHeader(s), kv: s, maxTxnOps: s.Cfg.MaxTxnOps} } func (s *kvServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) { @@ -94,7 +95,7 @@ func (s *kvServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (* } func (s *kvServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) { - if err := checkTxnRequest(r); err != nil { + if err := checkTxnRequest(r, int(s.maxTxnOps)); err != nil { return nil, err } @@ -150,8 +151,9 @@ func checkDeleteRequest(r *pb.DeleteRangeRequest) error { return nil } -func checkTxnRequest(r *pb.TxnRequest) error { - if len(r.Compare) > MaxOpsPerTxn || len(r.Success) > MaxOpsPerTxn || len(r.Failure) > MaxOpsPerTxn { +func checkTxnRequest(r *pb.TxnRequest, maxTxnOps int) error { + plog.Infof("maxTxnOps %v", maxTxnOps) + if len(r.Compare) > maxTxnOps || len(r.Success) > maxTxnOps || len(r.Failure) > maxTxnOps { return rpctypes.ErrGRPCTooManyOps } diff --git a/etcdserver/config.go b/etcdserver/config.go index 9c258934a1c0..d0c63c006946 100644 --- a/etcdserver/config.go +++ b/etcdserver/config.go @@ -54,6 +54,7 @@ type ServerConfig struct { AutoCompactionRetention int QuotaBackendBytes int64 + MaxTxnOps uint StrictReconfigCheck bool diff --git a/integration/v3_grpc_test.go b/integration/v3_grpc_test.go index ac6d66467e4e..b5c88997d9e4 100644 --- a/integration/v3_grpc_test.go +++ b/integration/v3_grpc_test.go @@ -201,7 +201,7 @@ func TestV3TxnTooManyOps(t *testing.T) { for i, tt := range tests { txn := &pb.TxnRequest{} - for j := 0; j < v3rpc.MaxOpsPerTxn+1; j++ { + for j := 0; j < v3rpc.MaxTxnOps+1; j++ { tt(txn) }