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.
153 lines
4.7 KiB
153 lines
4.7 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"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/arangodb/go-driver/util"
|
|
)
|
|
|
|
// NewClient creates a new Client based on the given config setting.
|
|
func NewClient(config ClientConfig) (Client, error) {
|
|
if config.Connection == nil {
|
|
return nil, WithStack(InvalidArgumentError{Message: "Connection is not set"})
|
|
}
|
|
conn := config.Connection
|
|
if config.Authentication != nil {
|
|
var err error
|
|
conn, err = conn.SetAuthentication(config.Authentication)
|
|
if err != nil {
|
|
return nil, WithStack(err)
|
|
}
|
|
}
|
|
c := &client{
|
|
conn: conn,
|
|
}
|
|
if config.SynchronizeEndpointsInterval > 0 {
|
|
go c.autoSynchronizeEndpoints(config.SynchronizeEndpointsInterval)
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// client implements the Client interface.
|
|
type client struct {
|
|
conn Connection
|
|
}
|
|
|
|
// Connection returns the connection used by this client
|
|
func (c *client) Connection() Connection {
|
|
return c.conn
|
|
}
|
|
|
|
// 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 {
|
|
return c.SynchronizeEndpoints2(ctx, "")
|
|
}
|
|
|
|
// SynchronizeEndpoints2 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.
|
|
// Compared to SynchronizeEndpoints, this function expects a database name as additional parameter.
|
|
// This database name is used to call `_db/<dbname>/_api/cluster/endpoints`. SynchronizeEndpoints uses
|
|
// the default database, i.e. `_system`. In the case the user does not have access to `_system`,
|
|
// SynchronizeEndpoints does not work with earlier versions of arangodb.
|
|
func (c *client) SynchronizeEndpoints2(ctx context.Context, dbname string) error {
|
|
// Cluster mode, fetch endpoints
|
|
cep, err := c.clusterEndpoints(ctx, dbname)
|
|
if err != nil {
|
|
// ignore Forbidden: automatic failover is not enabled errors
|
|
if !IsArangoErrorWithErrorNum(err, ErrHttpForbidden, ErrHttpInternal, 0, ErrNotImplemented, ErrForbidden) {
|
|
// 3.2 returns no error code, thus check for 0
|
|
// 501 with ErrorNum 9 is in there since 3.7, earlier versions returned 403 and ErrorNum 11.
|
|
return WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
var endpoints []string
|
|
for _, ep := range cep.Endpoints {
|
|
endpoints = append(endpoints, util.FixupEndpointURLScheme(ep.Endpoint))
|
|
}
|
|
|
|
// Update connection
|
|
if err := c.conn.UpdateEndpoints(endpoints); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// autoSynchronizeEndpoints performs automatic endpoint synchronization.
|
|
func (c *client) autoSynchronizeEndpoints(interval time.Duration) {
|
|
for {
|
|
// SynchronizeEndpoints endpoints
|
|
c.SynchronizeEndpoints(nil)
|
|
|
|
// Wait a bit
|
|
time.Sleep(interval)
|
|
}
|
|
}
|
|
|
|
type clusterEndpointsResponse struct {
|
|
Endpoints []clusterEndpoint `json:"endpoints,omitempty"`
|
|
}
|
|
|
|
type clusterEndpoint struct {
|
|
Endpoint string `json:"endpoint,omitempty"`
|
|
}
|
|
|
|
// clusterEndpoints returns the endpoints of a cluster.
|
|
func (c *client) clusterEndpoints(ctx context.Context, dbname string) (clusterEndpointsResponse, error) {
|
|
var url string
|
|
if dbname == "" {
|
|
url = "_api/cluster/endpoints"
|
|
} else {
|
|
url = path.Join("_db", pathEscape(dbname), "_api/cluster/endpoints")
|
|
}
|
|
req, err := c.conn.NewRequest("GET", url)
|
|
if err != nil {
|
|
return clusterEndpointsResponse{}, WithStack(err)
|
|
}
|
|
applyContextSettings(ctx, req)
|
|
resp, err := c.conn.Do(ctx, req)
|
|
if err != nil {
|
|
return clusterEndpointsResponse{}, WithStack(err)
|
|
}
|
|
if err := resp.CheckStatus(200); err != nil {
|
|
return clusterEndpointsResponse{}, WithStack(err)
|
|
}
|
|
var data clusterEndpointsResponse
|
|
if err := resp.ParseBody("", &data); err != nil {
|
|
return clusterEndpointsResponse{}, WithStack(err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|