Skip to content

Commit

Permalink
Adding cluster specific operations
Browse files Browse the repository at this point in the history
  • Loading branch information
ewoutp committed Dec 14, 2017
1 parent 53fd1ab commit d1c8929
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Expand Up @@ -32,5 +32,6 @@
"${commentprefix} ",
""
]
}
},
"go.gopath": "${workspaceRoot}/.gobuild"
}
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -3,7 +3,7 @@ SCRIPTDIR := $(shell pwd)
ROOTDIR := $(shell cd $(SCRIPTDIR) && pwd)

GOBUILDDIR := $(SCRIPTDIR)/.gobuild
GOVERSION := 1.8.3-alpine
GOVERSION := 1.9.2-alpine
TMPDIR := $(GOBUILDDIR)

ifndef ARANGODB
Expand Down
3 changes: 3 additions & 0 deletions client.go
Expand Up @@ -53,6 +53,9 @@ type Client interface {
// User functions
ClientUsers

// Cluster functions
ClientCluster

// Server/cluster administration functions
ClientServerAdmin
}
Expand Down
33 changes: 33 additions & 0 deletions client_cluster.go
@@ -0,0 +1,33 @@
//
// 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"

// ClientCluster provides methods needed to access cluster functionality from a client.
type ClientCluster interface {
// Cluster provides access to cluster wide specific operations.
// To use this interface, an ArangoDB cluster is required.
// If this method is a called without a cluster, a PreconditionFailed error is returned.
Cluster(ctx context.Context) (Cluster, error)
}
46 changes: 46 additions & 0 deletions client_cluster_impl.go
@@ -0,0 +1,46 @@
//
// 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"
)

// Cluster provides access to cluster wide specific operations.
// To use this interface, an ArangoDB cluster is required.
// If this method is a called without a cluster, a PreconditionFailed error is returned.
func (c *client) Cluster(ctx context.Context) (Cluster, error) {
role, _, err := c.role(ctx)
if err != nil {
return nil, WithStack(err)
}
if role == "SINGLE" {
// Standalone server, this is wrong
return nil, WithStack(newArangoError(412, 0, "Cluster expected, found SINGLE server"))
}
cl, err := newCluster(c.conn)
if err != nil {
return nil, WithStack(err)
}
return cl, nil
}
80 changes: 80 additions & 0 deletions cluster.go
@@ -0,0 +1,80 @@
//
// 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"
"time"
)

// Cluster provides access to cluster wide specific operations.
// To use this interface, an ArangoDB cluster is required.
type Cluster interface {
// Get the cluster configuration & health
Health(ctx context.Context) (ClusterHealth, error)

// MoveShard moves a single shard of the given collection from server `fromServer` to
// server `toServer`.
MoveShard(ctx context.Context, col Collection, shard int, fromServer, toServer ServerID) error
}

// ServerID identifies an arangod server in a cluster.
type ServerID string

// ClusterHealth contains health information for all servers in a cluster.
type ClusterHealth struct {
// Unique identifier of the entire cluster.
// This ID is created when the cluster was first created.
ID string `json:"ClusterId"`
// Health per server
Health map[ServerID]ServerHealth `json:"Health"`
}

// ServerHealth contains health information of a single server in a cluster.
type ServerHealth struct {
Endpoint string `json:"Endpoint"`
LastHeartbeatAcked time.Time `json:"LastHeartbeatAcked"`
LastHeartbeatSent time.Time `json:"LastHeartbeatSent"`
LastHeartbeatStatus string `json:"LastHeartbeatStatus"`
Role ServerRole `json:"Role"`
ShortName string `json:"ShortName"`
Status ServerStatus `json:"Status"`
CanBeDeleted bool `json:"CanBeDeleted"`
HostID string `json:"Host,omitempty"`
}

// ServerRole is the role of an arangod server
type ServerRole string

const (
ServerRoleDBServer ServerRole = "DBServer"
ServerRoleCoordinator ServerRole = "Coordinator"
ServerRoleAgent ServerRole = "Agent"
)

// ServerStatus describes the health status of a server
type ServerStatus string

const (
ServerStatusGood ServerStatus = "GOOD"
)
99 changes: 99 additions & 0 deletions cluster_impl.go
@@ -0,0 +1,99 @@
//
// 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"

// 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
}

// LoggerState returns the state of the replication logger
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
}

type moveShardRequest struct {
Database string `json:"database"`
Collection string `json:"collection"`
Shard int `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 int, 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)
}
applyContextSettings(ctx, req)
resp, err := c.conn.Do(ctx, req)
if err != nil {
return WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return WithStack(err)
}
if err := resp.ParseBody("", nil); err != nil {
return WithStack(err)
}
return nil
}
70 changes: 70 additions & 0 deletions test/cluster_test.go
@@ -0,0 +1,70 @@
//
// 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 test

import (
"context"
"testing"

driver "github.com/arangodb/go-driver"
)

// TestClusterHealth tests the Cluster.Health method.
func TestClusterHealth(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
cl, err := c.Cluster(ctx)
if driver.IsPreconditionFailed(err) {
t.Skip("Not a cluster")
} else {
h, err := cl.Health(ctx)
if err != nil {
t.Fatalf("Health failed: %s", describe(err))
}
if h.ID == "" {
t.Error("Expected cluster ID to be non-empty")
}
agents := 0
dbservers := 0
coordinators := 0
for _, sh := range h.Health {
switch sh.Role {
case driver.ServerRoleAgent:
agents++
case driver.ServerRoleDBServer:
dbservers++
case driver.ServerRoleCoordinator:
coordinators++
}
}
if agents == 0 {
t.Error("Expected at least 1 agent")
}
if dbservers == 0 {
t.Error("Expected at least 1 dbserver")
}
if coordinators == 0 {
t.Error("Expected at least 1 coordinator")
}
}
}

0 comments on commit d1c8929

Please sign in to comment.