vendor: update all dependencies

This commit is contained in:
Nick Craig-Wood
2018-06-17 17:59:12 +01:00
parent 3f0789e2db
commit 08021c4636
2474 changed files with 435818 additions and 282709 deletions
+1 -1
View File
@@ -867,7 +867,7 @@ func (iac *InstanceAdminClient) Clusters(ctx context.Context, instanceId string)
// GetCluster fetches a cluster in an instance
func (iac *InstanceAdminClient) GetCluster(ctx context.Context, instanceID, clusterID string) (*ClusterInfo, error) {
ctx = mergeOutgoingMetadata(ctx, iac.md)
req := &btapb.GetClusterRequest{Name: "projects/" + iac.project + "/instances/" + instanceID + "/clusters" + clusterID}
req := &btapb.GetClusterRequest{Name: "projects/" + iac.project + "/instances/" + instanceID + "/clusters/" + clusterID}
c, err := iac.iClient.GetCluster(ctx, req)
if err != nil {
return nil, err
+5 -6
View File
@@ -15,18 +15,17 @@
package bigtable
import (
"fmt"
"math"
"sort"
"strings"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
btapb "google.golang.org/genproto/googleapis/bigtable/admin/v2"
"strings"
)
func TestAdminIntegration(t *testing.T) {
@@ -102,7 +101,7 @@ func TestAdminIntegration(t *testing.T) {
t.Errorf("adminClient.Tables returned %#v, want %#v", got, want)
}
adminClient.WaitForReplication(ctx, "mytable")
must(adminClient.WaitForReplication(ctx, "mytable"))
if err := adminClient.DeleteTable(ctx, "myothertable"); err != nil {
t.Fatalf("Deleting table: %v", err)
@@ -172,13 +171,13 @@ func TestAdminIntegration(t *testing.T) {
}
var gotRowCount int
tbl.ReadRows(ctx, RowRange{}, func(row Row) bool {
must(tbl.ReadRows(ctx, RowRange{}, func(row Row) bool {
gotRowCount += 1
if !strings.HasPrefix(row.Key(), "b") {
t.Errorf("Invalid row after dropping range: %v", row)
}
return true
})
}))
if gotRowCount != 5 {
t.Errorf("Invalid row count after dropping range: got %v, want %v", gotRowCount, 5)
}
+3
View File
@@ -192,6 +192,9 @@ func (t *Table) ReadRows(ctx context.Context, arg RowSet, f func(Row) bool, opts
tracePrintf(ctx, attrMap, "Retry details in ReadRows")
return err
}
attrMap["time_secs"] = time.Since(startTime).Seconds()
attrMap["rowCount"] = len(res.Chunks)
tracePrintf(ctx, attrMap, "Details in ReadRows")
for _, cc := range res.Chunks {
row, err := cr.Process(cc)
+29 -26
View File
@@ -317,7 +317,8 @@ func (s *server) ReadRows(req *btpb.ReadRowsRequest, stream btpb.Bigtable_ReadRo
return true
}
if req.Rows != nil {
if req.Rows != nil &&
len(req.Rows.RowKeys)+len(req.Rows.RowRanges) > 0 {
// Add the explicitly given keys
for _, key := range req.Rows.RowKeys {
k := string(key)
@@ -698,8 +699,7 @@ func (s *server) MutateRows(req *btpb.MutateRowsRequest, stream btpb.Bigtable_Mu
}
r.mu.Unlock()
}
stream.Send(res)
return nil
return stream.Send(res)
}
func (s *server) CheckAndMutateRow(ctx context.Context, req *btpb.CheckAndMutateRowRequest) (*btpb.CheckAndMutateRowResponse, error) {
@@ -861,12 +861,13 @@ func (s *server) ReadModifyWriteRow(ctx context.Context, req *btpb.ReadModifyWri
if !ok {
return nil, status.Errorf(codes.NotFound, "table %q not found", req.TableName)
}
updates := make(map[string]cell) // copy of updated cells; keyed by full column name
fs := tbl.columnFamilies()
rowKey := string(req.RowKey)
r := tbl.mutableRow(rowKey)
resultRow := newRow(rowKey) // copy of updated cells
// This must be done before the row lock, acquired below, is released.
r.mu.Lock()
defer r.mu.Unlock()
@@ -914,35 +915,37 @@ func (s *server) ReadModifyWriteRow(ctx context.Context, req *btpb.ReadModifyWri
binary.BigEndian.PutUint64(val[:], uint64(v))
newCell = cell{ts: ts, value: val[:]}
}
key := strings.Join([]string{fam, col}, ":")
updates[key] = newCell
// Store the new cell
f.cells[col] = appendOrReplaceCell(f.cellsByColumn(col), newCell)
// Store a copy for the result row
resultFamily := resultRow.getOrCreateFamily(fam, fs[fam].order)
resultFamily.cellsByColumn(col) // create the column
resultFamily.cells[col] = []cell{newCell} // overwrite the cells
}
// Build the response using the result row
res := &btpb.Row{
Key: req.RowKey,
Key: req.RowKey,
Families: make([]*btpb.Family, len(resultRow.families)),
}
for col, cell := range updates {
i := strings.Index(col, ":")
fam, qual := col[:i], col[i+1:]
var f *btpb.Family
for _, ff := range res.Families {
if ff.Name == fam {
f = ff
break
for i, family := range resultRow.sortedFamilies() {
res.Families[i] = &btpb.Family{
Name: family.name,
Columns: make([]*btpb.Column, len(family.colNames)),
}
for j, colName := range family.colNames {
res.Families[i].Columns[j] = &btpb.Column{
Qualifier: []byte(colName),
Cells: []*btpb.Cell{{
TimestampMicros: family.cells[colName][0].ts,
Value: family.cells[colName][0].value,
}},
}
}
if f == nil {
f = &btpb.Family{Name: fam}
res.Families = append(res.Families, f)
}
f.Columns = append(f.Columns, &btpb.Column{
Qualifier: []byte(qual),
Cells: []*btpb.Cell{{
TimestampMicros: cell.ts,
Value: cell.value,
}},
})
}
return &btpb.ReadModifyWriteRowResponse{Row: res}, nil
}
+89 -2
View File
@@ -23,6 +23,8 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/net/context"
btapb "google.golang.org/genproto/googleapis/bigtable/admin/v2"
btpb "google.golang.org/genproto/googleapis/bigtable/v2"
@@ -99,7 +101,9 @@ func TestConcurrentMutationsReadModifyAndGC(t *testing.T) {
RowKey: []byte(fmt.Sprint(rand.Intn(100))),
Mutations: ms(),
}
s.MutateRow(ctx, req)
if _, err := s.MutateRow(ctx, req); err != nil {
panic(err) // can't use t.Fatal in goroutine
}
}
}()
wg.Add(1)
@@ -548,7 +552,9 @@ func TestReadRowsOrder(t *testing.T) {
}
}
for i := count; i > 0; i-- {
s.ReadModifyWriteRow(ctx, rmw(i))
if _, err := s.ReadModifyWriteRow(ctx, rmw(i)); err != nil {
t.Fatal(err)
}
}
req = &btpb.ReadRowsRequest{
TableName: tblInfo.Name,
@@ -621,6 +627,87 @@ func TestCheckAndMutateRowWithoutPredicate(t *testing.T) {
}
}
func TestServer_ReadModifyWriteRow(t *testing.T) {
s := &server{
tables: make(map[string]*table),
}
ctx := context.Background()
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}},
},
}
tbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
if err != nil {
t.Fatalf("Creating table: %v", err)
}
req := &btpb.ReadModifyWriteRowRequest{
TableName: tbl.Name,
RowKey: []byte("row-key"),
Rules: []*btpb.ReadModifyWriteRule{
{
FamilyName: "cf",
ColumnQualifier: []byte("q1"),
Rule: &btpb.ReadModifyWriteRule_AppendValue{
AppendValue: []byte("a"),
},
},
// multiple ops for same cell
{
FamilyName: "cf",
ColumnQualifier: []byte("q1"),
Rule: &btpb.ReadModifyWriteRule_AppendValue{
AppendValue: []byte("b"),
},
},
// different cell whose qualifier should sort before the prior rules
{
FamilyName: "cf",
ColumnQualifier: []byte("q0"),
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{
IncrementAmount: 1,
},
},
},
}
got, err := s.ReadModifyWriteRow(ctx, req)
if err != nil {
t.Fatalf("ReadModifyWriteRow error: %v", err)
}
want := &btpb.ReadModifyWriteRowResponse{
Row: &btpb.Row{
Key: []byte("row-key"),
Families: []*btpb.Family{{
Name: "cf",
Columns: []*btpb.Column{
{
Qualifier: []byte("q0"),
Cells: []*btpb.Cell{{
Value: []byte{0, 0, 0, 0, 0, 0, 0, 1},
}},
},
{
Qualifier: []byte("q1"),
Cells: []*btpb.Cell{{
Value: []byte("ab"),
}},
},
},
}},
},
}
diff := cmp.Diff(got, want, cmpopts.IgnoreFields(btpb.Cell{}, "TimestampMicros"))
if diff != "" {
t.Errorf("unexpected response: %s", diff)
}
}
// helper function to populate table data
func populateTable(ctx context.Context, s *server) (*btapb.Table, error) {
newTbl := btapb.Table{
+43 -12
View File
@@ -332,7 +332,8 @@ var commands = []struct {
Name: "lookup",
Desc: "Read from a single row",
do: doLookup,
Usage: "cbt lookup <table> <row> [app-profile=<app profile id>]\n" +
Usage: "cbt lookup <table> <row> [cells-per-column=<n>] [app-profile=<app profile id>]\n" +
" cells-per-column=<n> Read only this many cells per column\n" +
" app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n",
Required: cbtconfig.ProjectAndInstanceRequired,
},
@@ -356,12 +357,13 @@ var commands = []struct {
Desc: "Read rows",
do: doRead,
Usage: "cbt read <table> [start=<row>] [end=<row>] [prefix=<prefix>]" +
" [regex=<regex>] [count=<n>] [app-profile=<app profile id>]\n" +
" [regex=<regex>] [count=<n>] [cells-per-column=<n>] [app-profile=<app profile id>]\n" +
" start=<row> Start reading at this row\n" +
" end=<row> Stop reading before this row\n" +
" prefix=<prefix> Read rows with this prefix\n" +
" regex=<regex> Read rows with keys matching this regex\n" +
" count=<n> Read only this many rows\n" +
" cells-per-column=<n> Read only this many cells per column\n" +
" app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n",
Required: cbtconfig.ProjectAndInstanceRequired,
},
@@ -850,19 +852,34 @@ func doListClusters(ctx context.Context, args ...string) {
func doLookup(ctx context.Context, args ...string) {
if len(args) < 2 {
log.Fatalf("usage: cbt lookup <table> <row> [app-profile=<app profile id>]")
log.Fatalf("usage: cbt lookup <table> <row> [cells-per-column=<n>] [app-profile=<app profile id>]")
}
var appProfile string
if len(args) > 2 {
i := strings.Index(args[2], "=")
parsed := make(map[string]string)
for _, arg := range args[2:] {
i := strings.Index(arg, "=")
if i < 0 {
log.Fatalf("Bad arg %q", args[2])
log.Fatalf("Bad arg %q", arg)
}
appProfile = strings.Split(args[2], "=")[1]
key, val := arg[:i], arg[i+1:]
switch key {
default:
log.Fatalf("Unknown arg key %q", key)
case "cells-per-column", "app-profile":
parsed[key] = val
}
}
var opts []bigtable.ReadOption
if cellsPerColumn := parsed["cells-per-column"]; cellsPerColumn != "" {
n, err := strconv.Atoi(cellsPerColumn)
if err != nil {
log.Fatalf("Bad number of cells per column %q: %v", cellsPerColumn, err)
}
opts = append(opts, bigtable.RowFilter(bigtable.LatestNFilter(n)))
}
table, row := args[0], args[1]
tbl := getClient(bigtable.ClientConfig{AppProfile: appProfile}).Open(table)
r, err := tbl.ReadRow(ctx, row)
tbl := getClient(bigtable.ClientConfig{AppProfile: parsed["app-profile"]}).Open(table)
r, err := tbl.ReadRow(ctx, row, opts...)
if err != nil {
log.Fatalf("Reading row: %v", err)
}
@@ -995,7 +1012,7 @@ func doRead(ctx context.Context, args ...string) {
case "limit":
// Be nicer; we used to support this, but renamed it to "end".
log.Fatalf("Unknown arg key %q; did you mean %q?", key, "end")
case "start", "end", "prefix", "count", "regex", "app-profile":
case "start", "end", "prefix", "count", "cells-per-column", "regex", "app-profile":
parsed[key] = val
}
}
@@ -1021,8 +1038,22 @@ func doRead(ctx context.Context, args ...string) {
}
opts = append(opts, bigtable.LimitRows(n))
}
var filters []bigtable.Filter
if cellsPerColumn := parsed["cells-per-column"]; cellsPerColumn != "" {
n, err := strconv.Atoi(cellsPerColumn)
if err != nil {
log.Fatalf("Bad number of cells per column %q: %v", cellsPerColumn, err)
}
filters = append(filters, bigtable.LatestNFilter(n))
}
if regex := parsed["regex"]; regex != "" {
opts = append(opts, bigtable.RowFilter(bigtable.RowKeyFilter(regex)))
filters = append(filters, bigtable.RowKeyFilter(regex))
}
if len(filters) > 1 {
opts = append(opts, bigtable.RowFilter(bigtable.ChainFilters(filters...)))
} else if len(filters) == 1 {
opts = append(opts, bigtable.RowFilter(filters[0]))
}
// TODO(dsymonds): Support filters.
+1 -1
View File
@@ -123,7 +123,7 @@ func main() {
go func() {
s := <-c
log.Printf("Caught %v, cleaning scratch table.", s)
adminClient.DeleteTable(context.Background(), *scratchTable)
_ = adminClient.DeleteTable(context.Background(), *scratchTable)
os.Exit(1)
}()
+4 -6
View File
@@ -19,6 +19,10 @@ Package bigtable is an API to Google Cloud Bigtable.
See https://cloud.google.com/bigtable/docs/ for general product documentation.
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
connection pooling and similar aspects of this package.
Setup and Credentials
Use NewClient or NewAdminClient to create a client that can be used to access
@@ -92,12 +96,6 @@ If a read or write operation encounters a transient error it will be retried unt
response, an unretryable error or the context deadline is reached. Non-idempotent writes (where
the timestamp is set to ServerTime) will not be retried. In the case of ReadRows, retried calls
will not re-scan rows that have already been processed.
Authentication
See examples of authorization and authentication at
https://godoc.org/cloud.google.com/go#pkg-examples.
*/
package bigtable // import "cloud.google.com/go/bigtable"
+1 -1
View File
@@ -34,7 +34,7 @@ func TestRandomizedDelays(t *testing.T) {
deadline := time.Now().Add(1 * time.Second)
ctx, _ := context.WithDeadline(context.Background(), deadline)
var invokeTime time.Time
Invoke(ctx, func(childCtx context.Context) error {
_ = Invoke(ctx, func(childCtx context.Context) error {
// Keep failing, make sure we never slept more than max (plus a fudge factor)
if !invokeTime.IsZero() {
if got, want := time.Since(invokeTime), max; got > (want + 20*time.Millisecond) {
+8 -3
View File
@@ -123,10 +123,15 @@ func (agg *Aggregate) String() string {
// WriteCSV writes a csv file to the given Writer,
// with a header row and one row per aggregate.
func WriteCSV(aggs []*Aggregate, iow io.Writer) error {
func WriteCSV(aggs []*Aggregate, iow io.Writer) (err error) {
w := csv.NewWriter(iow)
defer w.Flush()
err := w.Write([]string{"name", "count", "errors", "min", "median", "max", "p75", "p90", "p95", "p99"})
defer func() {
w.Flush()
if err == nil {
err = w.Error()
}
}()
err = w.Write([]string{"name", "count", "errors", "min", "median", "max", "p75", "p90", "p95", "p99"})
if err != nil {
return err
}
+20 -13
View File
@@ -60,10 +60,10 @@ func TestSingleCell(t *testing.T) {
func TestMultipleCells(t *testing.T) {
cr := newChunkReader()
cr.Process(cc("rs", "fm1", "col1", 0, "val1", 0, false))
cr.Process(cc("rs", "fm1", "col1", 1, "val2", 0, false))
cr.Process(cc("rs", "fm1", "col2", 0, "val3", 0, false))
cr.Process(cc("rs", "fm2", "col1", 0, "val4", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "val1", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col1", 1, "val2", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col2", 0, "val3", 0, false))
mustProcess(t, cr, cc("rs", "fm2", "col1", 0, "val4", 0, false))
row, err := cr.Process(cc("rs", "fm2", "col2", 1, "extralongval5", 0, true))
if err != nil {
t.Fatalf("Processing chunk: %v", err)
@@ -95,8 +95,8 @@ func TestMultipleCells(t *testing.T) {
func TestSplitCells(t *testing.T) {
cr := newChunkReader()
cr.Process(cc("rs", "fm1", "col1", 0, "hello ", 11, false))
cr.Process(ccData("world", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "hello ", 11, false))
mustProcess(t, cr, ccData("world", 0, false))
row, err := cr.Process(cc("rs", "fm1", "col2", 0, "val2", 0, true))
if err != nil {
t.Fatalf("Processing chunk: %v", err)
@@ -171,12 +171,11 @@ func TestBlankQualifier(t *testing.T) {
func TestReset(t *testing.T) {
cr := newChunkReader()
cr.Process(cc("rs", "fm1", "col1", 0, "val1", 0, false))
cr.Process(cc("rs", "fm1", "col1", 1, "val2", 0, false))
cr.Process(cc("rs", "fm1", "col2", 0, "val3", 0, false))
cr.Process(ccReset())
row, _ := cr.Process(cc("rs1", "fm1", "col1", 1, "val1", 0, true))
mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "val1", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col1", 1, "val2", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col2", 0, "val3", 0, false))
mustProcess(t, cr, ccReset())
row := mustProcess(t, cr, cc("rs1", "fm1", "col1", 1, "val1", 0, true))
want := []ReadItem{ri("rs1", "fm1", "col1", 1, "val1")}
if !testutil.Equal(row["fm1"], want) {
t.Fatalf("Reset: got: %v\nwant: %v\n", row["fm1"], want)
@@ -189,13 +188,21 @@ func TestReset(t *testing.T) {
func TestNewFamEmptyQualifier(t *testing.T) {
cr := newChunkReader()
cr.Process(cc("rs", "fm1", "col1", 0, "val1", 0, false))
mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "val1", 0, false))
_, err := cr.Process(cc(nilStr, "fm2", nilStr, 0, "val2", 0, true))
if err == nil {
t.Fatalf("Expected error on second chunk with no qualifier set")
}
}
func mustProcess(t *testing.T, cr *chunkReader, cc *btspb.ReadRowsResponse_CellChunk) Row {
row, err := cr.Process(cc)
if err != nil {
t.Fatal(err)
}
return row
}
// The read rows acceptance test reads a json file specifying a number of tests,
// each consisting of one or more cell chunk text protos and one or more resulting
// cells or errors.
+18 -13
View File
@@ -179,28 +179,28 @@ func TestRetryApplyBulk(t *testing.T) {
f = func(ss grpc.ServerStream) error {
var err error
req := new(btpb.MutateRowsRequest)
ss.RecvMsg(req)
must(ss.RecvMsg(req))
switch errCount {
case 0:
// Retryable request failure
err = status.Errorf(codes.Unavailable, "")
case 1:
// Two mutations fail
writeMutateRowsResponse(ss, codes.Unavailable, codes.OK, codes.Aborted)
must(writeMutateRowsResponse(ss, codes.Unavailable, codes.OK, codes.Aborted))
err = nil
case 2:
// Two failures were retried. One will succeed.
if want, got := 2, len(req.Entries); want != got {
t.Errorf("2 bulk retries, got: %d, want %d", got, want)
}
writeMutateRowsResponse(ss, codes.OK, codes.Aborted)
must(writeMutateRowsResponse(ss, codes.OK, codes.Aborted))
err = nil
case 3:
// One failure was retried and will succeed.
if want, got := 1, len(req.Entries); want != got {
t.Errorf("1 bulk retry, got: %d, want %d", got, want)
}
writeMutateRowsResponse(ss, codes.OK)
must(writeMutateRowsResponse(ss, codes.OK))
err = nil
}
errCount++
@@ -218,12 +218,12 @@ func TestRetryApplyBulk(t *testing.T) {
f = func(ss grpc.ServerStream) error {
var err error
req := new(btpb.MutateRowsRequest)
ss.RecvMsg(req)
must(ss.RecvMsg(req))
switch errCount {
case 0:
// Give non-idempotent mutation a retryable error code.
// Nothing should be retried.
writeMutateRowsResponse(ss, codes.FailedPrecondition, codes.Aborted)
must(writeMutateRowsResponse(ss, codes.FailedPrecondition, codes.Aborted))
err = nil
case 1:
t.Errorf("unretryable errors: got one retry, want no retries")
@@ -245,8 +245,7 @@ func TestRetryApplyBulk(t *testing.T) {
// Test individual errors and a deadline exceeded
f = func(ss grpc.ServerStream) error {
writeMutateRowsResponse(ss, codes.FailedPrecondition, codes.OK, codes.Aborted)
return nil
return writeMutateRowsResponse(ss, codes.FailedPrecondition, codes.OK, codes.Aborted)
}
ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
errors, err = tbl.ApplyBulk(ctx, []string{"row1", "row2", "row3"}, []*Mutation{m1, m2, m3})
@@ -320,7 +319,7 @@ func TestRetryReadRows(t *testing.T) {
f = func(ss grpc.ServerStream) error {
var err error
req := new(btpb.ReadRowsRequest)
ss.RecvMsg(req)
must(ss.RecvMsg(req))
switch errCount {
case 0:
// Retryable request failure
@@ -330,7 +329,7 @@ func TestRetryReadRows(t *testing.T) {
if want, got := "a", string(req.Rows.RowRanges[0].GetStartKeyClosed()); want != got {
t.Errorf("first retry, no data received yet: got %q, want %q", got, want)
}
writeReadRowsResponse(ss, "a", "b")
must(writeReadRowsResponse(ss, "a", "b"))
err = status.Errorf(codes.Unavailable, "")
case 2:
// Retryable request failure
@@ -340,7 +339,7 @@ func TestRetryReadRows(t *testing.T) {
err = status.Errorf(codes.Unavailable, "")
case 3:
// Write two more rows
writeReadRowsResponse(ss, "c", "d")
must(writeReadRowsResponse(ss, "c", "d"))
err = nil
}
errCount++
@@ -348,10 +347,10 @@ func TestRetryReadRows(t *testing.T) {
}
var got []string
tbl.ReadRows(ctx, NewRange("a", "z"), func(r Row) bool {
must(tbl.ReadRows(ctx, NewRange("a", "z"), func(r Row) bool {
got = append(got, r.Key())
return true
})
}))
want := []string{"a", "b", "c", "d"}
if !testutil.Equal(got, want) {
t.Errorf("retry range integration: got %v, want %v", got, want)
@@ -370,3 +369,9 @@ func writeReadRowsResponse(ss grpc.ServerStream, rowKeys ...string) error {
}
return ss.SendMsg(&btpb.ReadRowsResponse{Chunks: chunks})
}
func must(err error) {
if err != nil {
panic(err)
}
}