// 这部分可以手动来进行的
// etcdctl --user root role add r
if_,err=cli.RoleAdd(context.TODO(),"r");err!=nil{log.Fatal(err)}// etcdctl --user root role grant-permission r foo zoo
// 使用 -prefix=true 可以仅指定开头前缀
if_,err=cli.RoleGrantPermission(context.TODO(),"r","foo","zoo",clientv3.PermissionType(clientv3.PermReadWrite),);err!=nil{log.Fatal(err)}// etcdctl --user root user add u --new-user-password 123
if_,err=cli.UserAdd(context.TODO(),"u","123");err!=nil{log.Fatal(err)}// etcdctl --user root user grant-role u r
if_,err=cli.UserGrantRole(context.TODO(),"u","r");err!=nil{log.Fatal(err)}// etcdctl auth enable
if_,err=cli.AuthEnable(context.TODO());err!=nil{log.Fatal(err)}// 这里使用 root 角色的用户来登录
rootCli,err:=clientv3.New(clientv3.Config{Endpoints:exampleEndpoints(),DialTimeout:dialTimeout,Username:"root",Password:"123",})iferr!=nil{log.Fatal(err)}deferrootCli.Close()// root 用户可以获取别的 用户或者角色的数据 etcdctl --user root role get r
resp,err:=rootCli.RoleGet(context.TODO(),"r")iferr!=nil{log.Fatal(err)}// 可以获得 角色权限的信息
fmt.Printf("user u permission: key %q, range end %q\n",resp.Perm[0].Key,resp.Perm[0].RangeEnd)// 这里关闭身份校验 etcdctl auth disable
if_,err=rootCli.AuthDisable(context.TODO());err!=nil{log.Fatal(err)}
// Txn 这个简单的事务接口,还是基于客户端连接来的
kvc:=clientv3.NewKV(cli)_,err=kvc.Put(context.TODO(),"key","xyz")iferr!=nil{log.Fatal(err)}ctx,cancel:=context.WithTimeout(context.Background(),requestTimeout)// if 条件成立 会执行 then 分支的修改,否则会执行else 分支的操作
_,err=kvc.Txn(ctx).// txn value comparisons are lexical
If(clientv3.Compare(clientv3.Value("key"),">","abc")).// the "Then" runs, since "xyz" > "abc"
Then(clientv3.OpPut("key","XYZ")).// the "Else" does not run
Else(clientv3.OpPut("key","ABC")).Commit()//
exchange:=func(stmconcurrency.STM){from,to:=rand.Intn(totalAccounts),rand.Intn(totalAccounts)iffrom==to{// nothing to do
return}// read values
fromK,toK:=fmt.Sprintf("accts/%d",from),fmt.Sprintf("accts/%d",to)fromV,toV:=stm.Get(fromK),stm.Get(toK)fromInt,toInt:=0,0fmt.Sscanf(fromV,"%d",&fromInt)fmt.Sscanf(toV,"%d",&toInt)// transfer amount
xfer:=fromInt/2fromInt,toInt=fromInt-xfer,toInt+xfer// write back
stm.Put(fromK,fmt.Sprintf("%d",fromInt))stm.Put(toK,fmt.Sprintf("%d",toInt))return}// concurrently exchange values between accounts
varwgsync.WaitGroupwg.Add(10)fori:=0;i<10;i++{gofunc(){deferwg.Done()if_,serr:=concurrency.NewSTM(cli,func(stmconcurrency.STM)error{exchange(stm)returnnil});serr!=nil{log.Fatal(serr)}}()}wg.Wait()
租约有点像 go 里面的上下文,租约过期时会撤销掉这期间的更改;同时在func (Lease).Revoke(ctx Context, id LeaseID) 释放租约的时候,之前修改会被视作失效了;func (Lease).KeepAliveOnce(ctx Context, id LeaseID) 可以手动续约,避免租约超期被取消了;
lease,err:=cli.Grant(context.Background(),100)iferr!=nil{t.Fatal(err)}// 每个会话会有一个唯一的ID 和TTL 存活时间
s,err:=concurrency.NewSession(cli,concurrency.WithLease(lease.ID))iferr!=nil{t.Fatal(err)}defers.Close()assert.Equal(t,s.Lease(),lease.ID)gos.Orphan()select{case<-s.Done():case<-time.After(time.Millisecond*100):t.Fatal("session did not get orphaned as expected")}
s,err:=concurrency.NewSession(cli,concurrency.WithTTL(setTTL))iferr!=nil{t.Fatal(err)}defers.Close()leaseID:=s.Lease()// TTL retrieved should be less than the set TTL, but not equal to default:60 or exprired:-1
resp,err:=cli.Lease.TimeToLive(context.Background(),leaseID)iferr!=nil{t.Log(err)}ifresp.TTL==-1{t.Errorf("client lease should not be expired: %d",resp.TTL)}ifresp.TTL==60{t.Errorf("default TTL value is used in the session, instead of set TTL: %d",setTTL)}ifresp.TTL>=int64(setTTL)||resp.TTL<int64(setTTL)-20{t.Errorf("Session TTL from lease should be less, but close to set TTL %d, have: %d",setTTL,resp.TTL)}
lease,err:=cli.Grant(context.Background(),100)iferr!=nil{t.Fatal(err)}s,err:=concurrency.NewSession(cli,concurrency.WithLease(lease.ID))iferr!=nil{t.Fatal(err)}defers.Close()assert.Equal(t,s.Lease(),lease.ID)// 主要是通过 会话的上下文的Done 来控制会话内操作的退出
childCtx,cancel:=context.WithCancel(s.Ctx())defercancel()gos.Orphan()select{case<-childCtx.Done():case<-time.After(time.Millisecond*100):t.Fatal("child context of session context is not canceled")}