// // DISCLAIMER // // Copyright 2017 ArangoDB GmbH, Cologne, Germany // // 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. // // Copyright holder is ArangoDB GmbH, Cologne, Germany // // Author Ewout Prangsma // package driver import ( "context" "encoding/json" "path" "reflect" ) // newCluster creates a new Cluster implementation. func newCluster(conn Connection) (Cluster, error) { if conn == nil { return nil, WithStack(InvalidArgumentError{Message: "conn is nil"}) } return &cluster{ conn: conn, }, nil } type cluster struct { conn Connection } // Health returns the state of the cluster func (c *cluster) Health(ctx context.Context) (ClusterHealth, error) { req, err := c.conn.NewRequest("GET", "_admin/cluster/health") if err != nil { return ClusterHealth{}, WithStack(err) } applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return ClusterHealth{}, WithStack(err) } if err := resp.CheckStatus(200); err != nil { return ClusterHealth{}, WithStack(err) } var result ClusterHealth if err := resp.ParseBody("", &result); err != nil { return ClusterHealth{}, WithStack(err) } return result, nil } // DatabaseInventory Get the inventory of the cluster containing all collections (with entire details) of a database. func (c *cluster) DatabaseInventory(ctx context.Context, db Database) (DatabaseInventory, error) { req, err := c.conn.NewRequest("GET", path.Join("_db", db.Name(), "_api/replication/clusterInventory")) if err != nil { return DatabaseInventory{}, WithStack(err) } applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return DatabaseInventory{}, WithStack(err) } if err := resp.CheckStatus(200); err != nil { return DatabaseInventory{}, WithStack(err) } var result DatabaseInventory if err := resp.ParseBody("", &result); err != nil { return DatabaseInventory{}, WithStack(err) } return result, nil } type moveShardRequest struct { Database string `json:"database"` Collection string `json:"collection"` Shard ShardID `json:"shard"` FromServer ServerID `json:"fromServer"` ToServer ServerID `json:"toServer"` } // MoveShard moves a single shard of the given collection from server `fromServer` to // server `toServer`. func (c *cluster) MoveShard(ctx context.Context, col Collection, shard ShardID, fromServer, toServer ServerID) error { req, err := c.conn.NewRequest("POST", "_admin/cluster/moveShard") if err != nil { return WithStack(err) } input := moveShardRequest{ Database: col.Database().Name(), Collection: col.Name(), Shard: shard, FromServer: fromServer, ToServer: toServer, } if _, err := req.SetBody(input); err != nil { return WithStack(err) } cs := applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return WithStack(err) } if err := resp.CheckStatus(202); err != nil { return WithStack(err) } var result jobIDResponse if err := resp.ParseBody("", &result); err != nil { return WithStack(err) } if cs.JobIDResponse != nil { *cs.JobIDResponse = result.JobID } return nil } type cleanOutServerRequest struct { Server string `json:"server"` } type jobIDResponse struct { JobID string `json:"id"` } // CleanOutServer triggers activities to clean out a DBServers. func (c *cluster) CleanOutServer(ctx context.Context, serverID string) error { req, err := c.conn.NewRequest("POST", "_admin/cluster/cleanOutServer") if err != nil { return WithStack(err) } input := cleanOutServerRequest{ Server: serverID, } if _, err := req.SetBody(input); err != nil { return WithStack(err) } cs := applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return WithStack(err) } if err := resp.CheckStatus(200, 202); err != nil { return WithStack(err) } var result jobIDResponse if err := resp.ParseBody("", &result); err != nil { return WithStack(err) } if cs.JobIDResponse != nil { *cs.JobIDResponse = result.JobID } return nil } // ResignServer triggers activities to let a DBServer resign for all shards. func (c *cluster) ResignServer(ctx context.Context, serverID string) error { req, err := c.conn.NewRequest("POST", "_admin/cluster/resignLeadership") if err != nil { return WithStack(err) } input := cleanOutServerRequest{ Server: serverID, } if _, err := req.SetBody(input); err != nil { return WithStack(err) } cs := applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return WithStack(err) } if err := resp.CheckStatus(200, 202); err != nil { return WithStack(err) } var result jobIDResponse if err := resp.ParseBody("", &result); err != nil { return WithStack(err) } if cs.JobIDResponse != nil { *cs.JobIDResponse = result.JobID } return nil } // IsCleanedOut checks if the dbserver with given ID has been cleaned out. func (c *cluster) IsCleanedOut(ctx context.Context, serverID string) (bool, error) { r, err := c.NumberOfServers(ctx) if err != nil { return false, WithStack(err) } for _, id := range r.CleanedServerIDs { if id == serverID { return true, nil } } return false, nil } // NumberOfServersResponse holds the data returned from a NumberOfServer request. type NumberOfServersResponse struct { NoCoordinators int `json:"numberOfCoordinators,omitempty"` NoDBServers int `json:"numberOfDBServers,omitempty"` CleanedServerIDs []string `json:"cleanedServers,omitempty"` } // NumberOfServers returns the number of coordinator & dbservers in a clusters and the // ID's of cleaned out servers. func (c *cluster) NumberOfServers(ctx context.Context) (NumberOfServersResponse, error) { req, err := c.conn.NewRequest("GET", "_admin/cluster/numberOfServers") if err != nil { return NumberOfServersResponse{}, WithStack(err) } applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return NumberOfServersResponse{}, WithStack(err) } if err := resp.CheckStatus(200); err != nil { return NumberOfServersResponse{}, WithStack(err) } var result NumberOfServersResponse if err := resp.ParseBody("", &result); err != nil { return NumberOfServersResponse{}, WithStack(err) } return result, nil } // RemoveServer is a low-level option to remove a server from a cluster. // This function is suitable for servers of type coordinator or dbserver. // The use of `ClientServerAdmin.Shutdown` is highly recommended above this function. func (c *cluster) RemoveServer(ctx context.Context, serverID ServerID) error { req, err := c.conn.NewRequest("POST", "_admin/cluster/removeServer") if err != nil { return WithStack(err) } if _, err := req.SetBody(serverID); err != nil { return WithStack(err) } applyContextSettings(ctx, req) resp, err := c.conn.Do(ctx, req) if err != nil { return WithStack(err) } if err := resp.CheckStatus(200, 202); err != nil { return WithStack(err) } return nil } // replicationFactor represents the replication factor of a collection // Has special value ReplicationFactorSatellite for satellite collections type replicationFactor int type inventoryCollectionParametersInternal struct { // Available from 3.7 ArangoD version. CacheEnabled bool `json:"cacheEnabled,omitempty"` Deleted bool `json:"deleted,omitempty"` DistributeShardsLike string `json:"distributeShardsLike,omitempty"` DoCompact bool `json:"doCompact,omitempty"` // Available from 3.7 ArangoD version. GloballyUniqueId string `json:"globallyUniqueId,omitempty"` ID string `json:"id,omitempty"` IndexBuckets int `json:"indexBuckets,omitempty"` Indexes []InventoryIndex `json:"indexes,omitempty"` // Available from 3.9 ArangoD version. InternalValidatorType int `json:"internalValidatorType,omitempty"` // Available from 3.7 ArangoD version. IsDisjoint bool `json:"isDisjoint,omitempty"` IsSmart bool `json:"isSmart,omitempty"` // Available from 3.7 ArangoD version. IsSmartChild bool `json:"isSmartChild,omitempty"` IsSystem bool `json:"isSystem,omitempty"` // Deprecated: since 3.7 version. It is related only to MMFiles. IsVolatile bool `json:"isVolatile,omitempty"` // Deprecated: since 3.7 version. It is related only to MMFiles. JournalSize int64 `json:"journalSize,omitempty"` KeyOptions struct { AllowUserKeys bool `json:"allowUserKeys,omitempty"` LastValue uint64 `json:"lastValue,omitempty"` Type string `json:"type,omitempty"` } `json:"keyOptions"` // Deprecated: use 'WriteConcern' instead MinReplicationFactor int `json:"minReplicationFactor,omitempty"` Name string `json:"name,omitempty"` NumberOfShards int `json:"numberOfShards,omitempty"` Path string `json:"path,omitempty"` PlanID string `json:"planId,omitempty"` ReplicationFactor replicationFactor `json:"replicationFactor,omitempty"` // Schema for collection validation Schema *CollectionSchemaOptions `json:"schema,omitempty"` ShadowCollections []int `json:"shadowCollections,omitempty"` ShardingStrategy ShardingStrategy `json:"shardingStrategy,omitempty"` ShardKeys []string `json:"shardKeys,omitempty"` Shards map[ShardID][]ServerID `json:"shards,omitempty"` // Optional only for some collections. SmartGraphAttribute string `json:"smartGraphAttribute,omitempty"` // Optional only for some collections. SmartJoinAttribute string `json:"smartJoinAttribute,omitempty"` Status CollectionStatus `json:"status,omitempty"` // Available from 3.7 ArangoD version SyncByRevision bool `json:"syncByRevision,omitempty"` Type CollectionType `json:"type,omitempty"` // Available from 3.7 ArangoD version UsesRevisionsAsDocumentIds bool `json:"usesRevisionsAsDocumentIds,omitempty"` WaitForSync bool `json:"waitForSync,omitempty"` // Available from 3.6 ArangoD version. WriteConcern int `json:"writeConcern,omitempty"` // Available from 3.10 ArangoD version. ComputedValues []ComputedValue `json:"computedValues,omitempty"` } func (p *InventoryCollectionParameters) asInternal() inventoryCollectionParametersInternal { lastValue := p.KeyOptions.LastValueV2 if lastValue == 0 && p.KeyOptions.LastValue != 0 { lastValue = uint64(p.KeyOptions.LastValue) } return inventoryCollectionParametersInternal{ CacheEnabled: p.CacheEnabled, Deleted: p.Deleted, DistributeShardsLike: p.DistributeShardsLike, DoCompact: p.DoCompact, GloballyUniqueId: p.GloballyUniqueId, ID: p.ID, IndexBuckets: p.IndexBuckets, Indexes: p.Indexes, InternalValidatorType: p.InternalValidatorType, IsDisjoint: p.IsDisjoint, IsSmart: p.IsSmart, IsSmartChild: p.IsSmartChild, IsSystem: p.IsSystem, IsVolatile: p.IsVolatile, JournalSize: p.JournalSize, KeyOptions: struct { AllowUserKeys bool `json:"allowUserKeys,omitempty"` LastValue uint64 `json:"lastValue,omitempty"` Type string `json:"type,omitempty"` }{ p.KeyOptions.AllowUserKeys, lastValue, p.KeyOptions.Type}, MinReplicationFactor: p.MinReplicationFactor, Name: p.Name, NumberOfShards: p.NumberOfShards, Path: p.Path, PlanID: p.PlanID, ReplicationFactor: replicationFactor(p.ReplicationFactor), Schema: p.Schema, ShadowCollections: p.ShadowCollections, ShardingStrategy: p.ShardingStrategy, ShardKeys: p.ShardKeys, Shards: p.Shards, SmartGraphAttribute: p.SmartGraphAttribute, SmartJoinAttribute: p.SmartJoinAttribute, Status: p.Status, SyncByRevision: p.SyncByRevision, Type: p.Type, UsesRevisionsAsDocumentIds: p.UsesRevisionsAsDocumentIds, WaitForSync: p.WaitForSync, WriteConcern: p.WriteConcern, ComputedValues: p.ComputedValues, } } func (p *InventoryCollectionParameters) fromInternal(i inventoryCollectionParametersInternal) { *p = i.asExternal() } func (p *inventoryCollectionParametersInternal) asExternal() InventoryCollectionParameters { return InventoryCollectionParameters{ CacheEnabled: p.CacheEnabled, Deleted: p.Deleted, DistributeShardsLike: p.DistributeShardsLike, DoCompact: p.DoCompact, GloballyUniqueId: p.GloballyUniqueId, ID: p.ID, IndexBuckets: p.IndexBuckets, Indexes: p.Indexes, InternalValidatorType: p.InternalValidatorType, IsDisjoint: p.IsDisjoint, IsSmart: p.IsSmart, IsSmartChild: p.IsSmartChild, IsSystem: p.IsSystem, IsVolatile: p.IsVolatile, JournalSize: p.JournalSize, KeyOptions: struct { AllowUserKeys bool `json:"allowUserKeys,omitempty"` LastValue int64 `json:"-"` LastValueV2 uint64 `json:"lastValue,omitempty"` Type string `json:"type,omitempty"` }{ p.KeyOptions.AllowUserKeys, // cast to int64 to keep backwards compatibility for most cases int64(p.KeyOptions.LastValue), p.KeyOptions.LastValue, p.KeyOptions.Type}, MinReplicationFactor: p.MinReplicationFactor, Name: p.Name, NumberOfShards: p.NumberOfShards, Path: p.Path, PlanID: p.PlanID, ReplicationFactor: int(p.ReplicationFactor), Schema: p.Schema, ShadowCollections: p.ShadowCollections, ShardingStrategy: p.ShardingStrategy, ShardKeys: p.ShardKeys, Shards: p.Shards, SmartGraphAttribute: p.SmartGraphAttribute, SmartJoinAttribute: p.SmartJoinAttribute, Status: p.Status, SyncByRevision: p.SyncByRevision, Type: p.Type, UsesRevisionsAsDocumentIds: p.UsesRevisionsAsDocumentIds, WaitForSync: p.WaitForSync, WriteConcern: p.WriteConcern, ComputedValues: p.ComputedValues, } } // MarshalJSON converts InventoryCollectionParameters into json func (p *InventoryCollectionParameters) MarshalJSON() ([]byte, error) { return json.Marshal(p.asInternal()) } // UnmarshalJSON loads InventoryCollectionParameters from json func (p *InventoryCollectionParameters) UnmarshalJSON(d []byte) error { var internal inventoryCollectionParametersInternal if err := json.Unmarshal(d, &internal); err != nil { return err } p.fromInternal(internal) return nil } const ( replicationFactorSatelliteString string = "satellite" ) // MarshalJSON marshals InventoryCollectionParameters to arangodb json representation func (r replicationFactor) MarshalJSON() ([]byte, error) { var replicationFactor interface{} if int(r) == ReplicationFactorSatellite { replicationFactor = replicationFactorSatelliteString } else { replicationFactor = int(r) } return json.Marshal(replicationFactor) } // UnmarshalJSON marshals InventoryCollectionParameters to arangodb json representation func (r *replicationFactor) UnmarshalJSON(d []byte) error { var internal interface{} if err := json.Unmarshal(d, &internal); err != nil { return err } if i, ok := internal.(float64); ok { *r = replicationFactor(i) return nil } else if str, ok := internal.(string); ok { if ok && str == replicationFactorSatelliteString { *r = replicationFactor(ReplicationFactorSatellite) return nil } } return &json.UnmarshalTypeError{ Value: string(d), Type: reflect.TypeOf(r).Elem(), } }