You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1134 lines
37 KiB

  1. /*
  2. Copyright 2015 Google LLC
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package bigtable
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "math"
  19. "regexp"
  20. "strings"
  21. "time"
  22. "cloud.google.com/go/bigtable/internal/gax"
  23. btopt "cloud.google.com/go/bigtable/internal/option"
  24. "cloud.google.com/go/iam"
  25. "cloud.google.com/go/internal/optional"
  26. "cloud.google.com/go/longrunning"
  27. lroauto "cloud.google.com/go/longrunning/autogen"
  28. "github.com/golang/protobuf/ptypes"
  29. durpb "github.com/golang/protobuf/ptypes/duration"
  30. "google.golang.org/api/cloudresourcemanager/v1"
  31. "google.golang.org/api/iterator"
  32. "google.golang.org/api/option"
  33. gtransport "google.golang.org/api/transport/grpc"
  34. btapb "google.golang.org/genproto/googleapis/bigtable/admin/v2"
  35. "google.golang.org/genproto/protobuf/field_mask"
  36. "google.golang.org/grpc"
  37. "google.golang.org/grpc/codes"
  38. "google.golang.org/grpc/metadata"
  39. "google.golang.org/grpc/status"
  40. )
  41. const adminAddr = "bigtableadmin.googleapis.com:443"
  42. // AdminClient is a client type for performing admin operations within a specific instance.
  43. type AdminClient struct {
  44. conn *grpc.ClientConn
  45. tClient btapb.BigtableTableAdminClient
  46. lroClient *lroauto.OperationsClient
  47. project, instance string
  48. // Metadata to be sent with each request.
  49. md metadata.MD
  50. }
  51. // NewAdminClient creates a new AdminClient for a given project and instance.
  52. func NewAdminClient(ctx context.Context, project, instance string, opts ...option.ClientOption) (*AdminClient, error) {
  53. o, err := btopt.DefaultClientOptions(adminAddr, AdminScope, clientUserAgent)
  54. if err != nil {
  55. return nil, err
  56. }
  57. // Need to add scopes for long running operations (for create table & snapshots)
  58. o = append(o, option.WithScopes(cloudresourcemanager.CloudPlatformScope))
  59. o = append(o, opts...)
  60. conn, err := gtransport.Dial(ctx, o...)
  61. if err != nil {
  62. return nil, fmt.Errorf("dialing: %v", err)
  63. }
  64. lroClient, err := lroauto.NewOperationsClient(ctx, option.WithGRPCConn(conn))
  65. if err != nil {
  66. // This error "should not happen", since we are just reusing old connection
  67. // and never actually need to dial.
  68. // If this does happen, we could leak conn. However, we cannot close conn:
  69. // If the user invoked the function with option.WithGRPCConn,
  70. // we would close a connection that's still in use.
  71. // TODO(pongad): investigate error conditions.
  72. return nil, err
  73. }
  74. return &AdminClient{
  75. conn: conn,
  76. tClient: btapb.NewBigtableTableAdminClient(conn),
  77. lroClient: lroClient,
  78. project: project,
  79. instance: instance,
  80. md: metadata.Pairs(resourcePrefixHeader, fmt.Sprintf("projects/%s/instances/%s", project, instance)),
  81. }, nil
  82. }
  83. // Close closes the AdminClient.
  84. func (ac *AdminClient) Close() error {
  85. return ac.conn.Close()
  86. }
  87. func (ac *AdminClient) instancePrefix() string {
  88. return fmt.Sprintf("projects/%s/instances/%s", ac.project, ac.instance)
  89. }
  90. // Tables returns a list of the tables in the instance.
  91. func (ac *AdminClient) Tables(ctx context.Context) ([]string, error) {
  92. ctx = mergeOutgoingMetadata(ctx, ac.md)
  93. prefix := ac.instancePrefix()
  94. req := &btapb.ListTablesRequest{
  95. Parent: prefix,
  96. }
  97. var res *btapb.ListTablesResponse
  98. err := gax.Invoke(ctx, func(ctx context.Context) error {
  99. var err error
  100. res, err = ac.tClient.ListTables(ctx, req)
  101. return err
  102. }, retryOptions...)
  103. if err != nil {
  104. return nil, err
  105. }
  106. names := make([]string, 0, len(res.Tables))
  107. for _, tbl := range res.Tables {
  108. names = append(names, strings.TrimPrefix(tbl.Name, prefix+"/tables/"))
  109. }
  110. return names, nil
  111. }
  112. // TableConf contains all of the information necessary to create a table with column families.
  113. type TableConf struct {
  114. TableID string
  115. SplitKeys []string
  116. // Families is a map from family name to GCPolicy
  117. Families map[string]GCPolicy
  118. }
  119. // CreateTable creates a new table in the instance.
  120. // This method may return before the table's creation is complete.
  121. func (ac *AdminClient) CreateTable(ctx context.Context, table string) error {
  122. return ac.CreateTableFromConf(ctx, &TableConf{TableID: table})
  123. }
  124. // CreatePresplitTable creates a new table in the instance.
  125. // The list of row keys will be used to initially split the table into multiple tablets.
  126. // Given two split keys, "s1" and "s2", three tablets will be created,
  127. // spanning the key ranges: [, s1), [s1, s2), [s2, ).
  128. // This method may return before the table's creation is complete.
  129. func (ac *AdminClient) CreatePresplitTable(ctx context.Context, table string, splitKeys []string) error {
  130. return ac.CreateTableFromConf(ctx, &TableConf{TableID: table, SplitKeys: splitKeys})
  131. }
  132. // CreateTableFromConf creates a new table in the instance from the given configuration.
  133. func (ac *AdminClient) CreateTableFromConf(ctx context.Context, conf *TableConf) error {
  134. ctx = mergeOutgoingMetadata(ctx, ac.md)
  135. var reqSplits []*btapb.CreateTableRequest_Split
  136. for _, split := range conf.SplitKeys {
  137. reqSplits = append(reqSplits, &btapb.CreateTableRequest_Split{Key: []byte(split)})
  138. }
  139. var tbl btapb.Table
  140. if conf.Families != nil {
  141. tbl.ColumnFamilies = make(map[string]*btapb.ColumnFamily)
  142. for fam, policy := range conf.Families {
  143. tbl.ColumnFamilies[fam] = &btapb.ColumnFamily{GcRule: policy.proto()}
  144. }
  145. }
  146. prefix := ac.instancePrefix()
  147. req := &btapb.CreateTableRequest{
  148. Parent: prefix,
  149. TableId: conf.TableID,
  150. Table: &tbl,
  151. InitialSplits: reqSplits,
  152. }
  153. _, err := ac.tClient.CreateTable(ctx, req)
  154. return err
  155. }
  156. // CreateColumnFamily creates a new column family in a table.
  157. func (ac *AdminClient) CreateColumnFamily(ctx context.Context, table, family string) error {
  158. // TODO(dsymonds): Permit specifying gcexpr and any other family settings.
  159. ctx = mergeOutgoingMetadata(ctx, ac.md)
  160. prefix := ac.instancePrefix()
  161. req := &btapb.ModifyColumnFamiliesRequest{
  162. Name: prefix + "/tables/" + table,
  163. Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
  164. Id: family,
  165. Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{Create: &btapb.ColumnFamily{}},
  166. }},
  167. }
  168. _, err := ac.tClient.ModifyColumnFamilies(ctx, req)
  169. return err
  170. }
  171. // DeleteTable deletes a table and all of its data.
  172. func (ac *AdminClient) DeleteTable(ctx context.Context, table string) error {
  173. ctx = mergeOutgoingMetadata(ctx, ac.md)
  174. prefix := ac.instancePrefix()
  175. req := &btapb.DeleteTableRequest{
  176. Name: prefix + "/tables/" + table,
  177. }
  178. _, err := ac.tClient.DeleteTable(ctx, req)
  179. return err
  180. }
  181. // DeleteColumnFamily deletes a column family in a table and all of its data.
  182. func (ac *AdminClient) DeleteColumnFamily(ctx context.Context, table, family string) error {
  183. ctx = mergeOutgoingMetadata(ctx, ac.md)
  184. prefix := ac.instancePrefix()
  185. req := &btapb.ModifyColumnFamiliesRequest{
  186. Name: prefix + "/tables/" + table,
  187. Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
  188. Id: family,
  189. Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Drop{Drop: true},
  190. }},
  191. }
  192. _, err := ac.tClient.ModifyColumnFamilies(ctx, req)
  193. return err
  194. }
  195. // TableInfo represents information about a table.
  196. type TableInfo struct {
  197. // DEPRECATED - This field is deprecated. Please use FamilyInfos instead.
  198. Families []string
  199. FamilyInfos []FamilyInfo
  200. }
  201. // FamilyInfo represents information about a column family.
  202. type FamilyInfo struct {
  203. Name string
  204. GCPolicy string
  205. }
  206. // TableInfo retrieves information about a table.
  207. func (ac *AdminClient) TableInfo(ctx context.Context, table string) (*TableInfo, error) {
  208. ctx = mergeOutgoingMetadata(ctx, ac.md)
  209. prefix := ac.instancePrefix()
  210. req := &btapb.GetTableRequest{
  211. Name: prefix + "/tables/" + table,
  212. }
  213. var res *btapb.Table
  214. err := gax.Invoke(ctx, func(ctx context.Context) error {
  215. var err error
  216. res, err = ac.tClient.GetTable(ctx, req)
  217. return err
  218. }, retryOptions...)
  219. if err != nil {
  220. return nil, err
  221. }
  222. ti := &TableInfo{}
  223. for name, fam := range res.ColumnFamilies {
  224. ti.Families = append(ti.Families, name)
  225. ti.FamilyInfos = append(ti.FamilyInfos, FamilyInfo{Name: name, GCPolicy: GCRuleToString(fam.GcRule)})
  226. }
  227. return ti, nil
  228. }
  229. // SetGCPolicy specifies which cells in a column family should be garbage collected.
  230. // GC executes opportunistically in the background; table reads may return data
  231. // matching the GC policy.
  232. func (ac *AdminClient) SetGCPolicy(ctx context.Context, table, family string, policy GCPolicy) error {
  233. ctx = mergeOutgoingMetadata(ctx, ac.md)
  234. prefix := ac.instancePrefix()
  235. req := &btapb.ModifyColumnFamiliesRequest{
  236. Name: prefix + "/tables/" + table,
  237. Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
  238. Id: family,
  239. Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Update{Update: &btapb.ColumnFamily{GcRule: policy.proto()}},
  240. }},
  241. }
  242. _, err := ac.tClient.ModifyColumnFamilies(ctx, req)
  243. return err
  244. }
  245. // DropRowRange permanently deletes a row range from the specified table.
  246. func (ac *AdminClient) DropRowRange(ctx context.Context, table, rowKeyPrefix string) error {
  247. ctx = mergeOutgoingMetadata(ctx, ac.md)
  248. prefix := ac.instancePrefix()
  249. req := &btapb.DropRowRangeRequest{
  250. Name: prefix + "/tables/" + table,
  251. Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte(rowKeyPrefix)},
  252. }
  253. _, err := ac.tClient.DropRowRange(ctx, req)
  254. return err
  255. }
  256. // CreateTableFromSnapshot creates a table from snapshot.
  257. // The table will be created in the same cluster as the snapshot.
  258. //
  259. // This is a private alpha release of Cloud Bigtable snapshots. This feature
  260. // is not currently available to most Cloud Bigtable customers. This feature
  261. // might be changed in backward-incompatible ways and is not recommended for
  262. // production use. It is not subject to any SLA or deprecation policy.
  263. func (ac *AdminClient) CreateTableFromSnapshot(ctx context.Context, table, cluster, snapshot string) error {
  264. ctx = mergeOutgoingMetadata(ctx, ac.md)
  265. prefix := ac.instancePrefix()
  266. snapshotPath := prefix + "/clusters/" + cluster + "/snapshots/" + snapshot
  267. req := &btapb.CreateTableFromSnapshotRequest{
  268. Parent: prefix,
  269. TableId: table,
  270. SourceSnapshot: snapshotPath,
  271. }
  272. op, err := ac.tClient.CreateTableFromSnapshot(ctx, req)
  273. if err != nil {
  274. return err
  275. }
  276. resp := btapb.Table{}
  277. return longrunning.InternalNewOperation(ac.lroClient, op).Wait(ctx, &resp)
  278. }
  279. // DefaultSnapshotDuration is the default TTL for a snapshot.
  280. const DefaultSnapshotDuration time.Duration = 0
  281. // SnapshotTable creates a new snapshot in the specified cluster from the
  282. // specified source table. Setting the TTL to `DefaultSnapshotDuration` will
  283. // use the server side default for the duration.
  284. //
  285. // This is a private alpha release of Cloud Bigtable snapshots. This feature
  286. // is not currently available to most Cloud Bigtable customers. This feature
  287. // might be changed in backward-incompatible ways and is not recommended for
  288. // production use. It is not subject to any SLA or deprecation policy.
  289. func (ac *AdminClient) SnapshotTable(ctx context.Context, table, cluster, snapshot string, ttl time.Duration) error {
  290. ctx = mergeOutgoingMetadata(ctx, ac.md)
  291. prefix := ac.instancePrefix()
  292. var ttlProto *durpb.Duration
  293. if ttl > 0 {
  294. ttlProto = ptypes.DurationProto(ttl)
  295. }
  296. req := &btapb.SnapshotTableRequest{
  297. Name: prefix + "/tables/" + table,
  298. Cluster: prefix + "/clusters/" + cluster,
  299. SnapshotId: snapshot,
  300. Ttl: ttlProto,
  301. }
  302. op, err := ac.tClient.SnapshotTable(ctx, req)
  303. if err != nil {
  304. return err
  305. }
  306. resp := btapb.Snapshot{}
  307. return longrunning.InternalNewOperation(ac.lroClient, op).Wait(ctx, &resp)
  308. }
  309. // Snapshots returns a SnapshotIterator for iterating over the snapshots in a cluster.
  310. // To list snapshots across all of the clusters in the instance specify "-" as the cluster.
  311. //
  312. // This is a private alpha release of Cloud Bigtable snapshots. This feature is not
  313. // currently available to most Cloud Bigtable customers. This feature might be
  314. // changed in backward-incompatible ways and is not recommended for production use.
  315. // It is not subject to any SLA or deprecation policy.
  316. func (ac *AdminClient) Snapshots(ctx context.Context, cluster string) *SnapshotIterator {
  317. ctx = mergeOutgoingMetadata(ctx, ac.md)
  318. prefix := ac.instancePrefix()
  319. clusterPath := prefix + "/clusters/" + cluster
  320. it := &SnapshotIterator{}
  321. req := &btapb.ListSnapshotsRequest{
  322. Parent: clusterPath,
  323. }
  324. fetch := func(pageSize int, pageToken string) (string, error) {
  325. req.PageToken = pageToken
  326. if pageSize > math.MaxInt32 {
  327. req.PageSize = math.MaxInt32
  328. } else {
  329. req.PageSize = int32(pageSize)
  330. }
  331. var resp *btapb.ListSnapshotsResponse
  332. err := gax.Invoke(ctx, func(ctx context.Context) error {
  333. var err error
  334. resp, err = ac.tClient.ListSnapshots(ctx, req)
  335. return err
  336. }, retryOptions...)
  337. if err != nil {
  338. return "", err
  339. }
  340. for _, s := range resp.Snapshots {
  341. snapshotInfo, err := newSnapshotInfo(s)
  342. if err != nil {
  343. return "", fmt.Errorf("failed to parse snapshot proto %v", err)
  344. }
  345. it.items = append(it.items, snapshotInfo)
  346. }
  347. return resp.NextPageToken, nil
  348. }
  349. bufLen := func() int { return len(it.items) }
  350. takeBuf := func() interface{} { b := it.items; it.items = nil; return b }
  351. it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, bufLen, takeBuf)
  352. return it
  353. }
  354. func newSnapshotInfo(snapshot *btapb.Snapshot) (*SnapshotInfo, error) {
  355. nameParts := strings.Split(snapshot.Name, "/")
  356. name := nameParts[len(nameParts)-1]
  357. tablePathParts := strings.Split(snapshot.SourceTable.Name, "/")
  358. tableID := tablePathParts[len(tablePathParts)-1]
  359. createTime, err := ptypes.Timestamp(snapshot.CreateTime)
  360. if err != nil {
  361. return nil, fmt.Errorf("invalid createTime: %v", err)
  362. }
  363. deleteTime, err := ptypes.Timestamp(snapshot.DeleteTime)
  364. if err != nil {
  365. return nil, fmt.Errorf("invalid deleteTime: %v", err)
  366. }
  367. return &SnapshotInfo{
  368. Name: name,
  369. SourceTable: tableID,
  370. DataSize: snapshot.DataSizeBytes,
  371. CreateTime: createTime,
  372. DeleteTime: deleteTime,
  373. }, nil
  374. }
  375. // SnapshotIterator is an EntryIterator that iterates over log entries.
  376. //
  377. // This is a private alpha release of Cloud Bigtable snapshots. This feature
  378. // is not currently available to most Cloud Bigtable customers. This feature
  379. // might be changed in backward-incompatible ways and is not recommended for
  380. // production use. It is not subject to any SLA or deprecation policy.
  381. type SnapshotIterator struct {
  382. items []*SnapshotInfo
  383. pageInfo *iterator.PageInfo
  384. nextFunc func() error
  385. }
  386. // PageInfo supports pagination. See https://godoc.org/google.golang.org/api/iterator package for details.
  387. func (it *SnapshotIterator) PageInfo() *iterator.PageInfo {
  388. return it.pageInfo
  389. }
  390. // Next returns the next result. Its second return value is iterator.Done
  391. // (https://godoc.org/google.golang.org/api/iterator) if there are no more
  392. // results. Once Next returns Done, all subsequent calls will return Done.
  393. func (it *SnapshotIterator) Next() (*SnapshotInfo, error) {
  394. if err := it.nextFunc(); err != nil {
  395. return nil, err
  396. }
  397. item := it.items[0]
  398. it.items = it.items[1:]
  399. return item, nil
  400. }
  401. // SnapshotInfo contains snapshot metadata.
  402. type SnapshotInfo struct {
  403. Name string
  404. SourceTable string
  405. DataSize int64
  406. CreateTime time.Time
  407. DeleteTime time.Time
  408. }
  409. // SnapshotInfo gets snapshot metadata.
  410. //
  411. // This is a private alpha release of Cloud Bigtable snapshots. This feature
  412. // is not currently available to most Cloud Bigtable customers. This feature
  413. // might be changed in backward-incompatible ways and is not recommended for
  414. // production use. It is not subject to any SLA or deprecation policy.
  415. func (ac *AdminClient) SnapshotInfo(ctx context.Context, cluster, snapshot string) (*SnapshotInfo, error) {
  416. ctx = mergeOutgoingMetadata(ctx, ac.md)
  417. prefix := ac.instancePrefix()
  418. clusterPath := prefix + "/clusters/" + cluster
  419. snapshotPath := clusterPath + "/snapshots/" + snapshot
  420. req := &btapb.GetSnapshotRequest{
  421. Name: snapshotPath,
  422. }
  423. var resp *btapb.Snapshot
  424. err := gax.Invoke(ctx, func(ctx context.Context) error {
  425. var err error
  426. resp, err = ac.tClient.GetSnapshot(ctx, req)
  427. return err
  428. }, retryOptions...)
  429. if err != nil {
  430. return nil, err
  431. }
  432. return newSnapshotInfo(resp)
  433. }
  434. // DeleteSnapshot deletes a snapshot in a cluster.
  435. //
  436. // This is a private alpha release of Cloud Bigtable snapshots. This feature
  437. // is not currently available to most Cloud Bigtable customers. This feature
  438. // might be changed in backward-incompatible ways and is not recommended for
  439. // production use. It is not subject to any SLA or deprecation policy.
  440. func (ac *AdminClient) DeleteSnapshot(ctx context.Context, cluster, snapshot string) error {
  441. ctx = mergeOutgoingMetadata(ctx, ac.md)
  442. prefix := ac.instancePrefix()
  443. clusterPath := prefix + "/clusters/" + cluster
  444. snapshotPath := clusterPath + "/snapshots/" + snapshot
  445. req := &btapb.DeleteSnapshotRequest{
  446. Name: snapshotPath,
  447. }
  448. _, err := ac.tClient.DeleteSnapshot(ctx, req)
  449. return err
  450. }
  451. // getConsistencyToken gets the consistency token for a table.
  452. func (ac *AdminClient) getConsistencyToken(ctx context.Context, tableName string) (string, error) {
  453. req := &btapb.GenerateConsistencyTokenRequest{
  454. Name: tableName,
  455. }
  456. resp, err := ac.tClient.GenerateConsistencyToken(ctx, req)
  457. if err != nil {
  458. return "", err
  459. }
  460. return resp.GetConsistencyToken(), nil
  461. }
  462. // isConsistent checks if a token is consistent for a table.
  463. func (ac *AdminClient) isConsistent(ctx context.Context, tableName, token string) (bool, error) {
  464. req := &btapb.CheckConsistencyRequest{
  465. Name: tableName,
  466. ConsistencyToken: token,
  467. }
  468. var resp *btapb.CheckConsistencyResponse
  469. // Retry calls on retryable errors to avoid losing the token gathered before.
  470. err := gax.Invoke(ctx, func(ctx context.Context) error {
  471. var err error
  472. resp, err = ac.tClient.CheckConsistency(ctx, req)
  473. return err
  474. }, retryOptions...)
  475. if err != nil {
  476. return false, err
  477. }
  478. return resp.GetConsistent(), nil
  479. }
  480. // WaitForReplication waits until all the writes committed before the call started have been propagated to all the clusters in the instance via replication.
  481. func (ac *AdminClient) WaitForReplication(ctx context.Context, table string) error {
  482. // Get the token.
  483. prefix := ac.instancePrefix()
  484. tableName := prefix + "/tables/" + table
  485. token, err := ac.getConsistencyToken(ctx, tableName)
  486. if err != nil {
  487. return err
  488. }
  489. // Periodically check if the token is consistent.
  490. timer := time.NewTicker(time.Second * 10)
  491. defer timer.Stop()
  492. for {
  493. consistent, err := ac.isConsistent(ctx, tableName, token)
  494. if err != nil {
  495. return err
  496. }
  497. if consistent {
  498. return nil
  499. }
  500. // Sleep for a bit or until the ctx is cancelled.
  501. select {
  502. case <-ctx.Done():
  503. return ctx.Err()
  504. case <-timer.C:
  505. }
  506. }
  507. }
  508. const instanceAdminAddr = "bigtableadmin.googleapis.com:443"
  509. // InstanceAdminClient is a client type for performing admin operations on instances.
  510. // These operations can be substantially more dangerous than those provided by AdminClient.
  511. type InstanceAdminClient struct {
  512. conn *grpc.ClientConn
  513. iClient btapb.BigtableInstanceAdminClient
  514. lroClient *lroauto.OperationsClient
  515. project string
  516. // Metadata to be sent with each request.
  517. md metadata.MD
  518. }
  519. // NewInstanceAdminClient creates a new InstanceAdminClient for a given project.
  520. func NewInstanceAdminClient(ctx context.Context, project string, opts ...option.ClientOption) (*InstanceAdminClient, error) {
  521. o, err := btopt.DefaultClientOptions(instanceAdminAddr, InstanceAdminScope, clientUserAgent)
  522. if err != nil {
  523. return nil, err
  524. }
  525. o = append(o, opts...)
  526. conn, err := gtransport.Dial(ctx, o...)
  527. if err != nil {
  528. return nil, fmt.Errorf("dialing: %v", err)
  529. }
  530. lroClient, err := lroauto.NewOperationsClient(ctx, option.WithGRPCConn(conn))
  531. if err != nil {
  532. // This error "should not happen", since we are just reusing old connection
  533. // and never actually need to dial.
  534. // If this does happen, we could leak conn. However, we cannot close conn:
  535. // If the user invoked the function with option.WithGRPCConn,
  536. // we would close a connection that's still in use.
  537. // TODO(pongad): investigate error conditions.
  538. return nil, err
  539. }
  540. return &InstanceAdminClient{
  541. conn: conn,
  542. iClient: btapb.NewBigtableInstanceAdminClient(conn),
  543. lroClient: lroClient,
  544. project: project,
  545. md: metadata.Pairs(resourcePrefixHeader, "projects/"+project),
  546. }, nil
  547. }
  548. // Close closes the InstanceAdminClient.
  549. func (iac *InstanceAdminClient) Close() error {
  550. return iac.conn.Close()
  551. }
  552. // StorageType is the type of storage used for all tables in an instance
  553. type StorageType int
  554. const (
  555. SSD StorageType = iota
  556. HDD
  557. )
  558. func (st StorageType) proto() btapb.StorageType {
  559. if st == HDD {
  560. return btapb.StorageType_HDD
  561. }
  562. return btapb.StorageType_SSD
  563. }
  564. // InstanceType is the type of the instance
  565. type InstanceType int32
  566. const (
  567. PRODUCTION InstanceType = InstanceType(btapb.Instance_PRODUCTION)
  568. DEVELOPMENT = InstanceType(btapb.Instance_DEVELOPMENT)
  569. )
  570. // InstanceInfo represents information about an instance
  571. type InstanceInfo struct {
  572. Name string // name of the instance
  573. DisplayName string // display name for UIs
  574. }
  575. // InstanceConf contains the information necessary to create an Instance
  576. type InstanceConf struct {
  577. InstanceId, DisplayName, ClusterId, Zone string
  578. // NumNodes must not be specified for DEVELOPMENT instance types
  579. NumNodes int32
  580. StorageType StorageType
  581. InstanceType InstanceType
  582. }
  583. // InstanceWithClustersConfig contains the information necessary to create an Instance
  584. type InstanceWithClustersConfig struct {
  585. InstanceID, DisplayName string
  586. Clusters []ClusterConfig
  587. InstanceType InstanceType
  588. }
  589. var instanceNameRegexp = regexp.MustCompile(`^projects/([^/]+)/instances/([a-z][-a-z0-9]*)$`)
  590. // CreateInstance creates a new instance in the project.
  591. // This method will return when the instance has been created or when an error occurs.
  592. func (iac *InstanceAdminClient) CreateInstance(ctx context.Context, conf *InstanceConf) error {
  593. newConfig := InstanceWithClustersConfig{
  594. InstanceID: conf.InstanceId,
  595. DisplayName: conf.DisplayName,
  596. InstanceType: conf.InstanceType,
  597. Clusters: []ClusterConfig{
  598. {
  599. InstanceID: conf.InstanceId,
  600. ClusterID: conf.ClusterId,
  601. Zone: conf.Zone,
  602. NumNodes: conf.NumNodes,
  603. StorageType: conf.StorageType,
  604. },
  605. },
  606. }
  607. return iac.CreateInstanceWithClusters(ctx, &newConfig)
  608. }
  609. // CreateInstanceWithClusters creates a new instance with configured clusters in the project.
  610. // This method will return when the instance has been created or when an error occurs.
  611. func (iac *InstanceAdminClient) CreateInstanceWithClusters(ctx context.Context, conf *InstanceWithClustersConfig) error {
  612. ctx = mergeOutgoingMetadata(ctx, iac.md)
  613. clusters := make(map[string]*btapb.Cluster)
  614. for _, cluster := range conf.Clusters {
  615. clusters[cluster.ClusterID] = cluster.proto(iac.project)
  616. }
  617. req := &btapb.CreateInstanceRequest{
  618. Parent: "projects/" + iac.project,
  619. InstanceId: conf.InstanceID,
  620. Instance: &btapb.Instance{DisplayName: conf.DisplayName, Type: btapb.Instance_Type(conf.InstanceType)},
  621. Clusters: clusters,
  622. }
  623. lro, err := iac.iClient.CreateInstance(ctx, req)
  624. if err != nil {
  625. return err
  626. }
  627. resp := btapb.Instance{}
  628. return longrunning.InternalNewOperation(iac.lroClient, lro).Wait(ctx, &resp)
  629. }
  630. // DeleteInstance deletes an instance from the project.
  631. func (iac *InstanceAdminClient) DeleteInstance(ctx context.Context, instanceID string) error {
  632. ctx = mergeOutgoingMetadata(ctx, iac.md)
  633. req := &btapb.DeleteInstanceRequest{Name: "projects/" + iac.project + "/instances/" + instanceID}
  634. _, err := iac.iClient.DeleteInstance(ctx, req)
  635. return err
  636. }
  637. // Instances returns a list of instances in the project.
  638. func (iac *InstanceAdminClient) Instances(ctx context.Context) ([]*InstanceInfo, error) {
  639. ctx = mergeOutgoingMetadata(ctx, iac.md)
  640. req := &btapb.ListInstancesRequest{
  641. Parent: "projects/" + iac.project,
  642. }
  643. var res *btapb.ListInstancesResponse
  644. err := gax.Invoke(ctx, func(ctx context.Context) error {
  645. var err error
  646. res, err = iac.iClient.ListInstances(ctx, req)
  647. return err
  648. }, retryOptions...)
  649. if err != nil {
  650. return nil, err
  651. }
  652. if len(res.FailedLocations) > 0 {
  653. // We don't have a good way to return a partial result in the face of some zones being unavailable.
  654. // Fail the entire request.
  655. return nil, status.Errorf(codes.Unavailable, "Failed locations: %v", res.FailedLocations)
  656. }
  657. var is []*InstanceInfo
  658. for _, i := range res.Instances {
  659. m := instanceNameRegexp.FindStringSubmatch(i.Name)
  660. if m == nil {
  661. return nil, fmt.Errorf("malformed instance name %q", i.Name)
  662. }
  663. is = append(is, &InstanceInfo{
  664. Name: m[2],
  665. DisplayName: i.DisplayName,
  666. })
  667. }
  668. return is, nil
  669. }
  670. // InstanceInfo returns information about an instance.
  671. func (iac *InstanceAdminClient) InstanceInfo(ctx context.Context, instanceID string) (*InstanceInfo, error) {
  672. ctx = mergeOutgoingMetadata(ctx, iac.md)
  673. req := &btapb.GetInstanceRequest{
  674. Name: "projects/" + iac.project + "/instances/" + instanceID,
  675. }
  676. var res *btapb.Instance
  677. err := gax.Invoke(ctx, func(ctx context.Context) error {
  678. var err error
  679. res, err = iac.iClient.GetInstance(ctx, req)
  680. return err
  681. }, retryOptions...)
  682. if err != nil {
  683. return nil, err
  684. }
  685. m := instanceNameRegexp.FindStringSubmatch(res.Name)
  686. if m == nil {
  687. return nil, fmt.Errorf("malformed instance name %q", res.Name)
  688. }
  689. return &InstanceInfo{
  690. Name: m[2],
  691. DisplayName: res.DisplayName,
  692. }, nil
  693. }
  694. // ClusterConfig contains the information necessary to create a cluster
  695. type ClusterConfig struct {
  696. InstanceID, ClusterID, Zone string
  697. NumNodes int32
  698. StorageType StorageType
  699. }
  700. func (cc *ClusterConfig) proto(project string) *btapb.Cluster {
  701. return &btapb.Cluster{
  702. ServeNodes: cc.NumNodes,
  703. DefaultStorageType: cc.StorageType.proto(),
  704. Location: "projects/" + project + "/locations/" + cc.Zone,
  705. }
  706. }
  707. // ClusterInfo represents information about a cluster.
  708. type ClusterInfo struct {
  709. Name string // name of the cluster
  710. Zone string // GCP zone of the cluster (e.g. "us-central1-a")
  711. ServeNodes int // number of allocated serve nodes
  712. State string // state of the cluster
  713. }
  714. // CreateCluster creates a new cluster in an instance.
  715. // This method will return when the cluster has been created or when an error occurs.
  716. func (iac *InstanceAdminClient) CreateCluster(ctx context.Context, conf *ClusterConfig) error {
  717. ctx = mergeOutgoingMetadata(ctx, iac.md)
  718. req := &btapb.CreateClusterRequest{
  719. Parent: "projects/" + iac.project + "/instances/" + conf.InstanceID,
  720. ClusterId: conf.ClusterID,
  721. Cluster: conf.proto(iac.project),
  722. }
  723. lro, err := iac.iClient.CreateCluster(ctx, req)
  724. if err != nil {
  725. return err
  726. }
  727. resp := btapb.Cluster{}
  728. return longrunning.InternalNewOperation(iac.lroClient, lro).Wait(ctx, &resp)
  729. }
  730. // DeleteCluster deletes a cluster from an instance.
  731. func (iac *InstanceAdminClient) DeleteCluster(ctx context.Context, instanceID, clusterID string) error {
  732. ctx = mergeOutgoingMetadata(ctx, iac.md)
  733. req := &btapb.DeleteClusterRequest{Name: "projects/" + iac.project + "/instances/" + instanceID + "/clusters/" + clusterID}
  734. _, err := iac.iClient.DeleteCluster(ctx, req)
  735. return err
  736. }
  737. // UpdateCluster updates attributes of a cluster
  738. func (iac *InstanceAdminClient) UpdateCluster(ctx context.Context, instanceID, clusterID string, serveNodes int32) error {
  739. ctx = mergeOutgoingMetadata(ctx, iac.md)
  740. cluster := &btapb.Cluster{
  741. Name: "projects/" + iac.project + "/instances/" + instanceID + "/clusters/" + clusterID,
  742. ServeNodes: serveNodes}
  743. lro, err := iac.iClient.UpdateCluster(ctx, cluster)
  744. if err != nil {
  745. return err
  746. }
  747. return longrunning.InternalNewOperation(iac.lroClient, lro).Wait(ctx, nil)
  748. }
  749. // Clusters lists the clusters in an instance.
  750. func (iac *InstanceAdminClient) Clusters(ctx context.Context, instanceID string) ([]*ClusterInfo, error) {
  751. ctx = mergeOutgoingMetadata(ctx, iac.md)
  752. req := &btapb.ListClustersRequest{Parent: "projects/" + iac.project + "/instances/" + instanceID}
  753. var res *btapb.ListClustersResponse
  754. err := gax.Invoke(ctx, func(ctx context.Context) error {
  755. var err error
  756. res, err = iac.iClient.ListClusters(ctx, req)
  757. return err
  758. }, retryOptions...)
  759. if err != nil {
  760. return nil, err
  761. }
  762. // TODO(garyelliott): Deal with failed_locations.
  763. var cis []*ClusterInfo
  764. for _, c := range res.Clusters {
  765. nameParts := strings.Split(c.Name, "/")
  766. locParts := strings.Split(c.Location, "/")
  767. cis = append(cis, &ClusterInfo{
  768. Name: nameParts[len(nameParts)-1],
  769. Zone: locParts[len(locParts)-1],
  770. ServeNodes: int(c.ServeNodes),
  771. State: c.State.String(),
  772. })
  773. }
  774. return cis, nil
  775. }
  776. // GetCluster fetches a cluster in an instance
  777. func (iac *InstanceAdminClient) GetCluster(ctx context.Context, instanceID, clusterID string) (*ClusterInfo, error) {
  778. ctx = mergeOutgoingMetadata(ctx, iac.md)
  779. req := &btapb.GetClusterRequest{Name: "projects/" + iac.project + "/instances/" + instanceID + "/clusters/" + clusterID}
  780. var c *btapb.Cluster
  781. err := gax.Invoke(ctx, func(ctx context.Context) error {
  782. var err error
  783. c, err = iac.iClient.GetCluster(ctx, req)
  784. return err
  785. }, retryOptions...)
  786. if err != nil {
  787. return nil, err
  788. }
  789. nameParts := strings.Split(c.Name, "/")
  790. locParts := strings.Split(c.Location, "/")
  791. cis := &ClusterInfo{
  792. Name: nameParts[len(nameParts)-1],
  793. Zone: locParts[len(locParts)-1],
  794. ServeNodes: int(c.ServeNodes),
  795. State: c.State.String(),
  796. }
  797. return cis, nil
  798. }
  799. // InstanceIAM returns the instance's IAM handle.
  800. func (iac *InstanceAdminClient) InstanceIAM(instanceID string) *iam.Handle {
  801. return iam.InternalNewHandleGRPCClient(iac.iClient, "projects/"+iac.project+"/instances/"+instanceID)
  802. }
  803. // Routing policies.
  804. const (
  805. // MultiClusterRouting is a policy that allows read/write requests to be
  806. // routed to any cluster in the instance. Requests will will fail over to
  807. // another cluster in the event of transient errors or delays. Choosing
  808. // this option sacrifices read-your-writes consistency to improve
  809. // availability.
  810. MultiClusterRouting = "multi_cluster_routing_use_any"
  811. // SingleClusterRouting is a policy that unconditionally routes all
  812. // read/write requests to a specific cluster. This option preserves
  813. // read-your-writes consistency, but does not improve availability.
  814. SingleClusterRouting = "single_cluster_routing"
  815. )
  816. // ProfileConf contains the information necessary to create an profile
  817. type ProfileConf struct {
  818. Name string
  819. ProfileID string
  820. InstanceID string
  821. Etag string
  822. Description string
  823. RoutingPolicy string
  824. ClusterID string
  825. AllowTransactionalWrites bool
  826. // If true, warnings are ignored
  827. IgnoreWarnings bool
  828. }
  829. // ProfileIterator iterates over profiles.
  830. type ProfileIterator struct {
  831. items []*btapb.AppProfile
  832. pageInfo *iterator.PageInfo
  833. nextFunc func() error
  834. }
  835. // ProfileAttrsToUpdate define addrs to update during an Update call. If unset, no fields will be replaced.
  836. type ProfileAttrsToUpdate struct {
  837. // If set, updates the description.
  838. Description optional.String
  839. //If set, updates the routing policy.
  840. RoutingPolicy optional.String
  841. //If RoutingPolicy is updated to SingleClusterRouting, set these fields as well.
  842. ClusterID string
  843. AllowTransactionalWrites bool
  844. // If true, warnings are ignored
  845. IgnoreWarnings bool
  846. }
  847. // GetFieldMaskPath returns the field mask path.
  848. func (p *ProfileAttrsToUpdate) GetFieldMaskPath() []string {
  849. path := make([]string, 0)
  850. if p.Description != nil {
  851. path = append(path, "description")
  852. }
  853. if p.RoutingPolicy != nil {
  854. path = append(path, optional.ToString(p.RoutingPolicy))
  855. }
  856. return path
  857. }
  858. // PageInfo supports pagination. See https://godoc.org/google.golang.org/api/iterator package for details.
  859. func (it *ProfileIterator) PageInfo() *iterator.PageInfo {
  860. return it.pageInfo
  861. }
  862. // Next returns the next result. Its second return value is iterator.Done
  863. // (https://godoc.org/google.golang.org/api/iterator) if there are no more
  864. // results. Once Next returns Done, all subsequent calls will return Done.
  865. func (it *ProfileIterator) Next() (*btapb.AppProfile, error) {
  866. if err := it.nextFunc(); err != nil {
  867. return nil, err
  868. }
  869. item := it.items[0]
  870. it.items = it.items[1:]
  871. return item, nil
  872. }
  873. // CreateAppProfile creates an app profile within an instance.
  874. func (iac *InstanceAdminClient) CreateAppProfile(ctx context.Context, profile ProfileConf) (*btapb.AppProfile, error) {
  875. ctx = mergeOutgoingMetadata(ctx, iac.md)
  876. parent := "projects/" + iac.project + "/instances/" + profile.InstanceID
  877. appProfile := &btapb.AppProfile{
  878. Etag: profile.Etag,
  879. Description: profile.Description,
  880. }
  881. if profile.RoutingPolicy == "" {
  882. return nil, errors.New("invalid routing policy")
  883. }
  884. switch profile.RoutingPolicy {
  885. case MultiClusterRouting:
  886. appProfile.RoutingPolicy = &btapb.AppProfile_MultiClusterRoutingUseAny_{
  887. MultiClusterRoutingUseAny: &btapb.AppProfile_MultiClusterRoutingUseAny{},
  888. }
  889. case SingleClusterRouting:
  890. appProfile.RoutingPolicy = &btapb.AppProfile_SingleClusterRouting_{
  891. SingleClusterRouting: &btapb.AppProfile_SingleClusterRouting{
  892. ClusterId: profile.ClusterID,
  893. AllowTransactionalWrites: profile.AllowTransactionalWrites,
  894. },
  895. }
  896. default:
  897. return nil, errors.New("invalid routing policy")
  898. }
  899. return iac.iClient.CreateAppProfile(ctx, &btapb.CreateAppProfileRequest{
  900. Parent: parent,
  901. AppProfile: appProfile,
  902. AppProfileId: profile.ProfileID,
  903. IgnoreWarnings: profile.IgnoreWarnings,
  904. })
  905. }
  906. // GetAppProfile gets information about an app profile.
  907. func (iac *InstanceAdminClient) GetAppProfile(ctx context.Context, instanceID, name string) (*btapb.AppProfile, error) {
  908. ctx = mergeOutgoingMetadata(ctx, iac.md)
  909. profileRequest := &btapb.GetAppProfileRequest{
  910. Name: "projects/" + iac.project + "/instances/" + instanceID + "/appProfiles/" + name,
  911. }
  912. var ap *btapb.AppProfile
  913. err := gax.Invoke(ctx, func(ctx context.Context) error {
  914. var err error
  915. ap, err = iac.iClient.GetAppProfile(ctx, profileRequest)
  916. return err
  917. }, retryOptions...)
  918. if err != nil {
  919. return nil, err
  920. }
  921. return ap, err
  922. }
  923. // ListAppProfiles lists information about app profiles in an instance.
  924. func (iac *InstanceAdminClient) ListAppProfiles(ctx context.Context, instanceID string) *ProfileIterator {
  925. ctx = mergeOutgoingMetadata(ctx, iac.md)
  926. listRequest := &btapb.ListAppProfilesRequest{
  927. Parent: "projects/" + iac.project + "/instances/" + instanceID,
  928. }
  929. pit := &ProfileIterator{}
  930. fetch := func(pageSize int, pageToken string) (string, error) {
  931. listRequest.PageToken = pageToken
  932. var profileRes *btapb.ListAppProfilesResponse
  933. err := gax.Invoke(ctx, func(ctx context.Context) error {
  934. var err error
  935. profileRes, err = iac.iClient.ListAppProfiles(ctx, listRequest)
  936. return err
  937. }, retryOptions...)
  938. if err != nil {
  939. return "", err
  940. }
  941. pit.items = append(pit.items, profileRes.AppProfiles...)
  942. return profileRes.NextPageToken, nil
  943. }
  944. bufLen := func() int { return len(pit.items) }
  945. takeBuf := func() interface{} { b := pit.items; pit.items = nil; return b }
  946. pit.pageInfo, pit.nextFunc = iterator.NewPageInfo(fetch, bufLen, takeBuf)
  947. return pit
  948. }
  949. // UpdateAppProfile updates an app profile within an instance.
  950. // updateAttrs should be set. If unset, all fields will be replaced.
  951. func (iac *InstanceAdminClient) UpdateAppProfile(ctx context.Context, instanceID, profileID string, updateAttrs ProfileAttrsToUpdate) error {
  952. ctx = mergeOutgoingMetadata(ctx, iac.md)
  953. profile := &btapb.AppProfile{
  954. Name: "projects/" + iac.project + "/instances/" + instanceID + "/appProfiles/" + profileID,
  955. }
  956. if updateAttrs.Description != nil {
  957. profile.Description = optional.ToString(updateAttrs.Description)
  958. }
  959. if updateAttrs.RoutingPolicy != nil {
  960. switch optional.ToString(updateAttrs.RoutingPolicy) {
  961. case MultiClusterRouting:
  962. profile.RoutingPolicy = &btapb.AppProfile_MultiClusterRoutingUseAny_{
  963. MultiClusterRoutingUseAny: &btapb.AppProfile_MultiClusterRoutingUseAny{},
  964. }
  965. case SingleClusterRouting:
  966. profile.RoutingPolicy = &btapb.AppProfile_SingleClusterRouting_{
  967. SingleClusterRouting: &btapb.AppProfile_SingleClusterRouting{
  968. ClusterId: updateAttrs.ClusterID,
  969. AllowTransactionalWrites: updateAttrs.AllowTransactionalWrites,
  970. },
  971. }
  972. default:
  973. return errors.New("invalid routing policy")
  974. }
  975. }
  976. patchRequest := &btapb.UpdateAppProfileRequest{
  977. AppProfile: profile,
  978. UpdateMask: &field_mask.FieldMask{
  979. Paths: updateAttrs.GetFieldMaskPath(),
  980. },
  981. IgnoreWarnings: updateAttrs.IgnoreWarnings,
  982. }
  983. updateRequest, err := iac.iClient.UpdateAppProfile(ctx, patchRequest)
  984. if err != nil {
  985. return err
  986. }
  987. return longrunning.InternalNewOperation(iac.lroClient, updateRequest).Wait(ctx, nil)
  988. }
  989. // DeleteAppProfile deletes an app profile from an instance.
  990. func (iac *InstanceAdminClient) DeleteAppProfile(ctx context.Context, instanceID, name string) error {
  991. ctx = mergeOutgoingMetadata(ctx, iac.md)
  992. deleteProfileRequest := &btapb.DeleteAppProfileRequest{
  993. Name: "projects/" + iac.project + "/instances/" + instanceID + "/appProfiles/" + name,
  994. IgnoreWarnings: true,
  995. }
  996. _, err := iac.iClient.DeleteAppProfile(ctx, deleteProfileRequest)
  997. return err
  998. }