Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grouping server specific info calls in ClientServerInfo, exposing ServerRole API #89

Merged
merged 4 commits into from
Feb 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ import (

// Client provides access to a single arangodb database server, or an entire cluster of arangodb servers.
type Client interface {
// Version returns version information from the connected database server.
// Use WithDetails to configure a context that will include additional details in the return VersionInfo.
Version(ctx context.Context) (VersionInfo, error)

// SynchronizeEndpoints fetches all endpoints from an ArangoDB cluster and updates the
// connection to use those endpoints.
// When this client is connected to a single server, nothing happens.
Expand All @@ -56,6 +52,9 @@ type Client interface {
// Cluster functions
ClientCluster

// Individual server information functions
ClientServerInfo

// Server/cluster administration functions
ClientServerAdmin
}
Expand Down
4 changes: 2 additions & 2 deletions client_cluster_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ import (
// 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)
role, err := c.ServerRole(ctx)
if err != nil {
return nil, WithStack(err)
}
if role == "SINGLE" {
if role == ServerRoleSingle || role == ServerRoleSingleActive || role == ServerRoleSinglePassive {
// Standalone server, this is wrong
return nil, WithStack(newArangoError(412, 0, "Cluster expected, found SINGLE server"))
}
Expand Down
52 changes: 2 additions & 50 deletions client_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,38 +61,17 @@ func (c *client) Connection() Connection {
return c.conn
}

// Version returns version information from the connected database server.
func (c *client) Version(ctx context.Context) (VersionInfo, error) {
req, err := c.conn.NewRequest("GET", "_api/version")
if err != nil {
return VersionInfo{}, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := c.conn.Do(ctx, req)
if err != nil {
return VersionInfo{}, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return VersionInfo{}, WithStack(err)
}
var data VersionInfo
if err := resp.ParseBody("", &data); err != nil {
return VersionInfo{}, WithStack(err)
}
return data, nil
}

// SynchronizeEndpoints fetches all endpoints from an ArangoDB cluster and updates the
// connection to use those endpoints.
// When this client is connected to a single server, nothing happens.
// When this client is connected to a cluster of servers, the connection will be updated to reflect
// the layout of the cluster.
func (c *client) SynchronizeEndpoints(ctx context.Context) error {
role, mode, err := c.role(ctx)
role, err := c.ServerRole(ctx)
if err != nil {
return WithStack(err)
}
if role == "SINGLE" && mode != "resilient" {
if role == ServerRoleSingle {
// Standalone server, do nothing
return nil
}
Expand Down Expand Up @@ -126,33 +105,6 @@ func (c *client) autoSynchronizeEndpoints(interval time.Duration) {
}
}

type roleResponse struct {
Role string `json:"role,omitempty"`
Mode string `json:"mode,omitempty"`
}

// role returns the role of the server that answers the request.
// Returns role, mode, error.
func (c *client) role(ctx context.Context) (string, string, error) {
req, err := c.conn.NewRequest("GET", "_admin/server/role")
if 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)
}
var data roleResponse
if err := resp.ParseBody("", &data); err != nil {
return "", "", WithStack(err)
}
return data.Role, data.Mode, nil
}

type clusterEndpointsResponse struct {
Endpoints []clusterEndpoint `json:"endpoints,omitempty"`
}
Expand Down
57 changes: 57 additions & 0 deletions client_server_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// DISCLAIMER
//
// Copyright 2018 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"

// ClientServerInfo provides access to information about a single ArangoDB server.
// When your client uses multiple endpoints, it is undefined which server
// will respond to requests of this interface.
type ClientServerInfo interface {
// Version returns version information from the connected database server.
// Use WithDetails to configure a context that will include additional details in the return VersionInfo.
Version(ctx context.Context) (VersionInfo, error)

// ServerRole returns the role of the server that answers the request.
ServerRole(ctx context.Context) (ServerRole, error)
}

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

const (
// ServerRoleSingle indicates that the server is a single-server instance
ServerRoleSingle ServerRole = "Single"
// ServerRoleSingleActive indicates that the server is a the leader of a single-server resilient pair
ServerRoleSingleActive ServerRole = "SingleActive"
// ServerRoleSinglePassive indicates that the server is a a follower of a single-server resilient pair
ServerRoleSinglePassive ServerRole = "SinglePassive"
// ServerRoleDBServer indicates that the server is a dbserver within a cluster
ServerRoleDBServer ServerRole = "DBServer"
// ServerRoleCoordinator indicates that the server is a coordinator within a cluster
ServerRoleCoordinator ServerRole = "Coordinator"
// ServerRoleAgent indicates that the server is an agent within a cluster
ServerRoleAgent ServerRole = "Agent"
// ServerRoleUndefined indicates that the role of the server cannot be determined
ServerRoleUndefined ServerRole = "Undefined"
)
125 changes: 125 additions & 0 deletions client_server_info_impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// DISCLAIMER
//
// Copyright 2018 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"
)

// Version returns version information from the connected database server.
func (c *client) Version(ctx context.Context) (VersionInfo, error) {
req, err := c.conn.NewRequest("GET", "_api/version")
if err != nil {
return VersionInfo{}, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := c.conn.Do(ctx, req)
if err != nil {
return VersionInfo{}, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return VersionInfo{}, WithStack(err)
}
var data VersionInfo
if err := resp.ParseBody("", &data); err != nil {
return VersionInfo{}, WithStack(err)
}
return data, nil
}

// roleResponse contains the response body of the `/admin/server/role` api.
type roleResponse struct {
// Role of the server within a cluster
Role string `json:"role,omitempty"`
Mode string `json:"mode,omitempty"`
}

// asServerRole converts the response into a ServerRole
func (r roleResponse) asServerRole(ctx context.Context, c *client) (ServerRole, error) {
switch r.Role {
case "SINGLE":
switch r.Mode {
case "resilient":
if err := c.echo(ctx); IsNoLeader(err) {
return ServerRoleSinglePassive, nil
} else if err != nil {
return ServerRoleUndefined, WithStack(err)
}
return ServerRoleSingleActive, nil
default:
return ServerRoleSingle, nil
}
case "PRIMARY":
return ServerRoleDBServer, nil
case "COORDINATOR":
return ServerRoleCoordinator, nil
case "AGENT":
return ServerRoleAgent, nil
case "UNDEFINED":
return ServerRoleUndefined, nil
default:
return ServerRoleUndefined, nil
}
}

// ServerRole returns the role of the server that answers the request.
func (c *client) ServerRole(ctx context.Context) (ServerRole, error) {
req, err := c.conn.NewRequest("GET", "_admin/server/role")
if err != nil {
return ServerRoleUndefined, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := c.conn.Do(ctx, req)
if err != nil {
return ServerRoleUndefined, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return ServerRoleUndefined, WithStack(err)
}
var data roleResponse
if err := resp.ParseBody("", &data); err != nil {
return ServerRoleUndefined, WithStack(err)
}
role, err := data.asServerRole(ctx, c)
if err != nil {
return ServerRoleUndefined, WithStack(err)
}
return role, nil
}

// clusterEndpoints returns the endpoints of a cluster.
func (c *client) echo(ctx context.Context) error {
req, err := c.conn.NewRequest("GET", "_admin/echo")
if 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)
}
return nil
}
9 changes: 0 additions & 9 deletions cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,6 @@ type ServerHealth struct {
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

Expand Down
13 changes: 12 additions & 1 deletion cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import (
driver "github.com/arangodb/go-driver"
)

const (
keyFollowLeaderRedirect driver.ContextKey = "arangodb-followLeaderRedirect"
)

// ConnectionConfig provides all configuration options for a cluster connection.
type ConnectionConfig struct {
// DefaultTimeout is the timeout used by requests that have no timeout set in the given context.
Expand Down Expand Up @@ -102,8 +106,15 @@ func (c *clusterConnection) NewRequest(method, path string) (driver.Request, err

// Do performs a given request, returning its response.
func (c *clusterConnection) Do(ctx context.Context, req driver.Request) (driver.Response, error) {
followLeaderRedirect := true
if ctx == nil {
ctx = context.Background()
} else {
if v := ctx.Value(keyFollowLeaderRedirect); v != nil {
if on, ok := v.(bool); ok {
followLeaderRedirect = on
}
}
}
// Timeout management.
// We take the given timeout and divide it in 3 so we allow for other servers
Expand Down Expand Up @@ -156,7 +167,7 @@ func (c *clusterConnection) Do(ctx context.Context, req driver.Request) (driver.
}

}
if !isNoLeaderResponse {
if !isNoLeaderResponse || !followLeaderRedirect {
if err == nil {
// We're done
return resp, nil
Expand Down
15 changes: 15 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
keyIgnoreRevs ContextKey = "arangodb-ignoreRevs"
keyEnforceReplicationFactor ContextKey = "arangodb-enforceReplicationFactor"
keyConfigured ContextKey = "arangodb-configured"
keyFollowLeaderRedirect ContextKey = "arangodb-followLeaderRedirect"
)

// WithRevision is used to configure a context to make document
Expand Down Expand Up @@ -192,6 +193,13 @@ func WithConfigured(parent context.Context, value ...bool) context.Context {
return context.WithValue(contextOrBackground(parent), keyConfigured, v)
}

// WithFollowLeaderRedirect is used to configure a context to return turn on/off
// following redirection responses from the server when the request is answered by a follower.
// Default behavior is "on".
func WithFollowLeaderRedirect(parent context.Context, value bool) context.Context {
return context.WithValue(contextOrBackground(parent), keyFollowLeaderRedirect, value)
}

type contextSettings struct {
Silent bool
WaitForSync bool
Expand All @@ -205,6 +213,7 @@ type contextSettings struct {
IgnoreRevs *bool
EnforceReplicationFactor *bool
Configured *bool
FollowLeaderRedirect *bool
}

// applyContextSettings returns the settings configured in the context in the given request.
Expand Down Expand Up @@ -312,6 +321,12 @@ func applyContextSettings(ctx context.Context, req Request) contextSettings {
result.Configured = &configured
}
}
// FollowLeaderRedirect
if v := ctx.Value(keyFollowLeaderRedirect); v != nil {
if followLeaderRedirect, ok := v.(bool); ok {
result.FollowLeaderRedirect = &followLeaderRedirect
}
}
return result
}

Expand Down