diff --git a/tests/common/alarm_test.go b/tests/common/alarm_test.go new file mode 100644 index 000000000..1d5708663 --- /dev/null +++ b/tests/common/alarm_test.go @@ -0,0 +1,93 @@ +// 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 common + +import ( + "os" + "strings" + "testing" + "time" + + "go.etcd.io/etcd/tests/v3/framework/config" +) + +func TestAlarm(t *testing.T) { + testRunner.BeforeTest(t) + + clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 3, QuotaBackendBytes: int64(13 * os.Getpagesize())}) + defer clus.Close() + + // test small put still works + smallbuf := strings.Repeat("a", 64) + if err := clus.Client().Put("1st_test", smallbuf); err != nil { + t.Fatalf("alarmTest: put kv error (%v)", err) + } + + // write some chunks to fill up the database + buf := strings.Repeat("b", os.Getpagesize()) + for { + if err := clus.Client().Put("2nd_test", buf); err != nil { + if !strings.Contains(err.Error(), "etcdserver: mvcc: database space exceeded") { + t.Fatal(err) + } + break + } + } + + // quota alarm should now be on + _, err := clus.Client().Alarm("list") + if err != nil { + t.Fatalf("alarmTest: Alarm error (%v)", err) + } + + // endpoint should not healthy + if err := clus.Client().Health(); err == nil { + t.Fatalf("endpoint should not healthy") + } + + // check that Put is rejected when alarm is on + if err := clus.Client().Put("3rd_test", smallbuf); err != nil { + if !strings.Contains(err.Error(), "etcdserver: mvcc: database space exceeded") { + t.Fatal(err) + } + } + + // get latest revision to compact + sresp, err := clus.Client().Status() + if err != nil { + t.Fatalf("get endpoint status error: %v", err) + } + + // make some space + _, err = clus.Client().Compact(sresp[0].Header.Revision, config.CompactOption{Physical: true, Timeout: 10 * time.Second}) + if err != nil { + t.Fatalf("alarmTest: Compact error (%v)", err) + } + + if err = clus.Client().Defragment(config.DefragOption{Timeout: 10 * time.Second}); err != nil { + t.Fatalf("alarmTest: defrag error (%v)", err) + } + + // turn off alarm + _, err = clus.Client().Alarm("disarm") + if err != nil { + t.Fatalf("alarmTest: Alarm error (%v)", err) + } + + // put one more key below quota + if err := clus.Client().Put("4th_test", smallbuf); err != nil { + t.Fatal(err) + } +} diff --git a/tests/framework/config/cluster.go b/tests/framework/config/cluster.go index c9deab294..f18c6e5c4 100644 --- a/tests/framework/config/cluster.go +++ b/tests/framework/config/cluster.go @@ -23,7 +23,8 @@ const ( ) type ClusterConfig struct { - ClusterSize int - PeerTLS TLSConfig - ClientTLS TLSConfig + ClusterSize int + PeerTLS TLSConfig + ClientTLS TLSConfig + QuotaBackendBytes int64 } diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index f11bdcead..3f0fb87a7 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -241,7 +241,13 @@ func (ctl *EtcdctlV3) Health() error { lines[i] = "is healthy" } return SpawnWithExpects(args, map[string]string{}, lines...) +} +func (ctl *EtcdctlV3) Alarm(cmd string) (*clientv3.AlarmResponse, error) { + args := ctl.cmdArgs() + args = append(args, "alarm", cmd) + + return nil, SpawnWithExpect(args, "alarm:NOSPACE") } func (ctl *EtcdctlV3) Defragment(o config.DefragOption) error { diff --git a/tests/framework/integration.go b/tests/framework/integration.go index 94c1925b7..e674de785 100644 --- a/tests/framework/integration.go +++ b/tests/framework/integration.go @@ -45,6 +45,7 @@ func (e integrationRunner) NewCluster(t testing.TB, cfg config.ClusterConfig) Cl var integrationCfg integration.ClusterConfig integrationCfg.Size = cfg.ClusterSize integrationCfg.ClientTLS, err = tlsInfo(t, cfg.ClientTLS) + integrationCfg.QuotaBackendBytes = cfg.QuotaBackendBytes if err != nil { t.Fatalf("ClientTLS: %s", err) } @@ -159,6 +160,14 @@ func (c integrationClient) Compact(rev int64, o config.CompactOption) (*clientv3 return c.Client.Compact(ctx, rev, clientOpts...) } +func (c integrationClient) Alarm(cmd string) (*clientv3.AlarmResponse, error) { + ctx := context.Background() + if cmd == "list" { + return c.Client.AlarmList(ctx) + } + return c.Client.AlarmDisarm(ctx, nil) +} + func (c integrationClient) Status() ([]*clientv3.StatusResponse, error) { endpoints := c.Client.Endpoints() var resp []*clientv3.StatusResponse diff --git a/tests/framework/interface.go b/tests/framework/interface.go index db4b3b980..9b1b424f6 100644 --- a/tests/framework/interface.go +++ b/tests/framework/interface.go @@ -37,9 +37,9 @@ type Client interface { Get(key string, opts config.GetOptions) (*clientv3.GetResponse, error) Delete(key string, opts config.DeleteOptions) (*clientv3.DeleteResponse, error) Compact(rev int64, opts config.CompactOption) (*clientv3.CompactResponse, error) - Status() ([]*clientv3.StatusResponse, error) HashKV(rev int64) ([]*clientv3.HashKVResponse, error) Health() error Defragment(opts config.DefragOption) error + Alarm(cmd string) (*clientv3.AlarmResponse, error) }