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.
489 lines
16 KiB
489 lines
16 KiB
//
|
|
// 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(),
|
|
}
|
|
}
|
|
|