├── .circleci └── config.yml ├── .gitignore ├── .golangci.yaml ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── Dockerfile ├── LICENSE ├── LICENSE.BOILERPLATE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── VERSION ├── admin.go ├── auth.go ├── auth_test.go ├── certificates.go ├── client ├── api.go ├── client.go ├── error.go └── http.go ├── config.go ├── docs ├── arangod_conf_update_spec.md ├── design.md ├── http_api.md ├── recovery_spec.md └── upgrade_spec.md ├── examples ├── local-sync │ └── README.md ├── mac_on_docker_cluster.sh └── systemd │ ├── README.md │ └── arangodb.service ├── go.mod ├── go.sum ├── help.go ├── internal └── generate-exit-codes │ └── main.go ├── main.go ├── options.go ├── pkg ├── api │ ├── empty.go │ ├── error.go │ └── inventory.go ├── arangodb │ └── executable.go ├── definitions │ ├── common.go │ ├── exitcodes.go │ ├── exitcodes_generated.go │ ├── process_type.go │ └── server_type.go ├── docker │ ├── cgroups_nonunix.go │ ├── cgroups_unix.go │ └── docker.go ├── features │ ├── feature.go │ └── jwt.go ├── logging │ ├── adapter.go │ ├── context.go │ ├── error.go │ ├── logger.go │ └── rotating_writer.go ├── net │ ├── ipv6.go │ └── ipv6_linux.go ├── terminal │ ├── terminal.go │ └── terminal_windows.go ├── trigger │ └── trigger.go └── utils │ ├── buffer.go │ ├── buffer_test.go │ └── util.go ├── remove.go ├── service ├── action_resign_leadership.go ├── actions │ └── actions.go ├── arangod_config.go ├── arangod_config_builder.go ├── arangosync_config_builder.go ├── authentication.go ├── backoff.go ├── bootstrap.go ├── bootstrap_config.go ├── bootstrap_master.go ├── bootstrap_slave.go ├── certificate.go ├── client_builder.go ├── clients │ ├── client.go │ ├── encryption.go │ ├── jwt.go │ ├── sha.go │ └── tls.go ├── cluster_config.go ├── command_file.go ├── command_file_test.go ├── database_features.go ├── docker.go ├── envs.go ├── error.go ├── files.go ├── guess_address.go ├── image_pull_policy.go ├── inventory.go ├── job.go ├── jwt.go ├── local_slaves.go ├── mode.go ├── options │ ├── forbidden.go │ ├── options.go │ ├── options_test.go │ ├── persistent.go │ └── utils.go ├── peer.go ├── port_check.go ├── process_wrapper.go ├── runner.go ├── runner_docker.go ├── runner_process.go ├── runner_process_shared.go ├── runner_process_windows.go ├── runtime_cluster_manager.go ├── runtime_server_manager.go ├── runtime_server_manager_shared.go ├── runtime_server_manager_windows.go ├── server.go ├── server_config_builder.go ├── service.go ├── service_recovery.go ├── setup_config.go ├── state.go ├── storage_engine.go ├── upgrade_manager.go ├── url_schemes.go ├── util.go └── version_check.go ├── start.go ├── stop.go ├── test ├── docker_check_exitcodes_test.go ├── docker_cluster_default_test.go ├── docker_cluster_diff_logdir_test.go ├── docker_cluster_diff_ports_test.go ├── docker_cluster_local_test.go ├── docker_cluster_multi_join_test.go ├── docker_cluster_recovery_test.go ├── docker_cluster_upgrade_test.go ├── docker_database_version_test.go ├── docker_install_go.sh ├── docker_restart_agent_member_test.go ├── docker_restart_noagent_member_test.go ├── docker_single_test.go ├── docker_util.go ├── features_util.go ├── gexpect.go ├── log.go ├── passthrough_test.go ├── process_check_exitcodes_test.go ├── process_cluster_default_test.go ├── process_cluster_diff_logdir_test.go ├── process_cluster_diff_ports_test.go ├── process_cluster_local_test.go ├── process_cluster_multi_join_test.go ├── process_cluster_recovery_test.go ├── process_cluster_resign_leadership_test.go ├── process_cluster_upgrade_test.go ├── process_config_test.go ├── process_database_version_test.go ├── process_restart_agent_member_test.go ├── process_restart_noagent_member_test.go ├── process_single_test.go ├── process_util.go ├── server_util.go ├── sync_util.go ├── testdata │ ├── activefailover.conf │ ├── invalidkeys.conf │ ├── invalidvalues.conf │ ├── single-passthrough-persistent-new.conf │ ├── single-passthrough-persistent-old.conf │ ├── single-passthrough.conf │ ├── single-sections.conf │ └── single.conf ├── timeout.go └── utils.go ├── tools └── release │ └── release.go ├── upgrade.go └── versioninfo.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | parameters: 4 | goImage: 5 | type: string 6 | default: "gcr.io/gcr-for-testing/golang:1.23.8" 7 | arangodbImage: 8 | type: string 9 | default: "gcr.io/gcr-for-testing/arangodb/enterprise:latest" 10 | alpineImage: 11 | type: string 12 | default: "gcr.io/gcr-for-testing/alpine:3.21" 13 | starterModes: 14 | type: string 15 | default: "single,cluster" 16 | 17 | executors: 18 | golang-executor: 19 | docker: 20 | - image: gcr.io/gcr-for-testing/golang:1.23.8 21 | machine-executor: 22 | machine: 23 | image: ubuntu-2204:current 24 | docker_layer_caching: true 25 | 26 | jobs: 27 | check-code: 28 | executor: golang-executor 29 | steps: 30 | - setup_remote_docker: 31 | docker_layer_caching: true 32 | - checkout 33 | - run: make init 34 | - run: make check 35 | - run: make binaries 36 | - run: make vulncheck 37 | 38 | run-tests: 39 | executor: machine-executor 40 | steps: 41 | - checkout 42 | - run: 43 | name: Run-tests 44 | command: | 45 | if [ -z "$CIRCLE_PULL_REQUEST" ]; then 46 | echo "This is not a pull request. Skipping..." 47 | exit 0 48 | fi 49 | make run-tests 50 | environment: 51 | ARANGODB: << pipeline.parameters.arangodbImage >> 52 | GOIMAGE: << pipeline.parameters.goImage >> 53 | ALPINE_IMAGE: << pipeline.parameters.alpineImage >> 54 | STARTER_MODES: << pipeline.parameters.starterModes >> 55 | DOCKER_PLATFORMS: "linux/amd64" 56 | VERBOSE: 1 57 | 58 | workflows: 59 | version: 2 60 | 61 | run_always: 62 | jobs: 63 | - check-code 64 | - run-tests 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gobuild 2 | /arangodb 3 | bin 4 | vendor/ 5 | 6 | .envrc 7 | .DS_Store 8 | 9 | #IDE's 10 | .idea 11 | 12 | resource.syso 13 | CONTAINER 14 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | run: 4 | timeout: 30m 5 | skip-dirs: 6 | - vendor 7 | - .gobuild 8 | 9 | linters: 10 | disable-all: true 11 | enable: 12 | - varcheck 13 | - structcheck 14 | - unconvert 15 | - gci 16 | - gofmt 17 | 18 | linters-settings: 19 | gci: 20 | sections: 21 | - standard 22 | - default 23 | - prefix(github.com/arangodb) 24 | - prefix(github.com/arangodb-helper/arangodb) 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "go.testFlags": ["-v"], 4 | "go.testTimeout": "600s", 5 | "search.exclude": { 6 | "**/node_modules": true, 7 | "**/bower_components": true, 8 | "vendor": true 9 | } 10 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Team-go owns the entire repository 2 | * @ajanikow @jwierzbo @enikon @djmeuleman 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG IMAGE 2 | FROM ${IMAGE} 3 | 4 | ARG TARGETARCH 5 | COPY bin/linux/${TARGETARCH}/arangodb /app/ 6 | 7 | EXPOSE 8528 8 | 9 | VOLUME /data 10 | 11 | # Data directory 12 | ENV DATA_DIR=/data 13 | 14 | # Signal running in docker 15 | ENV RUNNING_IN_DOCKER=true 16 | 17 | # Docker image containing arangod. 18 | ENV DOCKER_IMAGE=arangodb/arangodb:latest 19 | 20 | ENTRYPOINT ["/app/arangodb"] 21 | -------------------------------------------------------------------------------- /LICENSE.BOILERPLATE: -------------------------------------------------------------------------------- 1 | 2 | DISCLAIMER 3 | 4 | Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | 20 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainer Instructions 2 | 3 | ## Init 4 | 5 | Before starting any actions on repo, please do 6 | ```bash 7 | make init 8 | ``` 9 | 10 | ## Running tests 11 | 12 | A subset of all tests are run in CI pipeline. 13 | 14 | To run the entire test set, set the following environment variables: 15 | 16 | - `ARANGODB`: name of Docker image for ArangoDB to use 17 | - `VERBOSE` (optional): set to `1` for more output 18 | - `IP` (for Docker tests): set to a valid IP address on the local machine 19 | - `TESTOPTIONS`` (optional): set to `-test.run=REGEXPTOMATCHTESTS` to 20 | run only some tests 21 | 22 | Then run: 23 | ```bash 24 | make run-tests 25 | ``` 26 | 27 | To run only unit tests, execute: 28 | ```bash 29 | make run-unit-tests 30 | ``` 31 | 32 | ## Building a release 33 | 34 | On Linux, make sure that neither `.gobuild/tmp` nor `bin` contains any 35 | files which are owned by `root`. For example, a `chmod -R` with your 36 | user account is enough. This does not seem to be necessary on OSX. 37 | 38 | To make a release you must have: 39 | 40 | - A github access token in `~/.arangodb/github-token` that has read/write access 41 | for this repository. 42 | - Push permission for the current docker account (`docker login -u `) 43 | for the `arangodb` docker hub namespace. 44 | - The latest checked out `master` branch of this repository. 45 | 46 | Preparation steps: 47 | 1. `docker login -u ` 48 | 2. `make vendor` 49 | 50 | To create preview: 51 | ```bash 52 | make prerelease-patch 53 | # or 54 | make prerelease-minor 55 | # or 56 | make prerelease-major 57 | ``` 58 | 59 | To create final version: 60 | ```bash 61 | make release-patch 62 | # or 63 | make release-minor 64 | # or 65 | make release-major 66 | ``` 67 | 68 | If successful, a new version will be: 69 | 70 | - Built for Mac, Windows & Linux. 71 | - Tagged in GitHub 72 | - Uploaded as GitHub release 73 | - Pushed as Docker image to Docker Hub 74 | - `./VERSION` will be updated to a `+git` version (after the release process) 75 | 76 | If the release process fails, it may leave: 77 | 78 | - `./VERSION` uncommitted. To resolve, checkout `master` or edit it to 79 | the original value and commit to master. 80 | - A git tag named `..` in your repository. 81 | To resolve remove it using `git tag -d ...`. 82 | - A git tag named `..` in this repository in GitHub. 83 | To resolve remove it manually. 84 | 85 | ## Finalizing the release 86 | 87 | After the release has been built (which includes publication) the following 88 | has to be done: 89 | 90 | - Update CHANGELOG.md. Add a new first title (released version -> master). Commit and push it. 91 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.19.11+git -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package main 24 | 25 | import ( 26 | "testing" 27 | 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | func parseDuration(t *testing.T, in, out string) { 32 | t.Run(in, func(t *testing.T) { 33 | v, err := durationParser(in, "s") 34 | require.NoError(t, err) 35 | 36 | s := v.String() 37 | 38 | require.Equal(t, out, s) 39 | }) 40 | } 41 | 42 | func Test_Auth_DurationTest(t *testing.T) { 43 | parseDuration(t, "5", "5s") 44 | parseDuration(t, "5s", "5s") 45 | parseDuration(t, "5m", "5m0s") 46 | parseDuration(t, "5h", "5h0m0s") 47 | } 48 | -------------------------------------------------------------------------------- /certificates.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package main 24 | 25 | import ( 26 | certificates "github.com/arangodb-helper/go-certificates/cli" 27 | ) 28 | 29 | func init() { 30 | certificates.AddCommands(cmdMain, func(err error, msg string) { 31 | if err != nil { 32 | log.Fatal().Err(err).Msg(msg) 33 | } else { 34 | log.Fatal().Msg(msg) 35 | } 36 | }, cmdShowUsage) 37 | } 38 | -------------------------------------------------------------------------------- /client/http.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package client 24 | 25 | import ( 26 | "crypto/tls" 27 | "net" 28 | "net/http" 29 | "time" 30 | ) 31 | 32 | // DefaultHTTPClient creates a new HTTP client configured for accessing a starter. 33 | func DefaultHTTPClient() *http.Client { 34 | return &http.Client{ 35 | Timeout: time.Second * 15, 36 | Transport: &http.Transport{ 37 | Proxy: http.ProxyFromEnvironment, 38 | DialContext: (&net.Dialer{ 39 | Timeout: 30 * time.Second, 40 | KeepAlive: 30 * time.Second, 41 | DualStack: true, 42 | }).DialContext, 43 | MaxIdleConns: 100, 44 | IdleConnTimeout: 90 * time.Second, 45 | TLSHandshakeTimeout: 10 * time.Second, 46 | TLSClientConfig: &tls.Config{ 47 | // It is likely that we'll use self-signed certificates, so disable verification by default. 48 | InsecureSkipVerify: true, 49 | }, 50 | ExpectContinueTimeout: 1 * time.Second, 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/arangod_conf_update_spec.md: -------------------------------------------------------------------------------- 1 | # ArangoDB Config File Update Proposal 2 | 3 | ## Context 4 | 5 | The ArangoDB starter creates config files for all the `arangod` process 6 | it starts. These config files contain various settings such as authentication 7 | and server addresses. 8 | 9 | The user is allowed to manually edit these config files. 10 | 11 | Once the starter has created such a config file, it currently never touches 12 | it again. This means that when you specify something else on the commandline 13 | of the starter, it will not be reflected in the config file. 14 | For example, when you initially run the starter without authentication 15 | enabled and you later run it with authencation enabled, the database 16 | will still run without authentication. 17 | 18 | This causes confusion. 19 | 20 | This proposal intends to change that in a way that: 21 | 22 | - Is less confusing for the user 23 | - Still makes it possible to make manual changes to the config files 24 | 25 | ## Proposed changes 26 | 27 | We propose that the starter adds a hash to each generated config file. 28 | This hash reflects the content of the config file, without any comments or 29 | blank lines or spacing. 30 | 31 | When the starter runs again and finds an existing config file, is re-calculates 32 | a hash based on the current content of the config file and compares it with the hash 33 | stored in the config file. 34 | 35 | If these hashes are the same, the config file has not been modified by the user. 36 | In this case, the starter will update the config file according to its current 37 | commandline options, except for those settings that are immutable. 38 | 39 | When the user tries to change an immutable setting, the starter will yield a warning. 40 | 41 | If the hashes are different, the config file has been modified by the user. 42 | In that case, the starter will not modify the config file, but yield warnings 43 | about all settings in the config file that conflict with the current commandline 44 | options of the starter. 45 | 46 | ## Immutable settings 47 | 48 | The following settings are considered immutable: 49 | 50 | - Storage engine 51 | 52 | ## Hash details 53 | 54 | The hash of a config file is stored in a single line in the config file 55 | that looks like this: 56 | 57 | ```text 58 | # CONTENT-HASH: 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | # ArangoDB Starter Design 2 | 3 | ## Terminology 4 | 5 | - `Bootstrap` the process of creating a cluster (or single server) from scratch. 6 | - `Relaunch` the process to restarting a cluster from an existing (persisted) state. 7 | - `Cluster Configuration` a network of starters that, when all running, form a cluster / single server. 8 | - `Peer` single starter in a cluster configuration. 9 | - `Database Server` an instance of `arangod` configured as single server, agent, dbserver or coordinator. 10 | - `Service` the golang implementation of the starter processes. 11 | 12 | ## Startup 13 | 14 | When the starter starts, it reads all command line options and performs some simple checks for consistency. 15 | It then tries to read a persisted starter configuration `setup.json`. 16 | If that is found and valid, the starter continues with the [Relaunch](#relaunch) process. 17 | If that is not found or completely outdated, the starter continues with the [Bootstrap](#bootstrap) process. 18 | 19 | ## Bootstrap 20 | 21 | This chapter describes the process taken by one or more starters to create a cluster from scratch. 22 | Once the bootstrap process has completed a valid cluster configuration exists and it known by 23 | all involved starters. Database server can now be started (but have not yet been started). 24 | 25 | When bootstrapping, 1 starter acts as the master and all other starters act as slave. 26 | 27 | ### Master 28 | 29 | When the master starts a bootstrapping process it performs the following steps. 30 | 31 | - Create an empty cluster configuration and add itself as peer. 32 | - Start local slaves (when bootstrap configuration tells it to do so). 33 | - Receive join requests (POST `/hello`) from slaves. 34 | 35 | When the cluster configuration has reached a state where enough peers have been added to create 36 | an agency of the intended size, the master continues to the [Running](#running_state). 37 | 38 | ### Slave 39 | 40 | When the slave starts a bootstrapping process it performs the following steps. 41 | 42 | - Send a join request to the master (repeats until success response is received). 43 | - Frequently ask master for current cluster configuration 44 | 45 | When the cluster configuration has reached a state where enough peers have been added to create 46 | an agency of the intended size, the slave continues to the [Running](#running_state). 47 | 48 | ## Relaunch 49 | 50 | This chapter describes the process taken by one or more starters to restart a cluster from an existing persistent state. 51 | 52 | When relaunching all starters are considered the same. There is no master/slave differentiation. 53 | 54 | Each starter loads the last known cluster configuration from `setup.json`. 55 | The information in this setup file is sufficient to: 56 | - know all other starts in the cluster 57 | - know the size of the agency 58 | - know the mode in which the starter will operate (single|cluster) 59 | - know if the starter should start local slaves 60 | - know enough about the authentication settings of the database server to be able to contact them successfully 61 | 62 | All this information is loaded into the fields of the `Service` and then 63 | the starter continues to the [Running](#running_state). 64 | 65 | ## Running state 66 | 67 | This chapter describes the process taken by the starters after they have bootstrapped or relaunched and a cluster configuration exists. 68 | 69 | In the running state, the `Service` will keep starting database servers until a stop of the service has been requested. 70 | The type of database servers that will be starter is found in the cluster 71 | configuration for this specific starter. 72 | For each type of database server that needs to be started, the starter 73 | performs a loop that: 74 | - check if a database server is already running on the intended port, if so, adopt that process 75 | - if no such process it found, it starts the database server 76 | - wait for the database server to terminate 77 | - show error & logs when database server terminates to quickly or too often. 78 | 79 | TODO 80 | - Keep distributing current cluster configuration (design & implement) 81 | 82 | ## Cluster reconfiguration 83 | 84 | ### Adding new peers 85 | 86 | TODO 87 | 88 | ### Removing peers 89 | 90 | TODO 91 | -------------------------------------------------------------------------------- /docs/recovery_spec.md: -------------------------------------------------------------------------------- 1 | # ArangoDB Starter Recovery Procedure 2 | 3 | This procedure is intended to recover a cluster (that was started with the ArangoDB Starter) when a machine 4 | of that cluster is broken without the possibility to recover it (e.g. complete HD failure). 5 | In the procedure is does not matter if a replacement machine uses the old or a new IP address. 6 | 7 | To recover from this, you must first create a new (replacement) machine with ArangoDB (including starter) installed. 8 | Then create a file called `RECOVERY` in the data directory of the starter. 9 | This file must contain the IP address and port of the starter that has been broken (and will be replaced with this new machine). 10 | 11 | E.g. 12 | 13 | ```bash 14 | echo "192.168.1.25:8528" > $DATADIR/RECOVERY 15 | ``` 16 | 17 | After creating the `RECOVERY` file, start the starter using all the normal command line arguments. 18 | 19 | The starter will now: 20 | 1) Talk to the remaining starters to find the ID of the starter it replaces and use that ID to join the remaining starters. 21 | 1) Talk to the remaining agents to find the ID of the agent it replaces and adjust the commandline arguments of the agent (it will start) to use that ID. 22 | This is skipped if the starter was not running an agent. 23 | 1) Remove the `RECOVERY` file from the data directory. 24 | 25 | The cluster will now recover automatically. 26 | It will however have 1 more coordinators & dbservers than expected. 27 | Exactly 1 coordinator and 1 dbserver will be listed "red" in the web UI of the database. 28 | They will have to be removed manually using the web UI of the database. 29 | -------------------------------------------------------------------------------- /examples/local-sync/README.md: -------------------------------------------------------------------------------- 1 | # Run ArangoDB Starter with DC2DC locally 2 | 3 | This example shows how to run an ArangoDB cluster using the Starter locally (twice) with datacenter 4 | to datacenter replication between the 2 clusters. 5 | 6 | Note: This example shares secrets between clusters, so it is NOT suitable for any kind of production use! 7 | 8 | ## Step 1: Create certificates & tokens 9 | 10 | ```bash 11 | export CERTDIR= 12 | export IP= 13 | mkdir -p ${CERTDIR} 14 | 15 | # Create TLS certificates 16 | arangodb create tls ca --cert=${CERTDIR}/tls-ca.crt --key=${CERTDIR}/tls-ca.key 17 | arangodb create tls keyfile --cacert=${CERTDIR}/tls-ca.crt --cakey=${CERTDIR}/tls-ca.key --keyfile=${CERTDIR}/cluster1/tls.keyfile --host=${IP} --host=localhost 18 | arangodb create tls keyfile --cacert=${CERTDIR}/tls-ca.crt --cakey=${CERTDIR}/tls-ca.key --keyfile=${CERTDIR}/cluster2/tls.keyfile --host=${IP} --host=localhost 19 | 20 | # Create client authentication certificates 21 | arangodb create client-auth ca --cert=${CERTDIR}/client-auth-ca.crt --key=${CERTDIR}/client-auth-ca.key 22 | arangodb create client-auth keyfile --cacert=${CERTDIR}/client-auth-ca.crt --cakey=${CERTDIR}/client-auth-ca.key --keyfile=${CERTDIR}/client-auth-ca.keyfile 23 | 24 | # Create JWT secrets 25 | arangodb create jwt-secret --secret=${CERTDIR}/cluster1/syncmaster.jwtsecret 26 | arangodb create jwt-secret --secret=${CERTDIR}/cluster1/arangodb.jwtsecret 27 | arangodb create jwt-secret --secret=${CERTDIR}/cluster2/syncmaster.jwtsecret 28 | arangodb create jwt-secret --secret=${CERTDIR}/cluster2/arangodb.jwtsecret 29 | ``` 30 | 31 | ## Step 2: Start first & second cluster 32 | 33 | ```bash 34 | export DATADIR= 35 | mkdir -p ${DATADIR} 36 | 37 | arangodb --starter.data-dir=/${DATADIR}/cluster1 \ 38 | --starter.sync \ 39 | --starter.local \ 40 | --auth.jwt-secret=${CERTDIR}/cluster1/arangodb.jwtsecret \ 41 | --sync.server.keyfile=${CERTDIR}/cluster1/tls.keyfile \ 42 | --sync.server.client-cafile=${CERTDIR}/client-auth-ca.crt \ 43 | --sync.master.jwt-secret=${CERTDIR}/cluster1/syncmaster.jwtsecret \ 44 | --starter.address=${IP} 45 | 46 | ## In another terminal 47 | export DATADIR= 48 | 49 | arangodb --starter.data-dir=/${DATADIR}/cluster2 \ 50 | --starter.sync \ 51 | --starter.local \ 52 | --auth.jwt-secret=${CERTDIR}/cluster2/arangodb.jwtsecret \ 53 | --sync.server.keyfile=${CERTDIR}/cluster2/tls.keyfile \ 54 | --sync.server.client-cafile=${CERTDIR}/client-auth-ca.crt \ 55 | --sync.master.jwt-secret=${CERTDIR}/cluster2/syncmaster.jwtsecret \ 56 | --starter.address=${IP} \ 57 | --starter.port=9528 58 | ``` 59 | 60 | Note that it is not uncommon for a syncmaster to restart, since the cluster is not yet ready when it is started. 61 | 62 | ## Step 3: Configure synchronization from cluster 1 to cluster 2 63 | 64 | ```bash 65 | arangosync configure sync \ 66 | --master.endpoint=https://${IP}:9542 \ 67 | --master.keyfile=${CERTDIR}/client-auth-ca.keyfile \ 68 | --source.endpoint=https://${IP}:8542 \ 69 | --source.cacert=${CERTDIR}/tls-ca.crt \ 70 | --auth.keyfile=${CERTDIR}/client-auth-ca.keyfile 71 | ``` 72 | 73 | ## Step 4: Check status of configuration 74 | 75 | ```bash 76 | # Check status of cluster 1 77 | arangosync get status \ 78 | --master.endpoint=https://${IP}:8542 \ 79 | --auth.keyfile=${CERTDIR}/client-auth-ca.keyfile \ 80 | --verbose 81 | 82 | # Check status of cluster 2 83 | arangosync get status \ 84 | --master.endpoint=https://${IP}:9542 \ 85 | --auth.keyfile=${CERTDIR}/client-auth-ca.keyfile \ 86 | --verbose 87 | ``` 88 | 89 | ## Step 5: Check the status of synchronization for each shard 90 | 91 | Target DC endpoint should be specified: 92 | ``` 93 | arangosync check sync \ 94 | --master.endpoint=https://${IP}:9542 \ 95 | --auth.keyfile=${CERTDIR}/client-auth-ca.keyfile \ 96 | --verbose 97 | ``` 98 | -------------------------------------------------------------------------------- /examples/mac_on_docker_cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This exampe shows how to run an ArangoDB cluster all locally in docker on mac. 4 | # 5 | # By default this script uses the latest released ArangoDB docker image. 6 | # To use another image, set ARANGOIMAGE to the desired image, before calling this script. 7 | # 8 | # Note: This script does not use a volume mapping, so data is lost after stopping the starter! 9 | # If you want to persist data, add a volume mapping for /data. 10 | # 11 | 12 | NSCONTAINER=arangodb-on-mac-ns 13 | STARTERCONTAINER=arangodb-on-mac 14 | ARANGOIMAGE=${ARANGOIMAGE:=arangodb/arangodb:latest} 15 | 16 | # Create network namespace container. 17 | # Make sure to expose all ports you want here. 18 | docker run -d --name=${NSCONTAINER} \ 19 | -p 8528:8528 -p 8529:8529 -p 8534:8534 -p 8539:8539 \ 20 | alpine:latest sleep 630720000 21 | 22 | # Run the starter in a container using the network namespace of ${NSCONTAINER} 23 | docker run -it --name=${STARTERCONTAINER} --rm \ 24 | --net=container:${NSCONTAINER} \ 25 | -v /var/run/docker.sock:/var/run/docker.sock \ 26 | arangodb/arangodb-starter \ 27 | --docker.image=${ARANGOIMAGE} \ 28 | --starter.address=localhost \ 29 | --starter.local 30 | 31 | # Remove namespace container 32 | docker rm -vf ${NSCONTAINER} -------------------------------------------------------------------------------- /examples/systemd/README.md: -------------------------------------------------------------------------------- 1 | # Run ArangoDB Starter with SystemD 2 | 3 | This example shows how you can run the ArangoDB starter in a systemd service. 4 | 5 | The example will run the starter as user `arangodb` in group `arangodb`. 6 | It requires an environment variable file called `/etc/arangodb.env` 7 | with the following variables. 8 | 9 | ```text 10 | STARTERENDPOINTS= 11 | CLUSTERSECRET= 12 | CLUSTERSECRETPATH= 13 | ``` 14 | 15 | To use this service, do the following on every machine on which the starter should run: 16 | 17 | - Create the `/etc/arangodb.env` environment file as specified above. 18 | - Copy the `arangodb.service` file to `/etc/systemd/system/arangodb.service`. 19 | - Trigger a systemd reload using `sudo systemctl daemon-reload`. 20 | - Enable the service using `sudo systemctl enable arangodb.service`. 21 | - Start the service using `sudo systemctl start arangodb.service`. 22 | 23 | Once started, you can view the logs of the starter using: 24 | 25 | ```bash 26 | sudo journalctl -flu arangodb.service 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/systemd/arangodb.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Run the ArangoDB Starter 3 | After=network.target 4 | 5 | [Service] 6 | Restart=on-failure 7 | User=arangodb 8 | Group=arangodb 9 | EnvironmentFile=/etc/arangodb.env 10 | Environment=DATADIR=/var/lib/arangodb/cluster 11 | ExecStartPre=/usr/bin/sh -c "mkdir -p $(dirname $CLUSTERSECRETPATH)" 12 | ExecStartPre=/usr/bin/sh -c "mkdir -p ${DATADIR}" 13 | ExecStartPre=/usr/bin/sh -c "echo ${CLUSTERSECRET} > ${CLUSTERSECRETPATH}" 14 | ExecStart=/usr/bin/arangodb \ 15 | --log.file=false \ 16 | --starter.data-dir=${DATADIR} \ 17 | --starter.join=${STARTERENDPOINTS} \ 18 | --server.storage-engine=rocksdb \ 19 | --auth.jwt-secret=${CLUSTERSECRETPATH} \ 20 | --all.log.level=info --all.log.output=+ 21 | TimeoutStopSec=60 22 | 23 | [Install] 24 | WantedBy=multi-user.target 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arangodb-helper/arangodb 2 | 3 | go 1.23.8 4 | 5 | require ( 6 | github.com/arangodb-helper/go-certificates v0.0.0-20180821055445-9fca24fc2680 7 | github.com/arangodb-helper/go-helper v0.4.2 8 | github.com/arangodb/go-driver v1.6.6 9 | github.com/arangodb/go-upgrade-rules v0.0.0-20200605091205-439fb1ee86e7 10 | github.com/cenkalti/backoff v2.2.1+incompatible 11 | github.com/coreos/go-semver v0.3.1 12 | github.com/dchest/uniuri v1.2.0 13 | github.com/fatih/color v1.9.0 14 | github.com/fsouza/go-dockerclient v1.9.7 15 | github.com/golang-jwt/jwt/v5 v5.2.2 16 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 17 | github.com/mitchellh/go-homedir v1.1.0 18 | github.com/opencontainers/runc v1.1.14 19 | github.com/pkg/errors v0.9.1 20 | github.com/rs/zerolog v1.33.0 21 | github.com/ryanuber/columnize v2.1.0+incompatible 22 | github.com/spf13/cobra v1.0.0 23 | github.com/spf13/pflag v1.0.5 24 | github.com/stretchr/testify v1.9.0 25 | golang.org/x/crypto v0.35.0 26 | gopkg.in/ini.v1 v1.66.6 27 | ) 28 | 29 | require ( 30 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 31 | github.com/Microsoft/go-winio v0.6.2 // indirect 32 | github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect 33 | github.com/containerd/containerd v1.7.27 // indirect 34 | github.com/containerd/log v0.1.0 // indirect 35 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 36 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 37 | github.com/davecgh/go-spew v1.1.1 // indirect 38 | github.com/docker/docker v25.0.6+incompatible // indirect 39 | github.com/docker/go-connections v0.4.0 // indirect 40 | github.com/docker/go-units v0.5.0 // indirect 41 | github.com/godbus/dbus/v5 v5.1.0 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 44 | github.com/klauspost/compress v1.16.7 // indirect 45 | github.com/kr/text v0.2.0 // indirect 46 | github.com/mattn/go-colorable v0.1.13 // indirect 47 | github.com/mattn/go-isatty v0.0.20 // indirect 48 | github.com/moby/patternmatcher v0.5.0 // indirect 49 | github.com/moby/sys/mountinfo v0.6.2 // indirect 50 | github.com/moby/sys/sequential v0.5.0 // indirect 51 | github.com/moby/sys/user v0.3.0 // indirect 52 | github.com/moby/sys/userns v0.1.0 // indirect 53 | github.com/moby/term v0.5.0 // indirect 54 | github.com/morikuni/aec v1.0.0 // indirect 55 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 56 | github.com/opencontainers/go-digest v1.0.0 // indirect 57 | github.com/opencontainers/image-spec v1.1.0 // indirect 58 | github.com/opencontainers/runtime-spec v1.1.0 // indirect 59 | github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible // indirect 60 | github.com/pmezard/go-difflib v1.0.0 // indirect 61 | github.com/sirupsen/logrus v1.9.3 // indirect 62 | golang.org/x/sys v0.31.0 // indirect 63 | golang.org/x/term v0.30.0 // indirect 64 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 65 | gopkg.in/yaml.v3 v3.0.1 // indirect 66 | gotest.tools/v3 v3.4.0 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /help.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | "strings" 27 | 28 | "github.com/fatih/color" 29 | ) 30 | 31 | // --docker.container missing 32 | func showDockerContainerNameMissingHelp() { 33 | showFatalHelp( 34 | "Cannot find docker container name.", 35 | "", 36 | "How to solve this:", 37 | "1 - Add a commandline argument:", 38 | "", 39 | " `arangodb ... --docker.container=`", 40 | "", 41 | ) 42 | } 43 | 44 | // --cluster.agency-size invalid. 45 | func showClusterAgencySizeInvalidHelp() { 46 | showFatalHelp( 47 | "Cluster agency size is invalid cluster.agency-size needs to be a positive, odd number.", 48 | "", 49 | "How to solve this:", 50 | "1 - Use a positive, odd number as agency size:", 51 | "", 52 | " `arangodb ... --cluster.agency-size=<1, 3 or 5...>`", 53 | "", 54 | ) 55 | } 56 | 57 | // --cluster.agency-size=1 without specifying --starter.address. 58 | func showClusterAgencySize1WithoutAddressHelp() { 59 | showFatalHelp( 60 | "With a cluster agency size of 1, a starter address is required.", 61 | "", 62 | "How to solve this:", 63 | "1 - Add a commandline argument:", 64 | "", 65 | " `arangodb ... --starter.address=`", 66 | "", 67 | ) 68 | } 69 | 70 | // setting both --docker.image and --server.rr is not possible. 71 | func showDockerImageWithRRIsNotAllowedHelp() { 72 | showFatalHelp( 73 | "Using RR is not possible with docker.", 74 | "", 75 | "How to solve this:", 76 | "1 - Remove `--server.rr=...` commandline argument.", 77 | "", 78 | ) 79 | } 80 | 81 | // setting both --docker.net-host and --docker.net-mode is not possible 82 | func showDockerNetHostAndNotModeNotBothAllowedHelp() { 83 | showFatalHelp( 84 | "It is not allowed to set `--docker.net-host` and `--docker.net-mode` at the same time.", 85 | "", 86 | "How to solve this:", 87 | "1 - Remove one of these two commandline arguments.", 88 | "", 89 | ) 90 | } 91 | 92 | // Arangod is not found at given path. 93 | func showArangodExecutableNotFoundHelp(arangodPath string) { 94 | showFatalHelp( 95 | fmt.Sprintf("Cannot find `arangod` (expected at `%s`).", arangodPath), 96 | "", 97 | "How to solve this:", 98 | "1 - Install ArangoDB locally or run the ArangoDB starter in docker. (see README for details).", 99 | "", 100 | ) 101 | } 102 | 103 | // cannnot specify both `--ssl.auto-key` and `--ssl.keyfile` 104 | func showSslAutoKeyAndKeyFileNotBothAllowedHelp() { 105 | showFatalHelp( 106 | "Specifying both `--ssl.auto-key` and `--ssl.keyfile` is not allowed.", 107 | "", 108 | "How to solve this:", 109 | "1 - Remove one of these two commandline arguments.", 110 | "", 111 | ) 112 | } 113 | 114 | // showFatalHelp logs a title and prints additional usages 115 | // underneeth and the exit with code 1. 116 | // Backticks in the lines are colored yellow. 117 | func showFatalHelp(title string, lines ...string) { 118 | log.Error().Msg(highlight(title)) 119 | content := strings.Join(lines, "\n") 120 | fmt.Println(highlight(content)) 121 | os.Exit(1) 122 | } 123 | 124 | func highlight(content string) string { 125 | parts := strings.Split(content, "`") 126 | for i, p := range parts { 127 | if i%2 == 1 { 128 | parts[i] = color.YellowString(p) 129 | } 130 | } 131 | return strings.Join(parts, "") 132 | } 133 | -------------------------------------------------------------------------------- /pkg/api/empty.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package api 24 | 25 | type Empty struct { 26 | *Error `json:",inline"` 27 | } 28 | -------------------------------------------------------------------------------- /pkg/api/error.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package api 24 | 25 | type Error struct { 26 | Error string `json:"Error,omitempty"` 27 | } 28 | 29 | func (e *Error) String() string { 30 | if e == nil { 31 | return "" 32 | } 33 | 34 | return e.Error 35 | } 36 | 37 | func NewError(err error) *Error { 38 | if err == nil { 39 | return nil 40 | } 41 | 42 | return &Error{ 43 | Error: err.Error(), 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/api/inventory.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package api 24 | 25 | import ( 26 | "github.com/arangodb/go-driver" 27 | 28 | "github.com/arangodb-helper/arangodb/pkg/definitions" 29 | client "github.com/arangodb-helper/arangodb/service/clients" 30 | ) 31 | 32 | type ClusterInventory struct { 33 | Peers map[string]Inventory `json:"peers,omitempty"` 34 | 35 | *Error `json:",inline"` 36 | } 37 | 38 | type Inventory struct { 39 | Members map[definitions.ServerType]MemberInventory `json:"members,omitempty"` 40 | 41 | *Error `json:",inline"` 42 | } 43 | 44 | type MemberInventory struct { 45 | Version driver.VersionInfo `json:"version"` 46 | 47 | Hashes *MemberHashes `json:"hashes,omitempty"` 48 | 49 | *Error `json:",inline"` 50 | } 51 | 52 | type MemberHashes struct { 53 | JWT client.JWTDetailsResult `json:"jwt"` 54 | } 55 | -------------------------------------------------------------------------------- /pkg/arangodb/executable.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package arangodb 22 | 23 | import ( 24 | "os" 25 | "path/filepath" 26 | "runtime" 27 | "sort" 28 | "strings" 29 | 30 | "github.com/rs/zerolog" 31 | ) 32 | 33 | // FindExecutable uses a platform dependent approach to find an executable 34 | // with given process name. 35 | func FindExecutable(log zerolog.Logger, processName, defaultPath string, useLocalBin bool) (executablePath string, isBuild bool) { 36 | var localPaths []string 37 | if exePath, err := os.Executable(); err == nil { 38 | folder := filepath.Dir(exePath) 39 | localPaths = append(localPaths, filepath.Join(folder, processName+filepath.Ext(exePath))) 40 | 41 | // Also try searching in ../sbin in case if we are running from local installation 42 | if runtime.GOOS != "windows" { 43 | localPaths = append(localPaths, filepath.Join(folder, "../sbin", processName+filepath.Ext(exePath))) 44 | } 45 | } 46 | 47 | var pathList []string 48 | switch runtime.GOOS { 49 | case "windows": 50 | // Look in the default installation location: 51 | foundPaths := make([]string, 0, 20) 52 | basePath := "C:/Program Files" 53 | d, e := os.Open(basePath) 54 | if e == nil { 55 | l, e := d.Readdir(1024) 56 | if e == nil { 57 | for _, n := range l { 58 | if n.IsDir() { 59 | name := n.Name() 60 | if strings.HasPrefix(name, "ArangoDB3 ") || 61 | strings.HasPrefix(name, "ArangoDB3e ") { 62 | foundPaths = append(foundPaths, basePath+"/"+name+ 63 | "/usr/bin/"+processName+".exe") 64 | } 65 | } 66 | } 67 | } else { 68 | log.Error().Msgf("Could not read directory %s to look for executable.", basePath) 69 | } 70 | d.Close() 71 | } else { 72 | log.Error().Msgf("Could not open directory %s to look for executable.", basePath) 73 | } 74 | sort.Sort(sort.Reverse(sort.StringSlice(foundPaths))) 75 | pathList = append(pathList, foundPaths...) 76 | case "darwin": 77 | pathList = append(pathList, 78 | "/Applications/ArangoDB3-CLI.app/Contents/MacOS/usr/sbin/"+processName, 79 | "/usr/local/opt/arangodb/sbin/"+processName, 80 | ) 81 | case "linux": 82 | pathList = append(pathList, 83 | "/usr/sbin/"+processName, 84 | "/usr/local/sbin/"+processName, 85 | ) 86 | } 87 | 88 | if useLocalBin { 89 | pathList = append(localPaths, pathList...) 90 | } else { 91 | pathList = append(pathList, localPaths...) 92 | } 93 | 94 | // buildPath should be always first on the list 95 | buildPath := "build/bin/" + processName 96 | pathList = append([]string{buildPath}, pathList...) 97 | 98 | // Search for the first path that exists. 99 | for _, p := range pathList { 100 | if _, e := os.Stat(filepath.Clean(filepath.FromSlash(p))); e == nil || !os.IsNotExist(e) { 101 | executablePath, _ = filepath.Abs(filepath.FromSlash(p)) 102 | isBuild = p == buildPath 103 | return 104 | } 105 | } 106 | 107 | return defaultPath, false 108 | } 109 | -------------------------------------------------------------------------------- /pkg/definitions/common.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package definitions 22 | 23 | const ( 24 | PortOffsetUnknown = 0 // Coordinator/single server 25 | PortOffsetCoordinator = 1 // Coordinator/single server 26 | PortOffsetDBServer = 2 27 | PortOffsetAgent = 3 28 | PortOffsetIncrementOld = 5 // {our http server, agent, coordinator, dbserver, reserved} 29 | PortOffsetIncrementNew = 10 // {our http server, agent, coordinator, dbserver, syncmaster, syncworker, reserved...} 30 | ) 31 | 32 | const ( 33 | MaxRecentFailures = 100 // Maximum number of recent failures before the starter gives up. 34 | ) 35 | 36 | const ( 37 | ArangodConfFileName = "arangod.conf" 38 | ArangodJWTSecretFileName = "arangod.jwtsecret" 39 | ArangodJWTSecretFolderName = "jwt" 40 | ArangodJWTSecretActive = "-" 41 | ) 42 | -------------------------------------------------------------------------------- /pkg/definitions/exitcodes.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package definitions 22 | 23 | func GetExitCodeReason(code int) string { 24 | if r, ok := arangoDExitReason[code]; ok { 25 | return r 26 | } 27 | return "reason unknown" 28 | } 29 | 30 | // ExitCodeIsRecoverable returns true if provided code indicates that restarting ArangoD process with exact same config won't help 31 | func ExitCodeIsRecoverable(pType ProcessType, code int) bool { 32 | switch code { 33 | case ArangoDExitBinaryNotFound, 34 | ArangoDExitConfigNotFound, 35 | ArangoDExitInvalidOptionName, 36 | ArangoDExitInvalidOptionValue, 37 | ArangoDExitIcuInitializationFailed, 38 | ArangoDExitTzdataInitializationFailed, 39 | ArangoDExitResourcesTooLow, 40 | ArangoDExitDbNotEmpty, 41 | ArangoDExitUnsupportedStorageEngine: 42 | return false 43 | } 44 | return true 45 | } 46 | -------------------------------------------------------------------------------- /pkg/definitions/process_type.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package definitions 22 | 23 | import "fmt" 24 | 25 | // ProcessType specifies the types of process (the starter can work with). 26 | type ProcessType string 27 | 28 | const ( 29 | ProcessTypeArangod ProcessType = "arangod" 30 | ) 31 | 32 | // String returns a string representation of the given ProcessType. 33 | func (s ProcessType) String() string { 34 | return string(s) 35 | } 36 | 37 | // CommandFileName returns the name of a file containing the full command for processes 38 | // of this type. 39 | func (s ProcessType) CommandFileName() string { 40 | switch s { 41 | case ProcessTypeArangod: 42 | return "arangod_command.txt" 43 | default: 44 | return "" 45 | } 46 | } 47 | 48 | // LogFileName returns the name of the log file used by this process 49 | func (s ProcessType) LogFileName(suffix string) string { 50 | switch s { 51 | case ProcessTypeArangod: 52 | return fmt.Sprintf("arangod%s.log", suffix) 53 | default: 54 | return "" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pkg/definitions/server_type.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package definitions 22 | 23 | import ( 24 | "fmt" 25 | "time" 26 | ) 27 | 28 | // ServerType specifies the types of database servers. 29 | type ServerType string 30 | 31 | const ( 32 | ServerTypeUnknown = "unknown" 33 | ServerTypeCoordinator = "coordinator" 34 | ServerTypeDBServer = "dbserver" 35 | ServerTypeDBServerNoResign = "dbserver_noresign" 36 | ServerTypeAgent = "agent" 37 | ServerTypeSingle = "single" 38 | ) 39 | 40 | // String returns a string representation of the given ServerType. 41 | func (s ServerType) String() string { 42 | return string(s) 43 | } 44 | 45 | // PortOffset returns the offset from a peer base port for the given type of server. 46 | func (s ServerType) PortOffset() int { 47 | switch s { 48 | case ServerTypeCoordinator, ServerTypeSingle: 49 | return PortOffsetCoordinator 50 | case ServerTypeDBServer, ServerTypeDBServerNoResign: 51 | return PortOffsetDBServer 52 | case ServerTypeAgent: 53 | return PortOffsetAgent 54 | default: 55 | panic(fmt.Sprintf("Unknown ServerType: %s", string(s))) 56 | } 57 | } 58 | 59 | // InitialStopTimeout returns initial delay for process stopping 60 | func (s ServerType) InitialStopTimeout() time.Duration { 61 | switch s { 62 | case ServerTypeDBServer, ServerTypeSingle: 63 | return 3 * time.Second 64 | default: 65 | return time.Second 66 | } 67 | } 68 | 69 | // ProcessType returns the type of process needed to run a server of given type. 70 | func (s ServerType) ProcessType() ProcessType { 71 | switch s { 72 | default: 73 | return ProcessTypeArangod 74 | } 75 | } 76 | 77 | // ExpectedServerRole returns the expected `role` & `mode` value resulting from a request to `_admin/server/role`. 78 | func (s ServerType) ExpectedServerRole() (string, string) { 79 | switch s { 80 | case ServerTypeCoordinator: 81 | return "COORDINATOR", "" 82 | case ServerTypeSingle: 83 | return "SINGLE", "" 84 | case ServerTypeDBServer, ServerTypeDBServerNoResign: 85 | return "PRIMARY", "" 86 | case ServerTypeAgent: 87 | return "AGENT", "" 88 | default: 89 | panic(fmt.Sprintf("Unknown ServerType: %s", string(s))) 90 | } 91 | } 92 | 93 | // GetName returns readable name for the specific type of server. 94 | func (s ServerType) GetName() string { 95 | switch s { 96 | case ServerTypeAgent: 97 | return "agent" 98 | case ServerTypeDBServer: 99 | return "dbserver" 100 | case ServerTypeDBServerNoResign: 101 | return "dbserver" 102 | case ServerTypeCoordinator: 103 | return "coordinator" 104 | case ServerTypeSingle: 105 | return "single server" 106 | } 107 | 108 | return "" 109 | } 110 | 111 | func AllServerTypes() []ServerType { 112 | return []ServerType{ 113 | ServerTypeCoordinator, 114 | ServerTypeDBServer, 115 | ServerTypeDBServerNoResign, 116 | ServerTypeAgent, 117 | ServerTypeSingle, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pkg/docker/cgroups_nonunix.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | //go:build !linux && !freebsd && !openbsd && !netbsd && !solaris 22 | // +build !linux,!freebsd,!openbsd,!netbsd,!solaris 23 | 24 | package docker 25 | 26 | func IsCGroup2UnifiedMode() bool { 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /pkg/docker/cgroups_unix.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | //go:build linux || freebsd || openbsd || netbsd || solaris 22 | // +build linux freebsd openbsd netbsd solaris 23 | 24 | package docker 25 | 26 | import "github.com/opencontainers/runc/libcontainer/cgroups" 27 | 28 | func IsCGroup2UnifiedMode() bool { 29 | return cgroups.IsCgroup2UnifiedMode() 30 | } 31 | -------------------------------------------------------------------------------- /pkg/docker/docker.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package docker 24 | 25 | import ( 26 | "fmt" 27 | "io/ioutil" 28 | "os" 29 | "strings" 30 | 31 | docker "github.com/fsouza/go-dockerclient" 32 | "github.com/pkg/errors" 33 | ) 34 | 35 | const ( 36 | cgroupDockerMarker = ":/docker/" 37 | cgroupDockerCEMarker = ":/docker-ce/docker/" 38 | ) 39 | 40 | type ContainerInfo struct { 41 | Name string 42 | ImageName string 43 | } 44 | 45 | // IsRunningInDocker checks if the process is running in a docker container. 46 | func IsRunningInDocker() bool { 47 | if os.Getenv("RUNNING_IN_DOCKER") != "true" { 48 | return false 49 | } 50 | if _, err := os.Stat("/.dockerenv"); os.IsNotExist(err) { 51 | return false 52 | } 53 | return true 54 | } 55 | 56 | // FindDockerContainerInfo find information (name or if not possible the ID, image-name) of the container that is used to run this process. 57 | func FindDockerContainerInfo(dockerEndpoint string) (ContainerInfo, error) { 58 | id, err := findRunningContainerID() 59 | if err != nil { 60 | // Determining the container ID in unified-hierarchy mode is impossible. 61 | // Falling back to hostname if available 62 | id = os.Getenv("HOSTNAME") 63 | if !IsCGroup2UnifiedMode() || id == "" { 64 | return ContainerInfo{}, errors.WithMessagef(err, "hostname env val %s", id) 65 | } 66 | } 67 | info := ContainerInfo{Name: id} 68 | 69 | // Find name for container with ID. 70 | client, err := docker.NewClient(dockerEndpoint) 71 | if err != nil { 72 | return info, nil 73 | } 74 | container, err := client.InspectContainer(id) 75 | if err != nil { 76 | return info, nil 77 | } 78 | 79 | info.Name = container.ID 80 | if container.Name != "" { 81 | info.Name = container.Name 82 | } 83 | info.ImageName = container.Image 84 | 85 | // Try to inspect image for more naming info 86 | image, err := client.InspectImage(container.Image) 87 | if err == nil && len(image.RepoTags) > 0 { 88 | info.ImageName = image.RepoTags[0] 89 | } 90 | 91 | return info, nil 92 | } 93 | 94 | func findRunningContainerID() (string, error) { 95 | raw, err := ioutil.ReadFile("/proc/self/cgroup") 96 | if err != nil { 97 | return "", errors.WithStack(err) 98 | } 99 | lines := strings.Split(string(raw), "\n") 100 | for _, line := range lines { 101 | if i := strings.Index(line, cgroupDockerCEMarker); i > 0 { 102 | id := strings.TrimSpace(line[i+len(cgroupDockerCEMarker):]) 103 | if id != "" { 104 | return id, nil 105 | } 106 | } else if i := strings.Index(line, cgroupDockerMarker); i > 0 { 107 | id := strings.TrimSpace(line[i+len(cgroupDockerMarker):]) 108 | if id != "" { 109 | return id, nil 110 | } 111 | } 112 | } 113 | return "", errors.WithStack(fmt.Errorf("cannot find docker marker")) 114 | } 115 | -------------------------------------------------------------------------------- /pkg/features/feature.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package features 24 | 25 | import ( 26 | "fmt" 27 | 28 | flag "github.com/spf13/pflag" 29 | 30 | "github.com/arangodb/go-driver" 31 | ) 32 | 33 | type Version struct { 34 | Enterprise bool 35 | Version driver.Version 36 | } 37 | 38 | type Feature interface { 39 | Register(i *flag.FlagSet) error 40 | RegisterDeprecated(i *flag.FlagSet) error 41 | 42 | Enabled(v Version) bool 43 | } 44 | 45 | func NewFeature(name, description string, enabled bool, check func(v Version) bool) Feature { 46 | return &feature{ 47 | name: name, 48 | description: description, 49 | enabled: enabled, 50 | check: check, 51 | } 52 | } 53 | 54 | type feature struct { 55 | name, description string 56 | enabled bool 57 | check func(v Version) bool 58 | } 59 | 60 | func (f *feature) flagName() string { 61 | return fmt.Sprintf("feature.%s", f.name) 62 | } 63 | 64 | func (f *feature) Register(i *flag.FlagSet) error { 65 | i.BoolVar(&f.enabled, f.flagName(), f.enabled, f.description) 66 | 67 | return nil 68 | } 69 | 70 | func (f *feature) RegisterDeprecated(i *flag.FlagSet) error { 71 | err := f.Register(i) 72 | if err != nil { 73 | return err 74 | } 75 | return i.MarkDeprecated(f.flagName(), "Deprecated. Providing any value to this flag will not affect starter behaviour.") 76 | } 77 | 78 | func (f feature) Enabled(v Version) bool { 79 | return f.check(v) 80 | } 81 | -------------------------------------------------------------------------------- /pkg/features/jwt.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package features 24 | 25 | var ( 26 | jwtRotation = NewFeature("jwt.rotation", "Enable JWT rotation and folder support", false, func(v Version) bool { 27 | return v.Enterprise && v.Version.CompareTo("3.7.0") >= 0 28 | }) 29 | ) 30 | 31 | func JWTRotation() Feature { 32 | return jwtRotation 33 | } 34 | -------------------------------------------------------------------------------- /pkg/logging/adapter.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2023 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package logging 22 | 23 | import ( 24 | "io" 25 | goLog "log" 26 | "strings" 27 | 28 | "github.com/rs/zerolog" 29 | ) 30 | 31 | // logAdapter is used as an adapter for standard go logged 32 | type logAdapter zerolog.Logger 33 | 34 | var ( 35 | _ io.Writer = logAdapter{} 36 | ) 37 | 38 | func (l logAdapter) Write(p []byte) (int, error) { 39 | line := strings.TrimSpace(string(p)) 40 | if !strings.Contains(line, "TLS handshake error") { 41 | log := zerolog.Logger(l) 42 | log.Error().Msg(line) 43 | } 44 | return len(p), nil 45 | } 46 | 47 | func NewAdapter(l zerolog.Logger) *goLog.Logger { 48 | return goLog.New(logAdapter(l), "", 0) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/logging/context.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package logging 24 | 25 | import ( 26 | "context" 27 | 28 | "github.com/rs/zerolog" 29 | ) 30 | 31 | type ContextProvider func(ctx context.Context, c zerolog.Context) zerolog.Context 32 | 33 | var ( 34 | contextProviders []ContextProvider 35 | ) 36 | 37 | // RegisterContextProvider records a given context provider. 38 | // This method is supposed to be called in an `init` method. 39 | func RegisterContextProvider(p ContextProvider) { 40 | contextProviders = append(contextProviders, p) 41 | } 42 | 43 | // With creates a zerolog Context that contains all information available in the 44 | // given context. 45 | func With(ctx context.Context, log zerolog.Logger) zerolog.Context { 46 | c := log.With() 47 | for _, p := range contextProviders { 48 | c = p(ctx, c) 49 | } 50 | return c 51 | } 52 | -------------------------------------------------------------------------------- /pkg/logging/error.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package logging 24 | 25 | import "github.com/pkg/errors" 26 | 27 | var ( 28 | maskAny = errors.WithStack 29 | ) 30 | -------------------------------------------------------------------------------- /pkg/logging/rotating_writer.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package logging 24 | 25 | import ( 26 | "io" 27 | "os" 28 | "sync" 29 | ) 30 | 31 | type rotatingWriter struct { 32 | mutex sync.RWMutex 33 | path string 34 | f *os.File 35 | } 36 | 37 | // newRotatingWriter creates a new rotating writer. 38 | func newRotatingWriter(path string) (*rotatingWriter, error) { 39 | r := &rotatingWriter{path: path} 40 | if err := r.Rotate(); err != nil { 41 | return nil, maskAny(err) 42 | } 43 | return r, nil 44 | } 45 | 46 | var ( 47 | _ io.Writer = &rotatingWriter{} 48 | ) 49 | 50 | // Close the writer 51 | func (w *rotatingWriter) Close() error { 52 | w.mutex.Lock() 53 | defer w.mutex.Unlock() 54 | 55 | if w.f != nil { 56 | if err := w.f.Close(); err != nil { 57 | return maskAny(err) 58 | } 59 | w.f = nil 60 | } 61 | return nil 62 | } 63 | 64 | // Write to the writer 65 | func (w *rotatingWriter) Write(p []byte) (n int, err error) { 66 | w.mutex.RLock() 67 | defer w.mutex.RUnlock() 68 | 69 | if w.f != nil { 70 | return w.f.Write(p) 71 | } 72 | return 0, nil 73 | } 74 | 75 | // Rotate closes the writer and re-opens it. 76 | func (w *rotatingWriter) Rotate() error { 77 | if err := w.Close(); err != nil { 78 | return maskAny(err) 79 | } 80 | 81 | // Claim lock 82 | w.mutex.Lock() 83 | defer w.mutex.Unlock() 84 | 85 | newF, err := os.OpenFile(w.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 86 | if err != nil { 87 | return maskAny(err) 88 | } 89 | w.f = newF 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /pkg/net/ipv6.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | //go:build !linux 24 | // +build !linux 25 | 26 | package net 27 | 28 | // IsIPv6Supported returns true when IPv6 support is available, 29 | // false when it is not. 30 | // Note that it is not possible on all platforms to properly 31 | // detect IPv6 support. 32 | func IsIPv6Supported() bool { 33 | return true 34 | } 35 | -------------------------------------------------------------------------------- /pkg/net/ipv6_linux.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package net 24 | 25 | import "os" 26 | 27 | // IsIPv6Supported returns true when IPv6 support is available, 28 | // false when it is not. 29 | // Note that it is not possible on all platforms to properly 30 | // detect IPv6 support. 31 | func IsIPv6Supported() bool { 32 | if _, err := os.Stat("/proc/net/if_inet6"); err == nil { 33 | return true 34 | } 35 | return false 36 | } 37 | -------------------------------------------------------------------------------- /pkg/terminal/terminal.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 3 | // 4 | // The Programs (which include both the software and documentation) contain 5 | // proprietary information of ArangoDB GmbH; they are provided under a license 6 | // agreement containing restrictions on use and disclosure and are also 7 | // protected by copyright, patent and other intellectual and industrial 8 | // property laws. Reverse engineering, disassembly or decompilation of the 9 | // Programs, except to the extent required to obtain interoperability with 10 | // other independently created software or as specified by law, is prohibited. 11 | // 12 | // It shall be the licensee's responsibility to take all appropriate fail-safe, 13 | // backup, redundancy, and other measures to ensure the safe use of 14 | // applications if the Programs are used for purposes such as nuclear, 15 | // aviation, mass transit, medical, or other inherently dangerous applications, 16 | // and ArangoDB GmbH disclaims liability for any damages caused by such use of 17 | // the Programs. 18 | // 19 | // This software is the confidential and proprietary information of ArangoDB 20 | // GmbH. You shall not disclose such confidential and proprietary information 21 | // and shall use it only in accordance with the terms of the license agreement 22 | // you entered into with ArangoDB GmbH. 23 | // 24 | // Author Ewout Prangsma 25 | // 26 | 27 | //go:build !windows 28 | // +build !windows 29 | 30 | package terminal 31 | 32 | import ( 33 | "syscall" 34 | 35 | ssht "golang.org/x/crypto/ssh/terminal" 36 | ) 37 | 38 | // IsTerminal returns true when the process has an attached 39 | // terminal. 40 | func IsTerminal() bool { 41 | return ssht.IsTerminal(syscall.Stdin) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/terminal/terminal_windows.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 3 | // 4 | // The Programs (which include both the software and documentation) contain 5 | // proprietary information of ArangoDB GmbH; they are provided under a license 6 | // agreement containing restrictions on use and disclosure and are also 7 | // protected by copyright, patent and other intellectual and industrial 8 | // property laws. Reverse engineering, disassembly or decompilation of the 9 | // Programs, except to the extent required to obtain interoperability with 10 | // other independently created software or as specified by law, is prohibited. 11 | // 12 | // It shall be the licensee's responsibility to take all appropriate fail-safe, 13 | // backup, redundancy, and other measures to ensure the safe use of 14 | // applications if the Programs are used for purposes such as nuclear, 15 | // aviation, mass transit, medical, or other inherently dangerous applications, 16 | // and ArangoDB GmbH disclaims liability for any damages caused by such use of 17 | // the Programs. 18 | // 19 | // This software is the confidential and proprietary information of ArangoDB 20 | // GmbH. You shall not disclose such confidential and proprietary information 21 | // and shall use it only in accordance with the terms of the license agreement 22 | // you entered into with ArangoDB GmbH. 23 | // 24 | // Author Ewout Prangsma 25 | // 26 | 27 | package terminal 28 | 29 | // IsTerminal returns true when the process has an attached 30 | // terminal. 31 | func IsTerminal() bool { 32 | // TODO figure out how to properly implement this. 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /pkg/trigger/trigger.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package trigger 24 | 25 | import "sync" 26 | 27 | // Trigger is a synchronization utility used to wait (in a select statement) 28 | // until someone triggers it. 29 | type Trigger struct { 30 | mu sync.Mutex 31 | done chan struct{} 32 | pendingTriggers int 33 | } 34 | 35 | // Done returns the channel to use in a select case. 36 | // This channel is closed when someone calls Trigger. 37 | func (t *Trigger) Done() <-chan struct{} { 38 | t.mu.Lock() 39 | defer t.mu.Unlock() 40 | 41 | if t.done == nil { 42 | t.done = make(chan struct{}) 43 | } 44 | if t.pendingTriggers > 0 { 45 | t.pendingTriggers = 0 46 | d := t.done 47 | close(t.done) 48 | t.done = nil 49 | return d 50 | } 51 | return t.done 52 | } 53 | 54 | // Trigger closes any Done channel. 55 | func (t *Trigger) Trigger() { 56 | t.mu.Lock() 57 | defer t.mu.Unlock() 58 | t.pendingTriggers++ 59 | if t.done != nil { 60 | close(t.done) 61 | t.done = nil 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/utils/buffer.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package utils 22 | 23 | import "sync" 24 | 25 | // LimitedBuffer stores only last 'limit' bytes written to it 26 | type LimitedBuffer struct { 27 | buf []byte 28 | limit int 29 | writeMutex sync.Mutex 30 | } 31 | 32 | // NewLimitedBuffer returns new LimitedBuffer 33 | func NewLimitedBuffer(limit int) *LimitedBuffer { 34 | return &LimitedBuffer{ 35 | buf: make([]byte, 0, limit), 36 | limit: limit, 37 | } 38 | } 39 | 40 | // String returns string representation of buffer. 41 | func (b *LimitedBuffer) String() string { 42 | return string(b.buf) 43 | } 44 | 45 | // Write writes len(p) bytes from p to the buffer, 46 | // pushing out previously written bytes if they don't fit into buffer. 47 | // Always returns len(p). 48 | func (b *LimitedBuffer) Write(p []byte) (n int, err error) { 49 | b.writeMutex.Lock() 50 | defer b.writeMutex.Unlock() 51 | 52 | gotLen := len(p) 53 | if gotLen >= b.limit { 54 | b.buf = p[gotLen-b.limit:] 55 | } else if gotLen > 0 { 56 | newLength := len(b.buf) + gotLen 57 | if newLength <= b.limit { 58 | b.buf = append(b.buf, p...) 59 | } else { 60 | truncateIndex := newLength - b.limit 61 | b.buf = append(b.buf[truncateIndex:], p...) 62 | } 63 | } 64 | return gotLen, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/utils/buffer_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package utils 22 | 23 | import ( 24 | "strings" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestCaptureWriter(t *testing.T) { 31 | t.Run("simple-test", func(t *testing.T) { 32 | cw := NewLimitedBuffer(100) 33 | 34 | _, _ = cw.Write([]byte("")) 35 | assert.Equal(t, "", cw.String()) 36 | 37 | _, _ = cw.Write([]byte("0123456789")) 38 | assert.Equal(t, "0123456789", cw.String()) 39 | 40 | _, _ = cw.Write([]byte(strings.Repeat("0123456789", 9))) 41 | assert.Equal(t, strings.Repeat("0123456789", 10), cw.String()) 42 | }) 43 | 44 | t.Run("full-copy", func(t *testing.T) { 45 | cw := NewLimitedBuffer(100) 46 | 47 | _, _ = cw.Write([]byte(strings.Repeat("0123456789", 10))) 48 | assert.Equal(t, strings.Repeat("0123456789", 10), cw.String()) 49 | }) 50 | 51 | t.Run("overflow", func(t *testing.T) { 52 | cw := NewLimitedBuffer(10) 53 | 54 | _, _ = cw.Write([]byte(strings.Repeat("0123456789", 2))) 55 | assert.Equal(t, strings.Repeat("0123456789", 1), cw.String()) 56 | }) 57 | 58 | t.Run("one-byte-overflow", func(t *testing.T) { 59 | cw := NewLimitedBuffer(10) 60 | 61 | _, _ = cw.Write([]byte("01234567890")) 62 | assert.Equal(t, "1234567890", cw.String()) 63 | }) 64 | 65 | t.Run("one-byte-overflow-1", func(t *testing.T) { 66 | cw := NewLimitedBuffer(10) 67 | 68 | _, _ = cw.Write([]byte(strings.Repeat("001234", 1))) 69 | _, _ = cw.Write([]byte(strings.Repeat("56789", 1))) 70 | assert.Equal(t, strings.Repeat("0123456789", 1), cw.String()) 71 | }) 72 | 73 | t.Run("one-byte-overflow-2", func(t *testing.T) { 74 | cw := NewLimitedBuffer(10) 75 | 76 | _, _ = cw.Write([]byte(strings.Repeat("00123", 1))) 77 | _, _ = cw.Write([]byte(strings.Repeat("456789", 1))) 78 | assert.Equal(t, strings.Repeat("0123456789", 1), cw.String()) 79 | }) 80 | 81 | t.Run("overflow-many", func(t *testing.T) { 82 | cw := NewLimitedBuffer(100) 83 | 84 | _, _ = cw.Write([]byte(strings.Repeat("0123456789", 20))) 85 | assert.Equal(t, strings.Repeat("0123456789", 10), cw.String()) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /pkg/utils/util.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2023 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package utils 22 | 23 | func NotNilDefault(b *bool, def *bool) *bool { 24 | if b == nil { 25 | return def 26 | } 27 | return b 28 | } 29 | -------------------------------------------------------------------------------- /remove.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package main 24 | 25 | import ( 26 | "context" 27 | 28 | "github.com/spf13/cobra" 29 | ) 30 | 31 | var ( 32 | cmdRemove = &cobra.Command{ 33 | Use: "remove", 34 | Short: "Remove something", 35 | Run: cmdShowUsage, 36 | } 37 | cmdRemoveStarter = &cobra.Command{ 38 | Use: "starter", 39 | Short: "Remove a starter from the cluster", 40 | Run: cmdRemoveStarterRun, 41 | } 42 | removeStarterOptions struct { 43 | starterEndpoint string 44 | starterID string 45 | force bool 46 | } 47 | ) 48 | 49 | func init() { 50 | f := cmdRemoveStarter.Flags() 51 | f.StringVar(&removeStarterOptions.starterEndpoint, "starter.endpoint", "", "The endpoint of the starter to connect to. E.g. http://localhost:8528") 52 | f.StringVar(&removeStarterOptions.starterID, "starter.id", "", "The ID of the starter to remove") 53 | f.BoolVar(&removeStarterOptions.force, "force", false, "If set to true, the starter will be removed even if the servers cannot be properly shutdown") 54 | 55 | cmdMain.AddCommand(cmdRemove) 56 | cmdRemove.AddCommand(cmdRemoveStarter) 57 | } 58 | 59 | func cmdRemoveStarterRun(cmd *cobra.Command, args []string) { 60 | // Setup logging 61 | consoleOnly := true 62 | configureLogging(consoleOnly) 63 | 64 | // Create starter client 65 | c := mustCreateStarterClient(removeStarterOptions.starterEndpoint) 66 | 67 | // Fetch the ID of the starter for which the endpoint is given 68 | ctx := context.Background() 69 | info, err := c.ID(ctx) 70 | if err != nil { 71 | log.Fatal().Err(err).Msg("Failed to fetch ID from starter") 72 | } 73 | 74 | // Compare ID with requested. 75 | if removeStarterOptions.starterID == "" || removeStarterOptions.starterID == info.ID { 76 | // Shutdown (with goodbye) the starter at given endpoint 77 | goodbye := true 78 | if err := c.Shutdown(ctx, goodbye); err != nil { 79 | log.Fatal().Err(err).Msg("Removing starter from cluster failed") 80 | } else { 81 | log.Info().Msg("Starter has been shutdown and removed from cluster") 82 | } 83 | } else { 84 | // Remove another starter from the cluster 85 | if err := c.RemovePeer(ctx, removeStarterOptions.starterID, removeStarterOptions.force); err != nil { 86 | log.Fatal().Err(err).Msg("Removing starter from cluster failed") 87 | } else { 88 | log.Info().Msg("Starter has been removed from cluster") 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /service/arangod_config.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "fmt" 27 | "io" 28 | "io/ioutil" 29 | "strings" 30 | ) 31 | 32 | var confHeader = `# ArangoDB configuration file 33 | # 34 | # Documentation: 35 | # https://www.arangodb.com/docs/stable/administration-configuration.html 36 | # 37 | 38 | ` 39 | 40 | type configFile []*configSection 41 | 42 | // WriteTo writes the configuration sections to the given writer. 43 | func (cf configFile) WriteTo(w io.Writer) (int64, error) { 44 | x := int64(0) 45 | n, err := w.Write([]byte(confHeader)) 46 | if err != nil { 47 | return x, maskAny(err) 48 | } 49 | x += int64(n) 50 | for _, section := range cf { 51 | n, err := section.WriteTo(w) 52 | if err != nil { 53 | return x, maskAny(err) 54 | } 55 | x += n 56 | } 57 | return x, nil 58 | } 59 | 60 | // FindSection searches for a section with given name and returns it. 61 | // If not found, nil is returned. 62 | func (cf configFile) FindSection(sectionName string) *configSection { 63 | for _, sect := range cf { 64 | if sect.Name == sectionName { 65 | return sect 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | type configSection struct { 72 | Name string 73 | Settings map[string]string 74 | } 75 | 76 | // WriteTo writes the configuration section to the given writer. 77 | func (s *configSection) WriteTo(w io.Writer) (int64, error) { 78 | lines := []string{"[" + s.Name + "]"} 79 | for k, v := range s.Settings { 80 | lines = append(lines, fmt.Sprintf("%s = %s", k, v)) 81 | } 82 | lines = append(lines, "") 83 | n, err := w.Write([]byte(strings.Join(lines, "\n"))) 84 | return int64(n), maskAny(err) 85 | } 86 | 87 | // readConfigFile loads the content of a config file. 88 | func readConfigFile(path string) (configFile, error) { 89 | content, err := ioutil.ReadFile(path) 90 | if err != nil { 91 | return nil, maskAny(err) 92 | } 93 | lines := strings.Split(string(content), "\n") 94 | config := configFile{} 95 | var section *configSection 96 | for _, line := range lines { 97 | idx := strings.Index(line, "#") 98 | if idx >= 0 { 99 | line = line[:idx] 100 | } 101 | line = strings.TrimSpace(line) 102 | if line == "" { 103 | continue 104 | } 105 | if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { 106 | name := strings.TrimSpace(line[1 : len(line)-2]) 107 | section = &configSection{ 108 | Name: name, 109 | Settings: make(map[string]string), 110 | } 111 | config = append(config, section) 112 | } else if section != nil { 113 | parts := strings.SplitN(line, "=", 2) 114 | if len(parts) != 2 { 115 | continue 116 | } 117 | key := strings.TrimSpace(parts[0]) 118 | value := strings.TrimSpace(parts[1]) 119 | section.Settings[key] = value 120 | } 121 | } 122 | return config, nil 123 | } 124 | -------------------------------------------------------------------------------- /service/authentication.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "net/http" 27 | "time" 28 | 29 | "github.com/golang-jwt/jwt/v5" 30 | ) 31 | 32 | const ( 33 | AuthorizationHeader = "Authorization" 34 | BearerPrefix = "bearer " 35 | ) 36 | 37 | // CreateJwtToken calculates a JWT authorization token based on the given secret. 38 | // If the secret is empty, an empty token is returned. 39 | func CreateJwtToken(jwtSecret, user string, serverId string, paths []string, exp time.Duration, fieldsOverride jwt.MapClaims) (string, error) { 40 | if jwtSecret == "" { 41 | return "", nil 42 | } 43 | if serverId == "" { 44 | serverId = "foo" 45 | } 46 | 47 | // Create a new token object, specifying signing method and the claims 48 | // you would like it to contain. 49 | claims := jwt.MapClaims{ 50 | "iss": "arangodb", 51 | "server_id": serverId, 52 | } 53 | if user != "" { 54 | claims["preferred_username"] = user 55 | } 56 | if paths != nil { 57 | claims["allowed_paths"] = paths 58 | } 59 | if exp > 0 { 60 | t := time.Now().UTC() 61 | claims["iat"] = t.Unix() 62 | claims["exp"] = t.Add(exp).Unix() 63 | } 64 | for k, v := range fieldsOverride { 65 | claims[k] = v 66 | } 67 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 68 | 69 | // Sign and get the complete encoded token as a string using the secret 70 | signedToken, err := token.SignedString([]byte(jwtSecret)) 71 | if err != nil { 72 | return "", maskAny(err) 73 | } 74 | 75 | return signedToken, nil 76 | } 77 | 78 | // addJwtHeader calculates a JWT authorization header based on the given secret 79 | // and adds it to the given request. 80 | // If the secret is empty, nothing is done. 81 | func addJwtHeader(req *http.Request, jwtSecret string) error { 82 | if jwtSecret == "" { 83 | return nil 84 | } 85 | signedToken, err := CreateJwtToken(jwtSecret, "", "", nil, 0, nil) 86 | if err != nil { 87 | return maskAny(err) 88 | } 89 | 90 | req.Header.Set(AuthorizationHeader, BearerPrefix+signedToken) 91 | return nil 92 | } 93 | 94 | // addBearerTokenHeader adds an authorization header based on the given bearer token 95 | // to the given request. 96 | // If the given token is empty, nothing is done. 97 | func addBearerTokenHeader(req *http.Request, bearerToken string) error { 98 | if bearerToken == "" { 99 | return nil 100 | } 101 | 102 | req.Header.Set(AuthorizationHeader, BearerPrefix+bearerToken) 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /service/backoff.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "context" 27 | "time" 28 | 29 | "github.com/cenkalti/backoff" 30 | "github.com/pkg/errors" 31 | ) 32 | 33 | type PermanentError struct { 34 | Err error 35 | } 36 | 37 | func (e *PermanentError) Error() string { 38 | return e.Err.Error() 39 | } 40 | 41 | func retry(ctx context.Context, op func() error, timeout time.Duration) error { 42 | var failure error 43 | wrappedOp := func() error { 44 | if err := ctx.Err(); err != nil { 45 | failure = err 46 | return nil 47 | } 48 | if err := op(); err == nil { 49 | return nil 50 | } else { 51 | if pe, ok := errors.Cause(err).(*PermanentError); ok { 52 | // Detected permanent error 53 | failure = pe.Err 54 | return nil 55 | } else { 56 | return err 57 | } 58 | } 59 | } 60 | b := backoff.NewExponentialBackOff() 61 | b.MaxElapsedTime = timeout 62 | b.MaxInterval = timeout / 3 63 | if err := backoff.Retry(wrappedOp, b); err != nil { 64 | return maskAny(err) 65 | } 66 | if failure != nil { 67 | return maskAny(failure) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /service/certificate.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "crypto/tls" 27 | "crypto/x509/pkix" 28 | "io/ioutil" 29 | "strings" 30 | "time" 31 | 32 | certificates "github.com/arangodb-helper/go-certificates" 33 | ) 34 | 35 | // CreateCertificateOptions configures how to create a certificate. 36 | type CreateCertificateOptions struct { 37 | Hosts []string // Host names and/or IP addresses 38 | ValidFor time.Duration 39 | Organization string 40 | } 41 | 42 | const ( 43 | defaultValidFor = time.Hour * 24 * 365 // 1year 44 | defaultCurve = "P256" 45 | ) 46 | 47 | // CreateCertificate creates a self-signed certificate according to the given configuration. 48 | // The resulting certificate + private key will be written into a single file in the given folder. 49 | // The path of that single file is returned. 50 | func CreateCertificate(options CreateCertificateOptions, folder string) (string, error) { 51 | if options.ValidFor == 0 { 52 | options.ValidFor = defaultValidFor 53 | } 54 | certOpts := certificates.CreateCertificateOptions{ 55 | Hosts: options.Hosts, 56 | Subject: &pkix.Name{ 57 | Organization: []string{options.Organization}, 58 | }, 59 | ValidFrom: time.Now(), 60 | ValidFor: options.ValidFor, 61 | ECDSACurve: defaultCurve, 62 | } 63 | 64 | // Create self-signed certificate 65 | cert, priv, err := certificates.CreateCertificate(certOpts, nil) 66 | if err != nil { 67 | return "", maskAny(err) 68 | } 69 | 70 | // Write the certificate to disk 71 | f, err := ioutil.TempFile(folder, "key-") 72 | if err != nil { 73 | return "", maskAny(err) 74 | } 75 | defer f.Close() 76 | content := strings.TrimSpace(cert) + "\n" + priv 77 | if _, err := f.WriteString(content); err != nil { 78 | return "", maskAny(err) 79 | } 80 | 81 | return f.Name(), nil 82 | } 83 | 84 | // LoadKeyFile loads a SSL keyfile formatted for the arangod server. 85 | func LoadKeyFile(keyFile string) (tls.Certificate, error) { 86 | result, err := certificates.LoadKeyFile(keyFile) 87 | if err != nil { 88 | return tls.Certificate{}, maskAny(err) 89 | } 90 | return result, nil 91 | } 92 | -------------------------------------------------------------------------------- /service/client_builder.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // Author Tomasz Mielech 22 | // 23 | 24 | package service 25 | 26 | import ( 27 | driver "github.com/arangodb/go-driver" 28 | 29 | "github.com/arangodb-helper/arangodb/pkg/definitions" 30 | ) 31 | 32 | type ConnectionType int 33 | 34 | const ( 35 | ConnectionTypeDatabase ConnectionType = iota 36 | ConnectionTypeAgency 37 | ) 38 | 39 | // ClientBuilder describes how to create connections to the specific instance of the cluster. 40 | type ClientBuilder interface { 41 | // ClientBuilder creates authenticated go-driver clients with or without follow-redirect. 42 | CreateClient(endpoints []string, connectionType ConnectionType, serverType definitions.ServerType) (driver.Client, error) 43 | } 44 | -------------------------------------------------------------------------------- /service/clients/encryption.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package client 24 | 25 | type EncryptionDetailsResult struct { 26 | Keys Entries `json:"encryption-keys,omitempty"` 27 | } 28 | 29 | func (e EncryptionDetailsResult) KeysPresent(m map[string][]byte) bool { 30 | return e.Keys.KeysPresent(m) 31 | } 32 | 33 | type EncryptionDetails struct { 34 | Result EncryptionDetailsResult `json:"result,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /service/clients/jwt.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package client 24 | 25 | type JWTDetailsResult struct { 26 | Active *Entry `json:"active,omitempty"` 27 | Passive Entries `json:"passive,omitempty"` 28 | } 29 | 30 | type JWTDetails struct { 31 | Result JWTDetailsResult `json:"result,omitempty"` 32 | } 33 | -------------------------------------------------------------------------------- /service/clients/sha.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package client 24 | 25 | import ( 26 | "strings" 27 | ) 28 | 29 | type Sha string 30 | 31 | func (s Sha) String() string { 32 | return string(s) 33 | } 34 | 35 | func (s Sha) Type() string { 36 | z := strings.Split(s.String(), ":") 37 | if len(z) < 2 { 38 | return "sha256" 39 | } 40 | return z[0] 41 | } 42 | 43 | func (s Sha) Checksum() string { 44 | z := strings.Split(s.String(), ":") 45 | if len(z) < 2 { 46 | return z[0] 47 | } 48 | return z[1] 49 | } 50 | 51 | type Entry struct { 52 | Sha256 *Sha `json:"sha256,omitempty"` 53 | Sha256Old *Sha `json:"SHA256,omitempty"` 54 | } 55 | 56 | func (e *Entry) GetSHA() Sha { 57 | if e == nil { 58 | return "" 59 | } 60 | 61 | if e.Sha256 != nil { 62 | return *e.Sha256 63 | } 64 | if e.Sha256Old != nil { 65 | return *e.Sha256Old 66 | } 67 | 68 | return "" 69 | } 70 | 71 | type Entries []Entry 72 | 73 | func (e Entries) KeysPresent(m map[string][]byte) bool { 74 | if len(e) != len(m) { 75 | return false 76 | } 77 | 78 | for key := range m { 79 | ok := false 80 | for _, entry := range e { 81 | if entry.GetSHA().Checksum() == key { 82 | ok = true 83 | break 84 | } 85 | } 86 | if !ok { 87 | return false 88 | } 89 | } 90 | 91 | return true 92 | } 93 | 94 | func (e Entries) Contains(s string) bool { 95 | for _, entry := range e { 96 | if entry.GetSHA().String() == s { 97 | return true 98 | } 99 | } 100 | return false 101 | } 102 | 103 | func (e Entries) ContainsSha(s string) bool { 104 | for _, entry := range e { 105 | if entry.GetSHA().Checksum() == s { 106 | return true 107 | } 108 | } 109 | return false 110 | } 111 | -------------------------------------------------------------------------------- /service/clients/tls.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package client 24 | 25 | type TLSKeyFile struct { 26 | *Entry `json:",inline"` 27 | 28 | PrivateKeyHash string `json:"privateKeySHA256,omitempty"` 29 | Certificates []string `json:"certificates,omitempty"` 30 | } 31 | 32 | type TLSDetailsResult struct { 33 | KeyFile TLSKeyFile `json:"keyfile,omitempty"` 34 | SNI map[string]TLSKeyFile `json:"SNI,omitempty"` 35 | } 36 | 37 | type TLSDetails struct { 38 | Result TLSDetailsResult `json:"result,omitempty"` 39 | } 40 | -------------------------------------------------------------------------------- /service/command_file.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | import ( 24 | "bytes" 25 | "io/ioutil" 26 | "os" 27 | "strings" 28 | "text/template" 29 | "time" 30 | 31 | "github.com/rs/zerolog" 32 | ) 33 | 34 | const commandFileHeaderTmpl = `# This is the startup command used by the ArangoDB Starter. 35 | # Generated on {{ .DateTime }} for informational purposes only. 36 | # 37 | # Updates to this file will not have any effect. 38 | # If you want to make configuration changes, please adjust the Starter's command line options directly. 39 | # 40 | 41 | ` 42 | 43 | func createCommandFileHeader() string { 44 | type info struct { 45 | DateTime string 46 | } 47 | 48 | t := template.Must(template.New("header").Parse(commandFileHeaderTmpl)) 49 | var buf bytes.Buffer 50 | 51 | err := t.Execute(&buf, info{DateTime: time.Now().Format(time.RFC3339)}) 52 | if err != nil { 53 | panic(err) 54 | } 55 | return buf.String() 56 | } 57 | 58 | // writeCommandFile writes the command used to start a server in a file with given path. 59 | func writeCommandFile(log zerolog.Logger, filename string, args []string) { 60 | content := createCommandFileHeader() + strings.Join(args, " \\\n") + "\n" 61 | if _, err := os.Stat(filename); os.IsNotExist(err) { 62 | if err := ioutil.WriteFile(filename, []byte(content), 0755); err != nil { 63 | log.Error().Err(err).Msgf("Failed to write command to %s", filename) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /service/command_file_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestCreateCommandFileHeader(t *testing.T) { 30 | var h string 31 | require.NotPanics(t, func() { 32 | h = createCommandFileHeader() 33 | }) 34 | require.NotEmpty(t, h) 35 | } 36 | -------------------------------------------------------------------------------- /service/database_features.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | import ( 24 | driver "github.com/arangodb/go-driver" 25 | 26 | "github.com/arangodb-helper/arangodb/pkg/features" 27 | ) 28 | 29 | // DatabaseFeatures provides information about the features provided by the 30 | // database in a given version. 31 | type DatabaseFeatures struct { 32 | Version driver.Version 33 | Enterprise bool 34 | } 35 | 36 | const ( 37 | v32 driver.Version = "3.2.0" 38 | v33_20 driver.Version = "3.3.20" 39 | v33_22 driver.Version = "3.3.22" 40 | v34 driver.Version = "3.4.0" 41 | v34_2 driver.Version = "3.4.2" 42 | v312 driver.Version = "3.12.0" 43 | ) 44 | 45 | // NewDatabaseFeatures returns a new DatabaseFeatures based on 46 | // the given version. 47 | func NewDatabaseFeatures(version driver.Version, enterprise bool) DatabaseFeatures { 48 | return DatabaseFeatures{ 49 | Version: version, 50 | Enterprise: enterprise, 51 | } 52 | } 53 | 54 | // HasStorageEngineOption returns true when `server.storage-engine` 55 | // option is supported. 56 | func (v DatabaseFeatures) HasStorageEngineOption() bool { 57 | return v.Version.CompareTo(v32) >= 0 58 | } 59 | 60 | // DefaultStorageEngine returns the default storage engine (mmfiles|rocksdb). 61 | func (v DatabaseFeatures) DefaultStorageEngine() string { 62 | if v.Version.CompareTo(v34) >= 0 { 63 | return "rocksdb" 64 | } 65 | return "mmfiles" 66 | } 67 | 68 | // HasCopyInstallationFiles does server support copying installation files 69 | func (v DatabaseFeatures) HasCopyInstallationFiles() bool { 70 | if v.Version.CompareTo(v34) >= 0 { 71 | return true 72 | } 73 | if v.Version.CompareTo(v33_20) >= 0 { 74 | return true 75 | } 76 | return false 77 | } 78 | 79 | // HasJWTSecretFileOption does the server support passing jwt secret by file 80 | func (v DatabaseFeatures) HasJWTSecretFileOption() bool { 81 | if v.Version.CompareTo(v33_22) >= 0 && v.Version.CompareTo(v34) < 0 { 82 | return true 83 | } 84 | if v.Version.CompareTo(v34_2) >= 0 { 85 | return true 86 | } 87 | return false 88 | } 89 | 90 | func (v DatabaseFeatures) GetJWTFolderOption() bool { 91 | return features.JWTRotation().Enabled(features.Version{ 92 | Version: v.Version, 93 | Enterprise: v.Enterprise, 94 | }) 95 | } 96 | 97 | // SupportsActiveFailover returns true if Active-Failover (resilient-single) mode is supported 98 | func (v DatabaseFeatures) SupportsActiveFailover() bool { 99 | return v.Version.CompareTo(v312) < 0 100 | } 101 | 102 | // SupportsArangoSync returns true if ArangoSync is supported 103 | func (v DatabaseFeatures) SupportsArangoSync() bool { 104 | return v.Version.CompareTo(v312) < 0 105 | } 106 | -------------------------------------------------------------------------------- /service/docker.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "fmt" 27 | "os" 28 | "strconv" 29 | "strings" 30 | 31 | docker "github.com/fsouza/go-dockerclient" 32 | ) 33 | 34 | // findDockerExposedAddress looks up the external port number to which the given 35 | // port is mapped onto for the given container. 36 | func findDockerExposedAddress(dockerEndpoint, containerName string, port int) (hostPort int, isNetHost bool, networkMode string, hasTTY bool, err error) { 37 | 38 | os.Setenv("DOCKER_HOST", dockerEndpoint) 39 | client, err := docker.NewClientFromEnv() 40 | if err != nil { 41 | return 0, false, "", false, maskAny(err) 42 | } 43 | container, err := client.InspectContainer(containerName) 44 | if err != nil { 45 | return 0, false, "", false, maskAny(err) 46 | } 47 | hasTTY = container.Config.Tty 48 | networkMode = container.HostConfig.NetworkMode 49 | isNetHost = networkMode == "host" || strings.HasPrefix(networkMode, "container:") 50 | if isNetHost { 51 | // There is no port mapping for `--net=host` 52 | return port, isNetHost, networkMode, hasTTY, nil 53 | } 54 | dockerPort := docker.Port(fmt.Sprintf("%d/tcp", port)) 55 | bindings, ok := container.NetworkSettings.Ports[dockerPort] 56 | if !ok || len(bindings) == 0 { 57 | return 0, isNetHost, networkMode, hasTTY, maskAny(fmt.Errorf("Cannot find port binding for TCP port %d", port)) 58 | } 59 | hostPort, err = strconv.Atoi(bindings[0].HostPort) 60 | if err != nil { 61 | return 0, isNetHost, networkMode, hasTTY, maskAny(err) 62 | } 63 | return hostPort, isNetHost, networkMode, hasTTY, nil 64 | } 65 | -------------------------------------------------------------------------------- /service/envs.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "github.com/arangodb-helper/arangodb/pkg/definitions" 27 | ) 28 | 29 | // createEnvs returns environment variables which should be passed to created process. 30 | func createEnvs(config Config, serverType definitions.ServerType) map[string]string { 31 | return config.Configuration.EnvsForServerType(serverType) 32 | } 33 | -------------------------------------------------------------------------------- /service/error.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "fmt" 27 | 28 | "github.com/pkg/errors" 29 | 30 | driver "github.com/arangodb/go-driver" 31 | ) 32 | 33 | var ( 34 | maskAny = errors.WithStack 35 | ) 36 | 37 | type RedirectError struct { 38 | Location string 39 | } 40 | 41 | func (e RedirectError) Error() string { 42 | return fmt.Sprintf("Redirect to %s", e.Location) 43 | } 44 | 45 | func IsRedirect(err error) (string, bool) { 46 | if rerr, ok := errors.Cause(err).(RedirectError); ok { 47 | return rerr.Location, true 48 | } 49 | return "", false 50 | } 51 | 52 | // IsArangoErrorWithCodeAndNum returns true when the given raw content 53 | // contains a valid arangodb error encoded as json with the given 54 | // code and errorNum values. 55 | func IsArangoErrorWithCodeAndNum(aerr driver.ArangoError, code, errorNum int) bool { 56 | return aerr.HasError && aerr.Code == code && aerr.ErrorNum == errorNum 57 | } 58 | 59 | // IsLeadershipChallengeOngoingError returns true when given response content 60 | // contains an arango error indicating an ongoing leadership challenge. 61 | func IsLeadershipChallengeOngoingError(d driver.ArangoError) bool { 62 | return IsArangoErrorWithCodeAndNum(d, 503, 1495) 63 | } 64 | 65 | // IsNoLeaderError returns true when given response content 66 | // contains an arango error indicating an "I'm not the leader" error. 67 | func IsNoLeaderError(d driver.ArangoError) bool { 68 | return IsArangoErrorWithCodeAndNum(d, 503, 1496) 69 | } 70 | -------------------------------------------------------------------------------- /service/files.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2020 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "crypto/sha256" 27 | "fmt" 28 | "os" 29 | "strings" 30 | ) 31 | 32 | func FilterFiles(f []os.FileInfo, filter func(f os.FileInfo) bool) []os.FileInfo { 33 | localFiles := make([]os.FileInfo, 0, len(f)) 34 | 35 | for _, file := range f { 36 | if !filter(file) { 37 | continue 38 | } 39 | 40 | localFiles = append(localFiles, file) 41 | } 42 | 43 | return localFiles 44 | } 45 | 46 | func FilterOnlyFiles(f os.FileInfo) bool { 47 | return f.Mode().IsRegular() 48 | } 49 | 50 | func Sha256sum(d []byte) string { 51 | cleanData := []byte(strings.TrimSpace(string(d))) 52 | return fmt.Sprintf("%0x", sha256.Sum256(cleanData)) 53 | } 54 | -------------------------------------------------------------------------------- /service/guess_address.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "fmt" 27 | "net" 28 | ) 29 | 30 | // GuessOwnAddress takes a "best guess" approach to find the IP address used to reach the host PC. 31 | func GuessOwnAddress() (string, error) { 32 | intfs, err := net.Interfaces() 33 | if err != nil { 34 | return "", maskAny(err) 35 | } 36 | validIP4s := make([]net.IP, 0, 32) 37 | validIP6s := make([]net.IP, 0, 32) 38 | for _, intf := range intfs { 39 | if intf.Flags&net.FlagUp == 0 { 40 | continue 41 | } 42 | if intf.Flags&net.FlagLoopback != 0 { 43 | continue 44 | } 45 | addrs, err := intf.Addrs() 46 | if err != nil { 47 | // Just ignore this interface in case we cannot get its addresses 48 | continue 49 | } 50 | for _, addr := range addrs { 51 | ip, _, err := net.ParseCIDR(addr.String()) 52 | if err != nil { 53 | continue 54 | } 55 | if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { 56 | continue 57 | } 58 | if ip4 := ip.To4(); ip4 != nil { 59 | validIP4s = append(validIP4s, ip4) 60 | } else if ip6 := ip.To16(); ip6 != nil { 61 | validIP6s = append(validIP6s, ip6) 62 | } 63 | } 64 | } 65 | if len(validIP4s) > 0 { 66 | return validIP4s[0].String(), nil 67 | } 68 | if len(validIP6s) > 0 { 69 | return validIP6s[0].String(), nil 70 | } 71 | return "", fmt.Errorf("No suitable addresses found") 72 | } 73 | -------------------------------------------------------------------------------- /service/image_pull_policy.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "strconv" 27 | "strings" 28 | ) 29 | 30 | type ImagePullPolicy string 31 | 32 | const ( 33 | // ImagePullPolicyAlways is a policy to always pull the image 34 | ImagePullPolicyAlways ImagePullPolicy = "Always" 35 | // ImagePullPolicyIfNotPresent is a policy to only pull the image when it does not exist locally 36 | ImagePullPolicyIfNotPresent ImagePullPolicy = "IfNotPresent" 37 | // ImagePullPolicyNever is a policy to never pull the image 38 | ImagePullPolicyNever ImagePullPolicy = "Never" 39 | ) 40 | 41 | // ParseImagePullPolicy parses a string into an image pull policy 42 | func ParseImagePullPolicy(s, image string) (ImagePullPolicy, error) { 43 | switch strings.ToLower(s) { 44 | case "": 45 | if strings.Contains(image, ":latest") { 46 | return ImagePullPolicyAlways, nil 47 | } else { 48 | return ImagePullPolicyIfNotPresent, nil 49 | } 50 | case "always": 51 | return ImagePullPolicyAlways, nil 52 | case "ifnotpresent": 53 | return ImagePullPolicyIfNotPresent, nil 54 | case "never": 55 | return ImagePullPolicyNever, nil 56 | default: 57 | if boolValue, err := strconv.ParseBool(s); err != nil { 58 | return ImagePullPolicyIfNotPresent, maskAny(err) 59 | } else if boolValue { 60 | return ImagePullPolicyAlways, nil 61 | } else { 62 | return ImagePullPolicyNever, nil 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /service/job.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Tomasz Mielech 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "context" 27 | "fmt" 28 | "time" 29 | 30 | "github.com/pkg/errors" 31 | 32 | "github.com/arangodb/go-driver/agency" 33 | 34 | "github.com/arangodb-helper/arangodb/service/actions" 35 | ) 36 | 37 | type JobState string 38 | 39 | type JobStatus struct { 40 | Reason string `json:"reason,omitempty"` 41 | Server string `json:"server,omitempty"` 42 | JobID string `json:"jobId,omitempty"` 43 | Type string `json:"type,omitempty"` 44 | state JobState `json:"-"` 45 | } 46 | 47 | var ( 48 | JobStateToDo = JobState("ToDo") 49 | JobStatePending = JobState("Pending") 50 | JobStateFinished = JobState("Finished") 51 | JobStateFailed = JobState("Failed") 52 | 53 | agencyJobStateKeyPrefixes = []agency.Key{ 54 | {"arango", "Target", string(JobStateToDo)}, 55 | {"arango", "Target", string(JobStatePending)}, 56 | {"arango", "Target", string(JobStateFinished)}, 57 | {"arango", "Target", string(JobStateFailed)}, 58 | } 59 | 60 | ErrJobNotFound = errors.New("job not found") 61 | ErrJobFailed = errors.New("job failed") 62 | ) 63 | 64 | // getJobStatus gets the status of the job. 65 | func getJobStatus(ctx context.Context, jobID string, agencyClient agency.Agency) (JobStatus, error) { 66 | for _, keyPrefix := range agencyJobStateKeyPrefixes { 67 | var job JobStatus 68 | 69 | key := keyPrefix.CreateSubKey(jobID) 70 | if err := agencyClient.ReadKey(ctx, key, &job); err == nil { 71 | job.state = JobState(keyPrefix[len(keyPrefix)-1]) 72 | return job, nil 73 | } else if agency.IsKeyNotFound(err) { 74 | continue 75 | } else { 76 | return JobStatus{}, err 77 | } 78 | } 79 | 80 | return JobStatus{}, ErrJobNotFound 81 | } 82 | 83 | // WaitForFinishedJob waits for the job to be finished until context is canceled. 84 | // If the job fails the error ErrJobFailed is returned. 85 | func WaitForFinishedJob(progress actions.Progressor, ctx context.Context, jobID string, agencyClient agency.Agency) error { 86 | for { 87 | job, err := getJobStatus(ctx, jobID, agencyClient) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | if job.state == JobStateFinished { 93 | return nil 94 | } 95 | 96 | if job.state == JobStateFailed { 97 | return errors.Wrapf(ErrJobFailed, " with the reason %s", job.Reason) 98 | } 99 | 100 | select { 101 | case <-ctx.Done(): 102 | return ctx.Err() 103 | case <-time.After(time.Second * 2): // TODO memory leak 104 | progress.Progress(fmt.Sprintf("Job %s of type %s for server %s in state %s: %s", job.JobID, job.Type, job.Server, string(job.state), job.Reason)) 105 | 106 | break 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /service/local_slaves.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2023 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | import ( 24 | "fmt" 25 | "net" 26 | "os" 27 | "path/filepath" 28 | "strconv" 29 | "sync" 30 | ) 31 | 32 | // createAndStartLocalSlaves creates additional peers for local slaves and starts services for them. 33 | func (s *Service) createAndStartLocalSlaves(wg *sync.WaitGroup, config Config, bsCfg BootstrapConfig) { 34 | peersNeeded := bsCfg.PeersNeeded() 35 | peers := make([]Peer, 0, peersNeeded) 36 | for index := 2; index <= peersNeeded; index++ { 37 | p := Peer{} 38 | var err error 39 | p.ID, err = createUniqueID() 40 | if err != nil { 41 | s.log.Error().Err(err).Msg("Failed to create unique ID") 42 | continue 43 | } 44 | p.DataDir = filepath.Join(config.DataDir, fmt.Sprintf("local-slave-%d", index-1)) 45 | peers = append(peers, p) 46 | } 47 | s.startLocalSlaves(wg, config, bsCfg, peers) 48 | } 49 | 50 | // startLocalSlaves starts additional services for local slaves based on the given peers. 51 | func (s *Service) startLocalSlaves(wg *sync.WaitGroup, config Config, bsCfg BootstrapConfig, peers []Peer) { 52 | s.log = s.mustCreateIDLogger(s.id) 53 | s.log.Info().Msgf("Starting %d local slaves...", len(peers)-1) 54 | masterAddr := config.OwnAddress 55 | if masterAddr == "" { 56 | masterAddr = "127.0.0.1" 57 | } 58 | masterAddr = net.JoinHostPort(masterAddr, strconv.Itoa(s.announcePort)) 59 | for _, p := range peers { 60 | if p.ID == s.id { 61 | continue 62 | } 63 | slaveLog := s.mustCreateIDLogger(p.ID) 64 | slaveBsCfg := bsCfg 65 | slaveBsCfg.ID = p.ID 66 | slaveBsCfg.StartLocalSlaves = false 67 | os.MkdirAll(p.DataDir, 0755) 68 | 69 | // Read existing setup.json (if any) 70 | setupConfig, relaunch, _ := ReadSetupConfig(slaveLog, p.DataDir) 71 | if relaunch { 72 | oldOpts := setupConfig.Peers.PersistentOptions 73 | newOpts := config.Configuration.PersistentOptions 74 | if err := oldOpts.ValidateCompatibility(&newOpts); err != nil { 75 | s.log.Error().Err(err).Msg("Please check pass-through options") 76 | } 77 | 78 | slaveBsCfg.LoadFromSetupConfig(setupConfig) 79 | } 80 | 81 | slaveConfig := config // Create copy 82 | slaveConfig.DataDir = p.DataDir 83 | slaveConfig.MasterAddresses = []string{masterAddr} 84 | slaveService := NewService(s.stopPeer.ctx, slaveLog, s.logService, slaveConfig, bsCfg, true) 85 | wg.Add(1) 86 | go func(p Peer) { 87 | defer func() { 88 | s.log.Debug().Str("peer", p.ID).Msg("Local slave finished running") 89 | wg.Done() 90 | }() 91 | if err := slaveService.Run(s.stopPeer.ctx, slaveBsCfg, setupConfig.Peers, relaunch); err != nil { 92 | s.log.Error().Str("peer", p.ID).Err(err).Msg("Unable to start one of peers") 93 | } 94 | }(p) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /service/mode.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | type ServiceMode string 24 | 25 | const ( 26 | ServiceModeCluster ServiceMode = "cluster" 27 | ServiceModeSingle ServiceMode = "single" 28 | ) 29 | 30 | // IsClusterMode returns true when the service is running in cluster mode. 31 | func (m ServiceMode) IsClusterMode() bool { 32 | return m == ServiceModeCluster 33 | } 34 | 35 | // IsSingleMode returns true when the service is running in single server mode. 36 | func (m ServiceMode) IsSingleMode() bool { 37 | return m == ServiceModeSingle 38 | } 39 | 40 | // SupportsRecovery returns true when the given mode support recovering from permanent failed machines. 41 | func (m ServiceMode) SupportsRecovery() bool { 42 | return m == "" || m.IsClusterMode() 43 | } 44 | 45 | // HasAgency returns true when the given mode involves an agency. 46 | func (m ServiceMode) HasAgency() bool { 47 | return !m.IsSingleMode() 48 | } 49 | -------------------------------------------------------------------------------- /service/options/forbidden.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package options 24 | 25 | type forbidden []string 26 | 27 | func (f forbidden) IsForbidden(key string) bool { 28 | for _, a := range f { 29 | if a == key { 30 | return true 31 | } 32 | } 33 | 34 | return false 35 | } 36 | 37 | var ( 38 | // forbiddenOptions holds a list of options that are not allowed to be overridden. 39 | forbiddenOptions = forbidden{ 40 | // Arangod 41 | "agency.activate", 42 | "agency.endpoint", 43 | "agency.my-address", 44 | "agency.size", 45 | "agency.supervision", 46 | "cluster.agency-endpoint", 47 | "cluster.my-address", 48 | "cluster.my-role", 49 | "database.directory", 50 | "javascript.startup-directory", 51 | "javascript.app-path", 52 | "log.file", 53 | "rocksdb.encryption-keyfile", 54 | "server.endpoint", 55 | "server.authentication", 56 | "server.jwt-secret", 57 | "server.storage-engine", 58 | "ssl.cafile", 59 | "ssl.keyfile", 60 | // ArangoSync 61 | "cluster.endpoint", 62 | "master.endpoint", 63 | "server.endpoint", 64 | } 65 | ) 66 | -------------------------------------------------------------------------------- /service/options/persistent.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2023 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package options 22 | 23 | import ( 24 | "fmt" 25 | "strconv" 26 | "strings" 27 | ) 28 | 29 | func IsPersistentOption(arg string) bool { 30 | switch arg { 31 | case "database.extended-names", "database.extended-names-databases", 32 | "default-language", "icu-language": 33 | return true 34 | } 35 | return false 36 | } 37 | 38 | type PersistentOptions []PersistentOption 39 | 40 | func (po *PersistentOptions) Add(prefix, arg string, val *[]string) { 41 | if po == nil { 42 | *po = make([]PersistentOption, 0) 43 | } 44 | *po = append(*po, PersistentOption{ 45 | Prefix: prefix, 46 | Arg: arg, 47 | Value: val, 48 | }) 49 | } 50 | 51 | func (po *PersistentOptions) ValidateCompatibility(otherOpts *PersistentOptions) error { 52 | if po == nil || otherOpts == nil { 53 | return nil 54 | } 55 | 56 | var reports []string 57 | for _, o := range *po { 58 | for _, other := range *otherOpts { 59 | if !o.canBeChangedWith(other) { 60 | reports = append(reports, fmt.Sprintf("--%s.%s", o.Prefix, o.Arg)) 61 | } 62 | } 63 | } 64 | 65 | if len(reports) > 0 { 66 | return fmt.Errorf("it is impossible to change persistent options: %s", strings.Join(reports, ", ")) 67 | } 68 | return nil 69 | } 70 | 71 | type PersistentOption struct { 72 | Prefix string `json:"prefix"` 73 | Arg string `json:"arg"` 74 | Value *[]string `json:"value"` 75 | } 76 | 77 | // asBool returns true if value can be parsed as bool and equals v. 78 | // empty value assumed to be true 79 | func (p PersistentOption) asBool() (bool, error) { 80 | normValue := p.normValue() 81 | if normValue == "" { 82 | return true, nil 83 | } 84 | 85 | return strconv.ParseBool(normValue) 86 | } 87 | 88 | func (p PersistentOption) normValue() string { 89 | if p.Value == nil { 90 | return "" 91 | } 92 | var normValue string 93 | // for persistent options only last value is important 94 | if p.Value != nil && len(*p.Value) > 0 { 95 | normValue = (*p.Value)[len(*p.Value)-1] 96 | } 97 | return strings.TrimSpace(normValue) 98 | } 99 | 100 | func (p PersistentOption) canBeChangedWith(other PersistentOption) bool { 101 | if p.Arg != other.Arg { 102 | return true 103 | } 104 | 105 | switch p.Arg { 106 | case "default-language", "icu-language": 107 | return p.normValue() == other.normValue() 108 | case "database.extended-names", "database.extended-names-databases": 109 | oldVal, err := p.asBool() 110 | if err != nil { 111 | return true 112 | } 113 | newVal, err := other.asBool() 114 | if err != nil { 115 | return true 116 | } 117 | return oldVal == newVal || !oldVal && newVal 118 | } 119 | return true 120 | } 121 | -------------------------------------------------------------------------------- /service/options/utils.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package options 24 | 25 | import "strings" 26 | 27 | // SectionName returns the name of the configuration section this option belongs to. 28 | func SectionName(key string) string { 29 | return strings.SplitN(key, ".", 2)[0] 30 | } 31 | 32 | // SectionKey returns the name of this option within its configuration section. 33 | func SectionKey(key string) string { 34 | parts := strings.SplitN(key, ".", 2) 35 | if len(parts) > 1 { 36 | return parts[1] 37 | } 38 | return "" 39 | } 40 | 41 | // FormattedOptionName returns the option ready to be used in a command line argument, 42 | // prefixed with `--`. 43 | func FormattedOptionName(key string) string { 44 | return "--" + key 45 | } 46 | -------------------------------------------------------------------------------- /service/port_check.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "net" 27 | "strconv" 28 | "time" 29 | ) 30 | 31 | // IsPortOpen checks if a TCP port is free to listen on. 32 | func IsPortOpen(host string, port int) bool { 33 | l, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port))) 34 | if err != nil { 35 | return false 36 | } 37 | l.Close() 38 | return true 39 | } 40 | 41 | // WaitUntilPortAvailable waits until a TCP port is free to listen on 42 | // or a timeout occurs. 43 | // Returns true when port is free to listen on. 44 | func WaitUntilPortAvailable(host string, port int, timeout time.Duration) bool { 45 | start := time.Now() 46 | for { 47 | if IsPortOpen(host, port) { 48 | return true 49 | } 50 | if time.Since(start) >= timeout { 51 | return false 52 | } 53 | time.Sleep(time.Millisecond * 10) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /service/runner_process_shared.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | //go:build !windows 24 | // +build !windows 25 | 26 | package service 27 | 28 | import ( 29 | "fmt" 30 | "syscall" 31 | ) 32 | 33 | func (p *process) Terminate() error { 34 | if proc := p.p; proc != nil { 35 | if err := proc.Signal(syscall.SIGTERM); err != nil { 36 | if err.Error() == "os: process already finished" { 37 | // Race condition on OSX 38 | return nil 39 | } 40 | return maskAny(err) 41 | } 42 | } else { 43 | return fmt.Errorf("could not send signal: process handle is missing") 44 | } 45 | return nil 46 | } 47 | 48 | func getSysProcAttr() *syscall.SysProcAttr { 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /service/runner_process_windows.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | //go:build windows 24 | // +build windows 25 | 26 | package service 27 | 28 | import ( 29 | "fmt" 30 | "syscall" 31 | 32 | "github.com/pkg/errors" 33 | ) 34 | 35 | var ( 36 | libkernel32 = syscall.MustLoadDLL("kernel32") 37 | generateConsoleCtrlEvent = libkernel32.MustFindProc("GenerateConsoleCtrlEvent") 38 | ) 39 | 40 | func (p *process) Terminate() error { 41 | p.log.Info().Int("process", p.ProcessID()).Msgf("Sending windows CTRL_BREAK_EVENT event") 42 | r, _, err := generateConsoleCtrlEvent.Call(syscall.CTRL_BREAK_EVENT, uintptr(p.ProcessID())) 43 | 44 | if r == 0 { 45 | return errors.WithMessage(err, fmt.Sprintf("Exited with code %d", r)) 46 | } 47 | 48 | p.log.Info().Int("process", p.ProcessID()).Msgf("Send windows CTRL_BREAK_EVENT event") 49 | 50 | return nil 51 | } 52 | 53 | func getSysProcAttr() *syscall.SysProcAttr { 54 | return &syscall.SysProcAttr{ 55 | CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /service/runtime_server_manager_shared.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | //go:build !windows 24 | // +build !windows 25 | 26 | package service 27 | 28 | // clean cleans post run action. 29 | func (s *runtimeServerManager) clean() { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /service/runtime_server_manager_windows.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | //go:build windows 24 | // +build windows 25 | 26 | package service 27 | 28 | import ( 29 | "os" 30 | ) 31 | 32 | // clean cleans post run action. 33 | func (s *runtimeServerManager) clean() { 34 | os.Exit(0) 35 | } 36 | -------------------------------------------------------------------------------- /service/server_config_builder.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | // 24 | // Arangod options are configured in the following places: 25 | // - arangod.conf: 26 | // This holds all settings that are considered static for the lifetime of the cluster. 27 | // Using new/different settings on the Starter will not change these settings. 28 | // - arangod commandline: 29 | // This holds all settings that can change over the lifetime of the cluster. 30 | // These settings mostly involve the cluster layout. 31 | // Using new/different settings on the Starter will change these settings. 32 | // Passthrough options are always added here. 33 | // 34 | 35 | import ( 36 | "runtime" 37 | 38 | "github.com/rs/zerolog" 39 | 40 | "github.com/arangodb-helper/arangodb/pkg/definitions" 41 | ) 42 | 43 | type optionPair struct { 44 | Key string 45 | Value string 46 | } 47 | 48 | // collectServerConfigVolumes collects all files from the given config file for which a volume needs to be mapped. 49 | func collectServerConfigVolumes(serverType definitions.ServerType, config configFile) []Volume { 50 | var result []Volume 51 | 52 | addVolumeForSetting := func(sectionName, key string) { 53 | if section := config.FindSection(sectionName); section != nil { 54 | if path, ok := section.Settings[key]; ok { 55 | result = addVolume(result, path, path, true) 56 | } 57 | } 58 | } 59 | 60 | switch serverType.ProcessType() { 61 | case definitions.ProcessTypeArangod: 62 | addVolumeForSetting("ssl", "keyfile") 63 | addVolumeForSetting("ssl", "cafile") 64 | addVolumeForSetting("rocksdb", "encryption-keyfile") 65 | } 66 | 67 | return result 68 | } 69 | 70 | // createServerArgs returns the command line arguments needed to run an arangod/arangosync server of given type. 71 | func createServerArgs(log zerolog.Logger, config Config, clusterConfig ClusterConfig, myContainerDir, myContainerLogFile string, 72 | myPeerID, myAddress, myPort string, serverType definitions.ServerType, arangodConfig configFile, 73 | clusterJWTSecretFile string, agentRecoveryID string, databaseAutoUpgrade bool, features DatabaseFeatures) ([]string, error) { 74 | switch serverType.ProcessType() { 75 | case definitions.ProcessTypeArangod: 76 | return createArangodArgs(log, config, clusterConfig, myContainerDir, myContainerLogFile, myPeerID, myAddress, myPort, serverType, arangodConfig, agentRecoveryID, databaseAutoUpgrade, clusterJWTSecretFile, features), nil 77 | default: 78 | return nil, nil 79 | } 80 | } 81 | 82 | // addVolume extends the list of volumes with given host+container pair if running on linux. 83 | func addVolume(configVolumes []Volume, hostPath, containerPath string, readOnly bool) []Volume { 84 | if runtime.GOOS == "linux" { 85 | return []Volume{ 86 | { 87 | HostPath: hostPath, 88 | ContainerPath: containerPath, 89 | ReadOnly: readOnly, 90 | }, 91 | } 92 | } 93 | return configVolumes 94 | } 95 | -------------------------------------------------------------------------------- /service/state.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | // State of the service. 26 | type State int 27 | 28 | const ( 29 | stateStart State = iota // initial state after start 30 | stateBootstrapMaster // bootstrap phase, acting as master 31 | stateBootstrapSlave // bootstrap phase, acting as slave 32 | stateRunningMaster // running phase, acting as master 33 | stateRunningSlave // running phase, acting as slave 34 | ) 35 | 36 | // IsBootstrap returns true if given state is bootstrap master/slave 37 | func (s State) IsBootstrap() bool { 38 | return s == stateBootstrapMaster || s == stateBootstrapSlave 39 | } 40 | 41 | // IsRunning returns true if given state is running master/slave 42 | func (s State) IsRunning() bool { 43 | return s == stateRunningMaster || s == stateRunningSlave 44 | } 45 | -------------------------------------------------------------------------------- /service/storage_engine.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | "path/filepath" 27 | "strings" 28 | 29 | "github.com/arangodb-helper/arangodb/pkg/definitions" 30 | ) 31 | 32 | // validateStorageEngine checks if the given storage engine is a valid one. 33 | // Empty is still allowed. 34 | func (s *Service) validateStorageEngine(storageEngine string, features DatabaseFeatures) error { 35 | switch storageEngine { 36 | case "": 37 | // Not set yet. We'll choose one later 38 | return nil 39 | case "mmfiles": 40 | // Always OK 41 | return nil 42 | case "rocksdb": 43 | if !features.HasStorageEngineOption() { 44 | return maskAny(fmt.Errorf("RocksDB storage engine is not support for this database version")) 45 | } 46 | return nil 47 | default: 48 | return maskAny(fmt.Errorf("Unknown storage engine '%s'", storageEngine)) 49 | } 50 | } 51 | 52 | // readActualStorageEngine reads the actually used storage engine from 53 | // the database directory. 54 | func (s *Service) readActualStorageEngine() (string, error) { 55 | features := s.DatabaseFeatures() 56 | if !features.HasStorageEngineOption() { 57 | // ENGINE file does not exist 58 | return features.DefaultStorageEngine(), nil 59 | } 60 | 61 | _, peer, mode := s.ClusterConfig() 62 | var serverType definitions.ServerType 63 | if mode.IsClusterMode() { 64 | // Read engine from dbserver data directory 65 | if peer.HasDBServer() { 66 | serverType = definitions.ServerTypeDBServer 67 | } else if peer.HasAgent() { 68 | serverType = definitions.ServerTypeAgent 69 | } else { 70 | // In case of Coordinator return default storage engine 71 | return features.DefaultStorageEngine(), nil 72 | } 73 | } else { 74 | // Read engine from single server data directory 75 | serverType = definitions.ServerTypeSingle 76 | } 77 | // Get directory 78 | dataDir, err := s.serverHostDir(serverType) 79 | if err != nil { 80 | return "", maskAny(err) 81 | } 82 | // Read ENGINE file 83 | engine, err := os.ReadFile(filepath.Join(dataDir, "data", "ENGINE")) 84 | if err != nil { 85 | return "", maskAny(err) 86 | } 87 | storageEngine := strings.ToLower(strings.TrimSpace(string(engine))) 88 | return storageEngine, nil 89 | } 90 | -------------------------------------------------------------------------------- /service/url_schemes.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | // URLSchemes contains URL schemes for browser & Arango Shell. 26 | type URLSchemes struct { 27 | Browser string // Scheme for use in a webbrowser 28 | Arangod string // URL Scheme for use in Arangod[.conf] 29 | ArangoSH string // URL Scheme for use in ArangoSH 30 | } 31 | 32 | // NewURLSchemes creates initialized schemes depending on isSecure flag. 33 | func NewURLSchemes(isSecure bool) URLSchemes { 34 | if isSecure { 35 | return URLSchemes{"https", "ssl", "ssl"} 36 | } 37 | return URLSchemes{"http", "tcp", "tcp"} 38 | } 39 | -------------------------------------------------------------------------------- /service/util.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package service 24 | 25 | import ( 26 | "crypto/rand" 27 | "encoding/hex" 28 | "net" 29 | "net/http" 30 | "net/url" 31 | "strings" 32 | 33 | "github.com/arangodb/go-driver" 34 | ) 35 | 36 | // createUniqueID creates a new random ID. 37 | func createUniqueID() (string, error) { 38 | b := make([]byte, 4) 39 | if _, err := rand.Read(b); err != nil { 40 | return "", maskAny(err) 41 | } 42 | return hex.EncodeToString(b), nil 43 | } 44 | 45 | // normalizeHostName normalizes all loopback addresses to "localhost" 46 | func normalizeHostName(host string) string { 47 | if ip := net.ParseIP(host); ip != nil { 48 | if ip.IsLoopback() { 49 | return "localhost" 50 | } 51 | } 52 | return host 53 | } 54 | 55 | // For Windows we need to change backslashes to slashes, strangely enough: 56 | func slasher(s string) string { 57 | return strings.Replace(s, "\\", "/", -1) 58 | } 59 | 60 | // boolRef returns a reference to given bool 61 | func boolRef(v bool) *bool { 62 | return &v 63 | } 64 | 65 | // copyBoolRef returns a clone of the given reference 66 | func copyBoolRef(v *bool) *bool { 67 | if v == nil { 68 | return nil 69 | } 70 | return boolRef(*v) 71 | } 72 | 73 | // boolFromRef returns a boolean from given reference, returning given default value 74 | // when reference is nil. 75 | func boolFromRef(v *bool, defaultValue bool) bool { 76 | if v == nil { 77 | return defaultValue 78 | } 79 | return *v 80 | } 81 | 82 | // getURLWithPath returns an URL consisting of the given rootURL with the given relative path. 83 | func getURLWithPath(rootURL string, relPath string) (string, error) { 84 | u, err := url.Parse(rootURL) 85 | if err != nil { 86 | return "", maskAny(err) 87 | } 88 | parts := strings.SplitN(relPath, "?", 2) 89 | u.Path = parts[0] 90 | u.RawQuery = "" 91 | query := "" 92 | if len(parts) > 1 { 93 | query = "?" + parts[1] 94 | } 95 | return u.String() + query, nil 96 | } 97 | 98 | type causer interface { 99 | Cause() error 100 | } 101 | 102 | func getErrorCodeFromError(err error) int { 103 | if e, ok := err.(driver.ArangoError); ok { 104 | return e.Code 105 | } 106 | 107 | if c, ok := err.(causer); ok && c.Cause() != err { 108 | return getErrorCodeFromError(c.Cause()) 109 | } 110 | 111 | return http.StatusInternalServerError 112 | } 113 | -------------------------------------------------------------------------------- /service/version_check.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package service 22 | 23 | import ( 24 | "bytes" 25 | "context" 26 | "fmt" 27 | "strings" 28 | "time" 29 | 30 | "github.com/dchest/uniuri" 31 | "github.com/pkg/errors" 32 | "github.com/rs/zerolog" 33 | 34 | "github.com/arangodb/go-driver" 35 | 36 | "github.com/arangodb-helper/arangodb/pkg/definitions" 37 | ) 38 | 39 | // DatabaseVersion returns the version of the `arangod` binary that is being 40 | // used by this starter. 41 | func (s *Service) DatabaseVersion(ctx context.Context) (driver.Version, bool, error) { 42 | return DatabaseVersion(ctx, s.log, s.cfg.ArangodPath, s.runner) 43 | } 44 | 45 | // DatabaseVersion returns the version of the `arangod` binary 46 | func DatabaseVersion(ctx context.Context, log zerolog.Logger, arangodPath string, runner Runner) (driver.Version, bool, error) { 47 | retries := 25 48 | var err error 49 | for i := 0; i < retries; i++ { 50 | var v driver.Version 51 | var enterprise bool 52 | v, enterprise, err = databaseVersion(ctx, arangodPath, runner) 53 | if err == nil { 54 | return v, enterprise, nil 55 | } 56 | 57 | if ctxErr := ctx.Err(); ctxErr != nil { 58 | return "", false, ctxErr 59 | } 60 | 61 | log.Warn().Err(err).Msgf("Error while getting version. Attempt %d of %d", i+1, retries) 62 | time.Sleep(time.Second) 63 | } 64 | 65 | return "", false, fmt.Errorf("unable to get version: %s", err.Error()) 66 | } 67 | 68 | func databaseVersion(ctx context.Context, arangodPath string, runner Runner) (driver.Version, bool, error) { 69 | // Start process to print version info 70 | output := &bytes.Buffer{} 71 | containerName := "arangodb-versioncheck-" + strings.ToLower(uniuri.NewLen(6)) 72 | p, err := runner.Start(ctx, definitions.ProcessTypeArangod, arangodPath, []string{"--version", "--log.force-direct=true"}, nil, nil, nil, containerName, ".", output) 73 | if err != nil { 74 | return "", false, errors.WithStack(err) 75 | } 76 | defer p.Cleanup() 77 | if code := p.Wait(); code != 0 { 78 | return "", false, fmt.Errorf("process exited with exit code %d - %s", code, output.String()) 79 | } 80 | 81 | // Parse output 82 | stdout := output.String() 83 | lines := strings.Split(stdout, "\n") 84 | parsedLines := map[string]string{} 85 | for _, l := range lines { 86 | parts := strings.Split(l, ":") 87 | if len(parts) != 2 { 88 | continue 89 | } 90 | parsedLines[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) 91 | } 92 | 93 | var v driver.Version 94 | 95 | if vs, ok := parsedLines["server-version"]; !ok { 96 | return "", false, fmt.Errorf("no server-version found in '%s'", stdout) 97 | } else { 98 | fmt.Println("ArangoDB version found: ", vs) 99 | v = driver.Version(vs) 100 | } 101 | 102 | l := "community" 103 | if lc, ok := parsedLines["license"]; ok { 104 | l = lc 105 | } 106 | 107 | return v, l == "enterprise", nil 108 | } 109 | -------------------------------------------------------------------------------- /stop.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package main 24 | 25 | import ( 26 | "context" 27 | "fmt" 28 | "net/url" 29 | "time" 30 | 31 | "github.com/spf13/cobra" 32 | 33 | "github.com/arangodb-helper/arangodb/client" 34 | ) 35 | 36 | var ( 37 | cmdStop = &cobra.Command{ 38 | Use: "stop", 39 | Short: "Stop a ArangoDB starter", 40 | Run: cmdStopRun, 41 | } 42 | ) 43 | 44 | func init() { 45 | cmdMain.AddCommand(cmdStop) 46 | } 47 | 48 | func cmdStopRun(cmd *cobra.Command, args []string) { 49 | // Setup logging 50 | consoleOnly := true 51 | configureLogging(consoleOnly) 52 | 53 | // Create starter client 54 | scheme := "http" 55 | if opts.ssl.autoKeyFile || opts.ssl.keyFile != "" { 56 | scheme = "https" 57 | } 58 | starterURL, err := url.Parse(fmt.Sprintf("%s://127.0.0.1:%d", scheme, opts.starter.masterPort)) 59 | if err != nil { 60 | log.Fatal().Err(err).Msg("Failed to create starter URL") 61 | } 62 | client, err := client.NewArangoStarterClient(*starterURL) 63 | if err != nil { 64 | log.Fatal().Err(err).Msg("Failed to create starter client") 65 | } 66 | 67 | // Shutdown starter 68 | rootCtx := context.Background() 69 | ctx, cancel := context.WithTimeout(rootCtx, time.Minute) 70 | err = client.Shutdown(ctx, false) 71 | cancel() 72 | if err != nil { 73 | log.Fatal().Err(err).Msg("Failed to shutdown starter") 74 | } 75 | 76 | // Wait for starter to be really gone 77 | for { 78 | ctx, cancel := context.WithTimeout(rootCtx, time.Second) 79 | _, err := client.Version(ctx) 80 | cancel() 81 | if err != nil { 82 | break 83 | } 84 | time.Sleep(time.Millisecond * 100) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/docker_check_exitcodes_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "context" 25 | "regexp" 26 | "testing" 27 | "time" 28 | 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | // TestDockerErrExitCodeHandler runs in 'single' mode with invalid ArangoD configuration and 33 | // checks that starter recognizes exit code 34 | func TestDockerErrExitCodeHandler(t *testing.T) { 35 | testMatch(t, testModeDocker, starterModeSingle, false) 36 | 37 | cID := createDockerID("starter-test-single-") 38 | createDockerVolume(t, cID) 39 | defer removeDockerVolume(t, cID) 40 | 41 | // Cleanup of left over tests 42 | removeDockerContainersByLabel(t, "starter-test=true") 43 | removeStarterCreatedDockerContainers(t) 44 | 45 | dockerRun := spawnMemberInDocker(t, basePort, cID, "", "--starter.mode=single --args.all.config=invalidvalue", "") 46 | defer dockerRun.Close() 47 | defer removeDockerContainer(t, cID) 48 | 49 | re := regexp.MustCompile("has failed 1 times, giving up") 50 | require.NoError(t, dockerRun.ExpectTimeout(context.Background(), time.Second*20, re, "")) 51 | 52 | require.NoError(t, dockerRun.WaitTimeout(time.Second*10), "Starter is not stopped in time") 53 | 54 | waitForCallFunction(t, ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement))) 55 | } 56 | -------------------------------------------------------------------------------- /test/docker_cluster_default_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "fmt" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // TestDockerClusterDefault runs 3 arangodb starters in docker with default settings. 30 | func TestDockerClusterDefault(t *testing.T) { 31 | testMatch(t, testModeDocker, starterModeCluster, false) 32 | 33 | cID1 := createDockerID("starter-test-cluster-default1-") 34 | createDockerVolume(t, cID1) 35 | defer removeDockerVolume(t, cID1) 36 | 37 | cID2 := createDockerID("starter-test-cluster-default2-") 38 | createDockerVolume(t, cID2) 39 | defer removeDockerVolume(t, cID2) 40 | 41 | cID3 := createDockerID("starter-test-cluster-default3-") 42 | createDockerVolume(t, cID3) 43 | defer removeDockerVolume(t, cID3) 44 | 45 | // Cleanup of left over tests 46 | removeDockerContainersByLabel(t, "starter-test=true") 47 | removeStarterCreatedDockerContainers(t) 48 | 49 | joins := fmt.Sprintf("localhost:%d,localhost:%d,localhost:%d", basePort, basePort+(1*portIncrement), basePort+(2*portIncrement)) 50 | 51 | start := time.Now() 52 | 53 | dockerRun1 := spawnMemberInDocker(t, basePort, cID1, joins, "", "") 54 | defer dockerRun1.Close() 55 | defer removeDockerContainer(t, cID1) 56 | 57 | dockerRun2 := spawnMemberInDocker(t, basePort+(1*portIncrement), cID2, joins, "", "") 58 | defer dockerRun2.Close() 59 | defer removeDockerContainer(t, cID2) 60 | 61 | dockerRun3 := spawnMemberInDocker(t, basePort+(2*portIncrement), cID3, joins, "", "") 62 | defer dockerRun3.Close() 63 | defer removeDockerContainer(t, cID3) 64 | 65 | if ok := WaitUntilStarterReady(t, whatCluster, 3, dockerRun1, dockerRun2, dockerRun3); ok { 66 | t.Logf("Cluster start took %s", time.Since(start)) 67 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 68 | testCluster(t, insecureStarterEndpoint(1*portIncrement), false) 69 | testCluster(t, insecureStarterEndpoint(2*portIncrement), false) 70 | } 71 | 72 | waitForCallFunction(t, 73 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement)), 74 | ShutdownStarterCall(insecureStarterEndpoint(1*portIncrement)), 75 | ShutdownStarterCall(insecureStarterEndpoint(2*portIncrement))) 76 | } 77 | -------------------------------------------------------------------------------- /test/docker_cluster_diff_ports_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "fmt" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // TestDockerClusterDifferentPorts runs 3 arangodb starters in docker with different `--starter.port` values. 30 | func TestDockerClusterDifferentPorts(t *testing.T) { 31 | testMatch(t, testModeDocker, starterModeCluster, false) 32 | 33 | cID1 := createDockerID("starter-test-cluster-default1-") 34 | createDockerVolume(t, cID1) 35 | defer removeDockerVolume(t, cID1) 36 | 37 | cID2 := createDockerID("starter-test-cluster-default2-") 38 | createDockerVolume(t, cID2) 39 | defer removeDockerVolume(t, cID2) 40 | 41 | cID3 := createDockerID("starter-test-cluster-default3-") 42 | createDockerVolume(t, cID3) 43 | defer removeDockerVolume(t, cID3) 44 | 45 | // Cleanup of left over tests 46 | removeDockerContainersByLabel(t, "starter-test=true") 47 | removeStarterCreatedDockerContainers(t) 48 | 49 | joins := fmt.Sprintf("localhost:%d", 6000) 50 | 51 | start := time.Now() 52 | 53 | dockerRun1 := spawnMemberInDocker(t, 6000, cID1, "", "", "") 54 | defer dockerRun1.Close() 55 | defer removeDockerContainer(t, cID1) 56 | 57 | dockerRun2 := spawnMemberInDocker(t, 7000, cID2, joins, "", "") 58 | defer dockerRun2.Close() 59 | defer removeDockerContainer(t, cID2) 60 | 61 | dockerRun3 := spawnMemberInDocker(t, 8000, cID3, joins, "", "") 62 | defer dockerRun3.Close() 63 | defer removeDockerContainer(t, cID3) 64 | 65 | if ok := WaitUntilStarterReady(t, whatCluster, 3, dockerRun1, dockerRun2, dockerRun3); ok { 66 | t.Logf("Cluster start took %s", time.Since(start)) 67 | testCluster(t, "http://localhost:6000", false) 68 | testCluster(t, "http://localhost:7000", false) 69 | testCluster(t, "http://localhost:8000", false) 70 | } 71 | 72 | waitForCallFunction(t, 73 | ShutdownStarterCall("http://localhost:6000"), 74 | ShutdownStarterCall("http://localhost:7000"), 75 | ShutdownStarterCall("http://localhost:8000")) 76 | } 77 | -------------------------------------------------------------------------------- /test/docker_cluster_local_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "fmt" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // TestDockerClusterLocal runs the arangodb starter in docker with `--local` 30 | func TestDockerClusterLocal(t *testing.T) { 31 | testMatch(t, testModeDocker, starterModeCluster, false) 32 | 33 | cID := createDockerID("starter-test-cluster-default1-") 34 | createDockerVolume(t, cID) 35 | defer removeDockerVolume(t, cID) 36 | 37 | // Cleanup of left over tests 38 | removeDockerContainersByLabel(t, "starter-test=true") 39 | removeStarterCreatedDockerContainers(t) 40 | 41 | joins := fmt.Sprintf("localhost:%d,localhost:%d,localhost:%d", basePort, basePort+(1*portIncrement), basePort+(2*portIncrement)) 42 | 43 | start := time.Now() 44 | 45 | dockerRun := spawnMemberInDocker(t, basePort, cID, joins, "--starter.local", "") 46 | defer dockerRun.Close() 47 | defer removeDockerContainer(t, cID) 48 | 49 | if ok := WaitUntilStarterReady(t, whatCluster, 1, dockerRun); ok { 50 | t.Logf("Cluster start took %s", time.Since(start)) 51 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 52 | } 53 | 54 | waitForCallFunction(t, 55 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement))) 56 | } 57 | 58 | // TestDockerClusterLocalAgencySize1 runs the arangodb starter in docker 59 | // with `--starter.local` & `--cluster.agency-size=1` 60 | func TestDockerClusterLocalAgencySize1(t *testing.T) { 61 | testMatch(t, testModeDocker, starterModeCluster, false) 62 | 63 | cID := createDockerID("starter-test-cluster-default1-") 64 | createDockerVolume(t, cID) 65 | defer removeDockerVolume(t, cID) 66 | 67 | // Cleanup of left over tests 68 | removeDockerContainersByLabel(t, "starter-test=true --cluster.agency-size=1") 69 | removeStarterCreatedDockerContainers(t) 70 | 71 | joins := fmt.Sprintf("localhost:%d,localhost:%d,localhost:%d", basePort, basePort+(1*portIncrement), basePort+(2*portIncrement)) 72 | 73 | start := time.Now() 74 | 75 | dockerRun := spawnMemberInDocker(t, basePort, cID, joins, "--starter.local", "") 76 | defer dockerRun.Close() 77 | defer removeDockerContainer(t, cID) 78 | 79 | if ok := WaitUntilStarterReady(t, whatCluster, 1, dockerRun); ok { 80 | t.Logf("Cluster start took %s", time.Since(start)) 81 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 82 | } 83 | 84 | waitForCallFunction(t, 85 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement))) 86 | } 87 | 88 | // TestOldDockerClusterLocal runs the arangodb starter in docker with `--local` 89 | func TestOldDockerClusterLocal(t *testing.T) { 90 | testMatch(t, testModeDocker, starterModeCluster, false) 91 | 92 | cID := createDockerID("starter-test-cluster-default1-") 93 | createDockerVolume(t, cID) 94 | defer removeDockerVolume(t, cID) 95 | 96 | // Cleanup of left over tests 97 | removeDockerContainersByLabel(t, "starter-test=true") 98 | removeStarterCreatedDockerContainers(t) 99 | 100 | joins := fmt.Sprintf("localhost:%d,localhost:%d,localhost:%d", basePort, basePort+(1*portIncrement), basePort+(2*portIncrement)) 101 | 102 | start := time.Now() 103 | 104 | dockerRun := spawnMemberInDocker(t, basePort, cID, joins, "--local", "") 105 | defer dockerRun.Close() 106 | defer removeDockerContainer(t, cID) 107 | 108 | if ok := WaitUntilStarterReady(t, whatCluster, 1, dockerRun); ok { 109 | t.Logf("Cluster start took %s", time.Since(start)) 110 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 111 | } 112 | 113 | waitForCallFunction(t, 114 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement))) 115 | } 116 | -------------------------------------------------------------------------------- /test/docker_cluster_multi_join_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "fmt" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // TestDockerClusterMultipleJoins creates a cluster by starting 3 starters with all 3 30 | // starter addresses as join argument. 31 | func TestDockerClusterMultipleJoins(t *testing.T) { 32 | testMatch(t, testModeDocker, starterModeCluster, false) 33 | 34 | cID1 := createDockerID("starter-test-cluster-default1-") 35 | createDockerVolume(t, cID1) 36 | defer removeDockerVolume(t, cID1) 37 | 38 | cID2 := createDockerID("starter-test-cluster-default2-") 39 | createDockerVolume(t, cID2) 40 | defer removeDockerVolume(t, cID2) 41 | 42 | cID3 := createDockerID("starter-test-cluster-default3-") 43 | createDockerVolume(t, cID3) 44 | defer removeDockerVolume(t, cID3) 45 | 46 | // Cleanup of left over tests 47 | removeDockerContainersByLabel(t, "starter-test=true") 48 | removeStarterCreatedDockerContainers(t) 49 | 50 | joins := fmt.Sprintf("localhost:%d,localhost:%d,localhost:%d", 6000, 7000, 8000) 51 | 52 | start := time.Now() 53 | 54 | dockerRun1 := spawnMemberInDocker(t, 6000, cID1, joins, "", "") 55 | defer dockerRun1.Close() 56 | defer removeDockerContainer(t, cID1) 57 | 58 | dockerRun2 := spawnMemberInDocker(t, 7000, cID2, joins, "", "") 59 | defer dockerRun2.Close() 60 | defer removeDockerContainer(t, cID2) 61 | 62 | dockerRun3 := spawnMemberInDocker(t, 8000, cID3, joins, "", "") 63 | defer dockerRun3.Close() 64 | defer removeDockerContainer(t, cID3) 65 | 66 | if ok := WaitUntilStarterReady(t, whatCluster, 3, dockerRun1, dockerRun2, dockerRun3); ok { 67 | t.Logf("Cluster start took %s", time.Since(start)) 68 | testCluster(t, "http://localhost:6000", false) 69 | testCluster(t, "http://localhost:7000", false) 70 | testCluster(t, "http://localhost:8000", false) 71 | } 72 | 73 | waitForCallFunction(t, 74 | ShutdownStarterCall("http://localhost:6000"), 75 | ShutdownStarterCall("http://localhost:7000"), 76 | ShutdownStarterCall("http://localhost:8000")) 77 | } 78 | -------------------------------------------------------------------------------- /test/docker_cluster_upgrade_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "fmt" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // TestDockerClusterUpgrade runs 3 arangodb starters in docker with default settings. 30 | // Once running it starts a database upgrade. 31 | func TestDockerClusterUpgrade(t *testing.T) { 32 | testMatch(t, testModeDocker, starterModeCluster, false) 33 | 34 | cID1 := createDockerID("starter-test-cluster-default1-") 35 | createDockerVolume(t, cID1) 36 | defer removeDockerVolume(t, cID1) 37 | 38 | cID2 := createDockerID("starter-test-cluster-default2-") 39 | createDockerVolume(t, cID2) 40 | defer removeDockerVolume(t, cID2) 41 | 42 | cID3 := createDockerID("starter-test-cluster-default3-") 43 | createDockerVolume(t, cID3) 44 | defer removeDockerVolume(t, cID3) 45 | 46 | // Cleanup of left over tests 47 | removeDockerContainersByLabel(t, "starter-test=true") 48 | removeStarterCreatedDockerContainers(t) 49 | 50 | joins := fmt.Sprintf("localhost:%d", basePort) 51 | 52 | start := time.Now() 53 | 54 | dockerRun1 := spawnMemberInDocker(t, basePort, cID1, "", "", "") 55 | defer dockerRun1.Close() 56 | defer removeDockerContainer(t, cID1) 57 | 58 | dockerRun2 := spawnMemberInDocker(t, basePort+(1*portIncrement), cID2, joins, "", "") 59 | defer dockerRun2.Close() 60 | defer removeDockerContainer(t, cID2) 61 | 62 | dockerRun3 := spawnMemberInDocker(t, basePort+(2*portIncrement), cID3, joins, "", "") 63 | defer dockerRun3.Close() 64 | defer removeDockerContainer(t, cID3) 65 | 66 | if ok := WaitUntilStarterReady(t, whatCluster, 3, dockerRun1, dockerRun2, dockerRun3); ok { 67 | t.Logf("Cluster start took %s", time.Since(start)) 68 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 69 | testCluster(t, insecureStarterEndpoint(1*portIncrement), false) 70 | testCluster(t, insecureStarterEndpoint(2*portIncrement), false) 71 | } 72 | 73 | testUpgradeProcess(t, insecureStarterEndpoint(0*portIncrement)) 74 | 75 | waitForCallFunction(t, 76 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement)), 77 | ShutdownStarterCall(insecureStarterEndpoint(1*portIncrement)), 78 | ShutdownStarterCall(insecureStarterEndpoint(2*portIncrement))) 79 | } 80 | -------------------------------------------------------------------------------- /test/docker_database_version_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "context" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // TestDockerDatabaseVersion runs the arangodb starter in docker with `--starter.mode=single` 30 | // and tries a `/database-version` request. 31 | func TestDockerDatabaseVersion(t *testing.T) { 32 | testMatch(t, testModeDocker, starterModeSingle, false) 33 | 34 | cID := createDockerID("starter-test-cluster-default1-") 35 | createDockerVolume(t, cID) 36 | defer removeDockerVolume(t, cID) 37 | 38 | // Cleanup of left over tests 39 | removeDockerContainersByLabel(t, "starter-test=true") 40 | removeStarterCreatedDockerContainers(t) 41 | 42 | start := time.Now() 43 | 44 | dockerRun := spawnMemberInDocker(t, basePort, cID, "", "--starter.mode=single", "") 45 | defer dockerRun.Close() 46 | defer removeDockerContainer(t, cID) 47 | 48 | if ok := WaitUntilStarterReady(t, whatSingle, 1, dockerRun); ok { 49 | t.Logf("Single server start took %s", time.Since(start)) 50 | testSingle(t, insecureStarterEndpoint(0*portIncrement), false) 51 | } 52 | 53 | if isVerbose { 54 | t.Log("Waiting for termination") 55 | } 56 | 57 | c := NewStarterClient(t, insecureStarterEndpoint(0*portIncrement)) 58 | ctx := context.Background() 59 | if v, err := c.DatabaseVersion(ctx); err != nil { 60 | t.Errorf("DatabaseVersion failed: %#v", err) 61 | } else { 62 | t.Logf("Got database-version %s", v) 63 | } 64 | 65 | waitForCallFunction(t, 66 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement))) 67 | } 68 | -------------------------------------------------------------------------------- /test/docker_install_go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -z "$(which apt-get)" ]; then 4 | # Install for debian 5 | # gcc for cgo 6 | apt-get update && apt-get install -y --no-install-recommends \ 7 | g++ \ 8 | gcc \ 9 | libc6-dev \ 10 | make \ 11 | pkg-config \ 12 | wget \ 13 | procps \ 14 | && rm -rf /var/lib/apt/lists/* 15 | 16 | set -eux; \ 17 | \ 18 | # this "case" statement is generated via "update.sh" 19 | dpkgArch="$(dpkg --print-architecture)"; \ 20 | case "${dpkgArch##*-}" in \ 21 | amd64) goRelArch='linux-amd64'; goRelSha256='d7d1f1f88ddfe55840712dc1747f37a790cbcaa448f6c9cf51bbe10aa65442f5' ;; \ 22 | armhf) goRelArch='linux-armv6l'; goRelSha256='feca4e920d5ca25001dc0823390df79bc7ea5b5b8c03483e5a2c54f164654936' ;; \ 23 | arm64) goRelArch='linux-arm64'; goRelSha256='1e07a159414b5090d31166d1a06ee501762076ef21140dcd54cdcbe4e68a9c9b' ;; \ 24 | i386) goRelArch='linux-386'; goRelSha256='acbe19d56123549faf747b4f61b730008b185a0e2145d220527d2383627dfe69' ;; \ 25 | ppc64el) goRelArch='linux-ppc64le'; goRelSha256='91d0026bbed601c4aad332473ed02f9a460b31437cbc6f2a37a88c0376fc3a65' ;; \ 26 | s390x) goRelArch='linux-s390x'; goRelSha256='e211a5abdacf843e16ac33a309d554403beb63959f96f9db70051f303035434b' ;; \ 27 | *) goRelArch='src'; goRelSha256='589449ff6c3ccbff1d391d4e7ab5bb5d5643a5a41a04c99315e55c16bbf73ddc'; \ 28 | echo >&2; echo >&2 "warning: current architecture ($dpkgArch) does not have a corresponding Go binary release; will be building from source"; echo >&2 ;; \ 29 | esac; \ 30 | \ 31 | url="https://golang.org/dl/go${GOLANG_VERSION}.${goRelArch}.tar.gz"; \ 32 | wget -q -O go.tgz "$url"; \ 33 | sha256sum *go.tgz ; \ 34 | echo "${goRelSha256} *go.tgz" | sha256sum -c -; \ 35 | tar -C /usr/local -xzf go.tgz; \ 36 | rm go.tgz; \ 37 | \ 38 | if [ "$goRelArch" = 'src' ]; then \ 39 | echo >&2; \ 40 | echo >&2 'error: UNIMPLEMENTED'; \ 41 | echo >&2 'TODO install golang-any from jessie-backports for GOROOT_BOOTSTRAP (and uninstall after build)'; \ 42 | echo >&2; \ 43 | exit 1; \ 44 | fi; \ 45 | \ 46 | export PATH="/usr/local/go/bin:$PATH"; \ 47 | go version 48 | else 49 | # Install for alpine 50 | set -eux; \ 51 | apk add --no-cache --virtual .build-deps \ 52 | bash \ 53 | gcc \ 54 | musl-dev \ 55 | openssl \ 56 | go \ 57 | ; \ 58 | export \ 59 | # set GOROOT_BOOTSTRAP such that we can actually build Go 60 | GOROOT_BOOTSTRAP="$(go env GOROOT)" \ 61 | # ... and set "cross-building" related vars to the installed system's values so that we create a build targeting the proper arch 62 | # (for example, if our build host is GOARCH=amd64, but our build env/image is GOARCH=386, our build needs GOARCH=386) 63 | GOOS="$(go env GOOS)" \ 64 | GOARCH="$(go env GOARCH)" \ 65 | GOHOSTOS="$(go env GOHOSTOS)" \ 66 | GOHOSTARCH="$(go env GOHOSTARCH)" \ 67 | ; \ 68 | # also explicitly set GO386 and GOARM if appropriate 69 | # https://github.com/docker-library/golang/issues/184 70 | apkArch="$(apk --print-arch)"; \ 71 | case "$apkArch" in \ 72 | armhf) export GOARM='6' ;; \ 73 | x86) export GO386='387' ;; \ 74 | esac; \ 75 | \ 76 | wget -q -O go.tgz "https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz"; \ 77 | sha256sum *go.tgz ; \ 78 | echo '4affc3e610cd8182c47abbc5b0c0e4e3c6a2b945b55aaa2ba952964ad9df1467 *go.tgz' | sha256sum -c -; \ 79 | tar -C /usr/local -xzf go.tgz; \ 80 | rm go.tgz; \ 81 | \ 82 | cd /usr/local/go/src; \ 83 | for p in /go-alpine-patches/*.patch; do \ 84 | [ -f "$p" ] || continue; \ 85 | patch -p2 -i "$p"; \ 86 | done; \ 87 | ./make.bash; \ 88 | \ 89 | rm -rf /go-alpine-patches; \ 90 | apk del .build-deps; \ 91 | \ 92 | export PATH="/usr/local/go/bin:$PATH"; \ 93 | go version 94 | fi 95 | -------------------------------------------------------------------------------- /test/docker_restart_agent_member_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | // TestDockerMultipleRestartAgentMember tests the default case of starting a cluster with 5 members and restarting all of them 31 | // In that case only 3 agents are started and 2 non-agents 32 | func TestDockerMultipleRestartAgentMember(t *testing.T) { 33 | testMatch(t, testModeDocker, starterModeCluster, false) 34 | 35 | // NOTE: Raise the iteration count to 10 to reproduce the issue 36 | for i := 0; i < 1; i++ { 37 | // Cleanup previous tests 38 | removeDockerContainersByLabel(t, "starter-test=true") 39 | removeStarterCreatedDockerContainers(t) 40 | removeDockerVolumesByLabel(t, "starter-test=true") 41 | 42 | members := map[int]MembersConfig{ 43 | 6000: {createDockerID("s6000-"), 6000, SetUniqueDataDir(t), nil, nil}, 44 | 7000: {createDockerID("s7000-"), 7000, SetUniqueDataDir(t), nil, nil}, 45 | 8000: {createDockerID("s8000-"), 8000, SetUniqueDataDir(t), nil, nil}, 46 | 9000: {createDockerID("s9000-"), 9000, SetUniqueDataDir(t), nil, nil}, 47 | 10000: {createDockerID("s10000-"), 10000, SetUniqueDataDir(t), nil, nil}, 48 | } 49 | 50 | joins := "localhost:6000,localhost:7000,localhost:8000" 51 | 52 | for k, m := range members { 53 | createDockerVolume(t, m.ID) 54 | 55 | m.Process = spawnMemberInDocker(t, m.Port, m.ID, joins, "", "") 56 | members[k] = m 57 | } 58 | 59 | waitForCluster(t, members, time.Now()) 60 | 61 | t.Logf("Verify setup.json after fresh start, iteration: %d", i) 62 | verifyEndpointSetup(t, members) 63 | verifyDockerSetupJson(t, members, 3) 64 | 65 | for j := 0; j < 1; j++ { 66 | t.Run("Restart all members", func(t *testing.T) { 67 | for k := range members { 68 | require.NoError(t, members[k].Process.Kill()) 69 | } 70 | 71 | time.Sleep(3 * time.Second) 72 | 73 | for k := range members { 74 | removeDockerContainer(t, members[k].ID) 75 | } 76 | 77 | for k, m := range members { 78 | m.Process = spawnMemberInDocker(t, m.Port, m.ID, joins, "", "") 79 | members[k] = m 80 | } 81 | 82 | waitForCluster(t, members, time.Now()) 83 | 84 | t.Logf("Verify setup member restart") 85 | verifyDockerSetupJson(t, members, 3) 86 | verifyEndpointSetup(t, members) 87 | }) 88 | } 89 | 90 | waitForCallFunction(t, getShutdownCalls(members)...) 91 | removeDockerVolumesByLabel(t, "starter-test=true") 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/features_util.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "context" 25 | "os" 26 | "testing" 27 | "time" 28 | 29 | "github.com/rs/zerolog" 30 | "github.com/stretchr/testify/require" 31 | 32 | "github.com/arangodb-helper/arangodb/pkg/arangodb" 33 | "github.com/arangodb-helper/arangodb/pkg/docker" 34 | "github.com/arangodb-helper/arangodb/service" 35 | ) 36 | 37 | var ( 38 | // test mode -> detected DB features 39 | supportedDBFeatures = make(map[string]*service.DatabaseFeatures) 40 | ) 41 | 42 | func getSupportedDatabaseFeatures(t *testing.T, testMode string) service.DatabaseFeatures { 43 | dbFeatures, ok := supportedDBFeatures[testMode] 44 | if !ok || dbFeatures == nil { 45 | t.Logf("Detecting arangod version for mode %s...", testMode) 46 | log := zerolog.New(zerolog.NewConsoleWriter()) 47 | 48 | arangodPath, _ := arangodb.FindExecutable(log, "arangod", "/usr/sbin/arangod", false) 49 | v, enterprise, err := service.DatabaseVersion(context.Background(), log, arangodPath, makeRunner(t, log, testMode)) 50 | require.NoError(t, err) 51 | t.Logf("Detected arangod %s, enterprise: %v", v, enterprise) 52 | 53 | f := service.NewDatabaseFeatures(v, enterprise) 54 | supportedDBFeatures[testMode] = &f 55 | } 56 | 57 | return *supportedDBFeatures[testMode] 58 | } 59 | 60 | func makeRunner(t *testing.T, log zerolog.Logger, testMode string) service.Runner { 61 | var err error 62 | runner := service.NewProcessRunner(log) 63 | if testMode == testModeDocker { 64 | dockerImage := os.Getenv("ARANGODB") 65 | if dockerImage == "" { 66 | dockerImage = os.Getenv("DOCKER_IMAGE") 67 | } 68 | dockerConfig := service.DockerConfig{ 69 | Endpoint: "unix:///var/run/docker.sock", 70 | GCDelay: time.Minute, 71 | TTY: true, 72 | ImageArangoD: dockerImage, 73 | } 74 | if docker.IsRunningInDocker() { 75 | info, err := docker.FindDockerContainerInfo(dockerConfig.Endpoint) 76 | if err == nil { 77 | dockerConfig.HostContainerName = info.Name 78 | } 79 | } 80 | 81 | runner, err = service.NewDockerRunner(log, dockerConfig) 82 | require.NoError(t, err, "could not create docker runner") 83 | } 84 | return runner 85 | } 86 | -------------------------------------------------------------------------------- /test/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "fmt" 27 | "sync" 28 | "testing" 29 | "time" 30 | ) 31 | 32 | var ( 33 | loggerMutex sync.Mutex 34 | loggers = map[*testing.T]Logger{} 35 | ) 36 | 37 | func cleanLogger(t *testing.T) { 38 | loggerMutex.Lock() 39 | defer loggerMutex.Unlock() 40 | 41 | delete(loggers, t) 42 | } 43 | 44 | func getLogger(parent *logger, t *testing.T) Logger { 45 | loggerMutex.Lock() 46 | defer loggerMutex.Unlock() 47 | 48 | if l, ok := loggers[t]; ok { 49 | return l 50 | } 51 | 52 | l := &logger{ 53 | start: time.Now(), 54 | t: t, 55 | parent: parent, 56 | } 57 | 58 | loggers[t] = l 59 | return l 60 | } 61 | 62 | type Logger interface { 63 | Log(format string, args ...interface{}) 64 | 65 | SubLogger(t *testing.T) Logger 66 | Checkpoint() Logger 67 | 68 | Clean() 69 | } 70 | 71 | type logger struct { 72 | start time.Time 73 | t *testing.T 74 | 75 | parent *logger 76 | } 77 | 78 | func (l *logger) Checkpoint() Logger { 79 | return &logger{ 80 | start: time.Now(), 81 | t: l.t, 82 | parent: l, 83 | } 84 | } 85 | 86 | func (l *logger) Clean() { 87 | cleanLogger(l.t) 88 | } 89 | 90 | func (l *logger) getParent() *logger { 91 | if l == nil || l.parent == nil { 92 | return nil 93 | } 94 | 95 | if p := l.parent.getParent(); p == nil { 96 | return l 97 | } else { 98 | return p 99 | } 100 | } 101 | 102 | func (l *logger) Log(format string, args ...interface{}) { 103 | line := fmt.Sprintf(format, args...) 104 | if p := l.getParent(); p == nil { 105 | line = fmt.Sprintf("Started: %s > %s", time.Now().Sub(l.start), line) 106 | } else { 107 | line = fmt.Sprintf("Started: %s, In Test: %s > %s", time.Now().Sub(p.start).String(), time.Now().Sub(l.start).String(), line) 108 | } 109 | 110 | l.t.Log(line) 111 | println(line) 112 | } 113 | 114 | func (l *logger) SubLogger(t *testing.T) Logger { 115 | return getLogger(l, t) 116 | } 117 | 118 | func GetLogger(t *testing.T) Logger { 119 | return getLogger(nil, t) 120 | } 121 | -------------------------------------------------------------------------------- /test/passthrough_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "context" 27 | "os" 28 | "regexp" 29 | "testing" 30 | "time" 31 | ) 32 | 33 | // TestPassthroughConflict runs `arangodb --starter.local --all.ssl.keyfile=foo` 34 | func TestPassthroughConflict(t *testing.T) { 35 | testMatch(t, testModeProcess, starterModeCluster, false) 36 | dataDir := SetUniqueDataDir(t) 37 | defer os.RemoveAll(dataDir) 38 | 39 | child := Spawn(t, "${STARTER} --starter.local --all.ssl.keyfile=foo") 40 | defer child.Close() 41 | 42 | expr := regexp.MustCompile("is essential to the starters behavior and cannot be overwritten") 43 | ctx := context.Background() 44 | if err := child.ExpectTimeout(ctx, time.Second*15, expr, "starter-passthrough"); err != nil { 45 | t.Errorf("Expected errors message, got %#v", err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/process_check_exitcodes_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2022 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "context" 25 | "os" 26 | "regexp" 27 | "testing" 28 | "time" 29 | 30 | "github.com/stretchr/testify/require" 31 | ) 32 | 33 | // TestProcessErrExitCodeHandler runs in 'single' mode with invalid ArangoD configuration and 34 | // checks that exit code is recognized by starter 35 | func TestProcessErrExitCodeHandler(t *testing.T) { 36 | removeArangodProcesses(t) 37 | testMatch(t, testModeProcess, starterModeSingle, false) 38 | dataDir := SetUniqueDataDir(t) 39 | defer os.RemoveAll(dataDir) 40 | 41 | child := Spawn(t, "${STARTER} --starter.mode=single --args.all.config=invalidvalue "+createEnvironmentStarterOptions()) 42 | defer child.Close() 43 | 44 | re := regexp.MustCompile("has failed 1 times, giving up") 45 | require.NoError(t, child.ExpectTimeout(context.Background(), time.Second*20, re, "")) 46 | 47 | if isVerbose { 48 | t.Log("Waiting for termination") 49 | } 50 | require.NoError(t, child.WaitTimeout(time.Second*10), "Starter is not stopped in time") 51 | } 52 | -------------------------------------------------------------------------------- /test/process_cluster_diff_ports_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "os" 27 | "testing" 28 | "time" 29 | ) 30 | 31 | func TestProcessClusterDifferentPorts(t *testing.T) { 32 | removeArangodProcesses(t) 33 | testMatch(t, testModeProcess, starterModeCluster, false) 34 | dataDirMaster := SetUniqueDataDir(t) 35 | defer os.RemoveAll(dataDirMaster) 36 | 37 | start := time.Now() 38 | 39 | master := Spawn(t, "${STARTER} --starter.port=6000 "+createEnvironmentStarterOptions()) 40 | defer master.Close() 41 | 42 | dataDirSlave1 := SetUniqueDataDir(t) 43 | defer os.RemoveAll(dataDirSlave1) 44 | slave1 := Spawn(t, "${STARTER} --starter.join 127.0.0.1:6000 --starter.port=7000 "+createEnvironmentStarterOptions()) 45 | defer slave1.Close() 46 | 47 | dataDirSlave2 := SetUniqueDataDir(t) 48 | defer os.RemoveAll(dataDirSlave2) 49 | slave2 := Spawn(t, "${STARTER} --starter.join 127.0.0.1:6000 --starter.port=8000 "+createEnvironmentStarterOptions()) 50 | defer slave2.Close() 51 | 52 | if ok := WaitUntilStarterReady(t, whatCluster, 3, master, slave1, slave2); ok { 53 | t.Logf("Cluster start took %s", time.Since(start)) 54 | testCluster(t, "http://localhost:6000", false) 55 | testCluster(t, "http://localhost:7000", false) 56 | testCluster(t, "http://localhost:8000", false) 57 | } 58 | 59 | if isVerbose { 60 | t.Log("Waiting for termination") 61 | } 62 | SendIntrAndWait(t, master, slave1, slave2) 63 | } 64 | -------------------------------------------------------------------------------- /test/process_cluster_local_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "os" 27 | "testing" 28 | "time" 29 | ) 30 | 31 | // TestProcessClusterLocal runs `arangodb --starter.local` 32 | func TestProcessClusterLocal(t *testing.T) { 33 | removeArangodProcesses(t) 34 | testMatch(t, testModeProcess, starterModeCluster, false) 35 | dataDir := SetUniqueDataDir(t) 36 | defer os.RemoveAll(dataDir) 37 | 38 | start := time.Now() 39 | 40 | child := Spawn(t, "${STARTER} --starter.local "+createEnvironmentStarterOptions()) 41 | defer child.Close() 42 | 43 | if ok := WaitUntilStarterReady(t, whatCluster, 1, child); ok { 44 | t.Logf("Cluster start took %s", time.Since(start)) 45 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 46 | testCluster(t, insecureStarterEndpoint(1*portIncrement), false) 47 | testCluster(t, insecureStarterEndpoint(2*portIncrement), false) 48 | } 49 | 50 | if isVerbose { 51 | t.Log("Waiting for termination") 52 | } 53 | SendIntrAndWait(t, child) 54 | } 55 | 56 | // TestProcessClusterLocal runs `arangodb --starter.local`, stopping it through the `/shutdown` API. 57 | func TestProcessClusterLocalShutdownViaAPI(t *testing.T) { 58 | removeArangodProcesses(t) 59 | testMatch(t, testModeProcess, starterModeCluster, false) 60 | dataDir := SetUniqueDataDir(t) 61 | defer os.RemoveAll(dataDir) 62 | 63 | start := time.Now() 64 | 65 | child := Spawn(t, "${STARTER} --starter.local "+createEnvironmentStarterOptions()) 66 | defer child.Close() 67 | 68 | if ok := WaitUntilStarterReady(t, whatCluster, 1, child); ok { 69 | t.Logf("Cluster start took %s", time.Since(start)) 70 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 71 | testCluster(t, insecureStarterEndpoint(1*portIncrement), false) 72 | testCluster(t, insecureStarterEndpoint(2*portIncrement), false) 73 | } 74 | 75 | waitForCallFunction(t, 76 | ShutdownStarterCall(insecureStarterEndpoint(0*portIncrement))) 77 | } 78 | 79 | // TestOldProcessClusterLocal runs `arangodb --local` 80 | func TestOldProcessClusterLocal(t *testing.T) { 81 | removeArangodProcesses(t) 82 | testMatch(t, testModeProcess, starterModeCluster, false) 83 | dataDir := SetUniqueDataDir(t) 84 | defer os.RemoveAll(dataDir) 85 | 86 | start := time.Now() 87 | 88 | child := Spawn(t, "${STARTER} --local "+createEnvironmentStarterOptions()) 89 | defer child.Close() 90 | 91 | if ok := WaitUntilStarterReady(t, whatCluster, 1, child); ok { 92 | t.Logf("Cluster start took %s", time.Since(start)) 93 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 94 | testCluster(t, insecureStarterEndpoint(1*portIncrement), false) 95 | testCluster(t, insecureStarterEndpoint(2*portIncrement), false) 96 | } 97 | 98 | if isVerbose { 99 | t.Log("Waiting for termination") 100 | } 101 | SendIntrAndWait(t, child) 102 | } 103 | -------------------------------------------------------------------------------- /test/process_cluster_multi_join_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "os" 27 | "testing" 28 | "time" 29 | ) 30 | 31 | // TestProcessClusterMultipleJoins creates a cluster by starting 3 starters with all 3 32 | // starter addresses as join argument. 33 | func TestProcessClusterMultipleJoins(t *testing.T) { 34 | removeArangodProcesses(t) 35 | testMatch(t, testModeProcess, starterModeCluster, false) 36 | dataDirMaster := SetUniqueDataDir(t) 37 | defer os.RemoveAll(dataDirMaster) 38 | 39 | start := time.Now() 40 | 41 | joins1 := "--starter.join=localhost:6000,localhost:7000,localhost:8000 " 42 | master := Spawn(t, "${STARTER} --starter.port=6000 "+joins1+createEnvironmentStarterOptions()) 43 | defer master.Close() 44 | 45 | dataDirSlave1 := SetUniqueDataDir(t) 46 | defer os.RemoveAll(dataDirSlave1) 47 | joins2 := "--starter.join=localhost:8000,localhost:7000,localhost:6000 " 48 | slave1 := Spawn(t, "${STARTER} --starter.port=7000 "+joins2+createEnvironmentStarterOptions()) 49 | defer slave1.Close() 50 | 51 | dataDirSlave2 := SetUniqueDataDir(t) 52 | defer os.RemoveAll(dataDirSlave2) 53 | joins3 := "--starter.join=localhost:7000,localhost:6000 " 54 | slave2 := Spawn(t, "${STARTER} --starter.port=8000 "+joins3+createEnvironmentStarterOptions()) 55 | defer slave2.Close() 56 | 57 | if ok := WaitUntilStarterReady(t, whatCluster, 3, master, slave1, slave2); ok { 58 | t.Logf("Cluster start took %s", time.Since(start)) 59 | testCluster(t, "http://localhost:6000", false) 60 | testCluster(t, "http://localhost:7000", false) 61 | testCluster(t, "http://localhost:8000", false) 62 | } 63 | 64 | if isVerbose { 65 | t.Log("Waiting for termination") 66 | } 67 | SendIntrAndWait(t, master, slave1, slave2) 68 | } 69 | -------------------------------------------------------------------------------- /test/process_cluster_upgrade_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2018-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "context" 25 | "os" 26 | "testing" 27 | "time" 28 | ) 29 | 30 | // TestProcessClusterUpgrade starts a master starter, followed by 2 slave starters. 31 | // Once running, it starts a database upgrade. 32 | func TestProcessClusterUpgrade(t *testing.T) { 33 | removeArangodProcesses(t) 34 | testMatch(t, testModeProcess, starterModeCluster, false) 35 | dataDirMaster := SetUniqueDataDir(t) 36 | defer os.RemoveAll(dataDirMaster) 37 | 38 | start := time.Now() 39 | 40 | master := Spawn(t, "${STARTER} "+createEnvironmentStarterOptions()) 41 | defer master.Close() 42 | 43 | dataDirSlave1 := SetUniqueDataDir(t) 44 | defer os.RemoveAll(dataDirSlave1) 45 | slave1 := Spawn(t, "${STARTER} --starter.join 127.0.0.1 "+createEnvironmentStarterOptions()) 46 | defer slave1.Close() 47 | 48 | dataDirSlave2 := SetUniqueDataDir(t) 49 | defer os.RemoveAll(dataDirSlave2) 50 | slave2 := Spawn(t, "${STARTER} --starter.join 127.0.0.1 "+createEnvironmentStarterOptions()) 51 | defer slave2.Close() 52 | 53 | if ok := WaitUntilStarterReady(t, whatCluster, 3, master, slave1, slave2); ok { 54 | t.Logf("Cluster start took %s", time.Since(start)) 55 | testCluster(t, insecureStarterEndpoint(0*portIncrement), false) 56 | testCluster(t, insecureStarterEndpoint(1*portIncrement), false) 57 | testCluster(t, insecureStarterEndpoint(2*portIncrement), false) 58 | } 59 | 60 | testUpgradeProcess(t, insecureStarterEndpoint(0*portIncrement)) 61 | 62 | if isVerbose { 63 | t.Log("Waiting for termination") 64 | } 65 | SendIntrAndWait(t, master, slave1, slave2) 66 | } 67 | 68 | func testUpgradeProcess(t *testing.T, endpoint string) { 69 | c := NewStarterClient(t, endpoint) 70 | ctx := context.Background() 71 | 72 | waitForStarter(t, c) 73 | WaitUntilCoordinatorReadyAPI(t, insecureStarterEndpoint(0*portIncrement)) 74 | WaitUntilCoordinatorReadyAPI(t, insecureStarterEndpoint(1*portIncrement)) 75 | WaitUntilCoordinatorReadyAPI(t, insecureStarterEndpoint(2*portIncrement)) 76 | 77 | t.Log("Starting database upgrade") 78 | 79 | if err := c.StartDatabaseUpgrade(ctx, false); err != nil { 80 | t.Fatalf("StartDatabaseUpgrade failed: %v", err) 81 | } 82 | // Wait until upgrade complete 83 | recentErrors := 0 84 | deadline := time.Now().Add(time.Minute * 10) 85 | for { 86 | status, err := c.UpgradeStatus(ctx) 87 | if err != nil { 88 | recentErrors++ 89 | if recentErrors > 20 { 90 | t.Fatalf("UpgradeStatus failed: %s", err) 91 | } else { 92 | t.Logf("UpgradeStatus failed: %s", err) 93 | } 94 | } else { 95 | recentErrors = 0 96 | if status.Failed { 97 | t.Fatalf("Upgrade failed: %s", status.Reason) 98 | } 99 | if status.Ready { 100 | if isVerbose { 101 | t.Logf("UpgradeStatus good: %v", status) 102 | } 103 | break 104 | } 105 | } 106 | if time.Now().After(deadline) { 107 | t.Fatal("Upgrade failed to finish in time") 108 | } 109 | time.Sleep(time.Second) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/process_database_version_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Ewout Prangsma 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "context" 27 | "os" 28 | "testing" 29 | "time" 30 | ) 31 | 32 | // TestProcessDatabaseVersion runs `arangodb --starter.mode=single` 33 | // and tries a /database-version request. 34 | func TestProcessDatabaseVersion(t *testing.T) { 35 | removeArangodProcesses(t) 36 | testMatch(t, testModeProcess, starterModeSingle, false) 37 | dataDir := SetUniqueDataDir(t) 38 | defer os.RemoveAll(dataDir) 39 | 40 | start := time.Now() 41 | 42 | child := Spawn(t, "${STARTER} --starter.mode=single "+createEnvironmentStarterOptions()) 43 | defer child.Close() 44 | 45 | if ok := WaitUntilStarterReady(t, whatSingle, 1, child); ok { 46 | t.Logf("Single server start took %s", time.Since(start)) 47 | testSingle(t, insecureStarterEndpoint(0*portIncrement), false) 48 | } 49 | 50 | if isVerbose { 51 | t.Log("Waiting for termination") 52 | } 53 | 54 | c := NewStarterClient(t, insecureStarterEndpoint(0*portIncrement)) 55 | ctx := context.Background() 56 | if v, err := c.DatabaseVersion(ctx); err != nil { 57 | t.Errorf("DatabaseVersion failed: %#v", err) 58 | } else { 59 | t.Logf("Got database-version %s", v) 60 | } 61 | 62 | SendIntrAndWait(t, child) 63 | } 64 | -------------------------------------------------------------------------------- /test/process_restart_agent_member_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | // TestDockerMultipleRestartAgentMember tests the default case of starting a cluster with 5 members and restarting all of them 31 | // In that case only 3 agents are started and 2 non-agents 32 | func TestProcessAgentsMultipleRestart(t *testing.T) { 33 | removeArangodProcesses(t) 34 | testMatch(t, testModeProcess, starterModeCluster, false) 35 | 36 | members := map[int]MembersConfig{ 37 | 6000: {"node1", 6000, SetUniqueDataDir(t), nil, nil}, 38 | 7000: {"node2", 7000, SetUniqueDataDir(t), nil, nil}, 39 | 8000: {"node3", 8000, SetUniqueDataDir(t), nil, nil}, 40 | 9000: {"node4", 9000, SetUniqueDataDir(t), nil, nil}, 41 | 10000: {"node5", 10000, SetUniqueDataDir(t), nil, nil}, 42 | } 43 | 44 | joins := "localhost:6000,localhost:7000,localhost:8000" 45 | for port, m := range members { 46 | m.Process = spawnMemberProcess(t, m.Port, m.DataDir, joins, "") 47 | members[port] = m 48 | } 49 | 50 | waitForCluster(t, members, time.Now()) 51 | 52 | t.Logf("Verify setup.json after fresh start") 53 | verifyProcessSetupJson(t, members, 3) 54 | verifyEndpointSetup(t, members) 55 | 56 | for i := 0; i < 1; i++ { 57 | t.Logf("Restart all members, iteration: %d", i) 58 | t.Run("Restart all members", func(t *testing.T) { 59 | for port := range members { 60 | require.NoError(t, members[port].Process.Kill()) 61 | } 62 | time.Sleep(3 * time.Second) 63 | 64 | for port, m := range members { 65 | m.Process = spawnMemberProcess(t, m.Port, m.DataDir, joins, "") 66 | members[port] = m 67 | } 68 | 69 | waitForCluster(t, members, time.Now()) 70 | 71 | t.Logf("Verify setup after member restart, iteration: %d", i) 72 | verifyProcessSetupJson(t, members, 3) 73 | verifyEndpointSetup(t, members) 74 | }) 75 | } 76 | 77 | waitForCallFunction(t, getShutdownCalls(members)...) 78 | } 79 | -------------------------------------------------------------------------------- /test/process_util.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | 21 | package test 22 | 23 | import ( 24 | "fmt" 25 | "strings" 26 | "testing" 27 | 28 | "github.com/rs/zerolog" 29 | "github.com/stretchr/testify/require" 30 | 31 | "github.com/arangodb-helper/arangodb/service" 32 | ) 33 | 34 | func removeArangodProcesses(t *testing.T) { 35 | t.Log("Removing arangod processes") 36 | listArangodProcesses(t, GetLogger(t)) 37 | c := SpawnWithExpand(t, "sh -c 'PIDS=$(pidof arangod); if [ ! -z \"${PIDS}\" ]; then kill -9 ${PIDS}; fi'", false) 38 | defer c.Close() 39 | err := c.Wait() 40 | if err != nil { 41 | t.Errorf("Failed to kill arangod processes: %v", err) 42 | } else { 43 | t.Log("Successfully killed arangod processes") 44 | } 45 | } 46 | 47 | func closeProcess(t *testing.T, s *SubProcess, name string) { 48 | s.Close() 49 | 50 | showProcessLogs(t, s, name) 51 | } 52 | 53 | func listArangodProcesses(t *testing.T, log Logger) { 54 | c := SpawnWithExpand(t, "pidof arangod", false) 55 | defer c.Close() 56 | c.Wait() 57 | 58 | logProcessOutput(log, c, "Processes: ") 59 | } 60 | 61 | func showProcessLogs(t *testing.T, s *SubProcess, name string) { 62 | if !t.Failed() { 63 | return 64 | } 65 | 66 | log := GetLogger(t) 67 | 68 | logProcessOutput(log, s, "Log of process: %s", name) 69 | } 70 | 71 | func spawnMemberProcess(t *testing.T, port int, dataDir, joins, extraArgs string) *SubProcess { 72 | return Spawn(t, strings.Join([]string{ 73 | fmt.Sprintf("${STARTER} --starter.port=%d", port), 74 | fmt.Sprintf("--starter.data-dir=%s", dataDir), 75 | fmt.Sprintf("--starter.join=%s", joins), 76 | createEnvironmentStarterOptions(), 77 | extraArgs, 78 | }, " ")) 79 | 80 | } 81 | 82 | func verifyProcessSetupJson(t *testing.T, members map[int]MembersConfig, expectedAgents int) { 83 | for _, m := range members { 84 | cfg, isRelaunch, err := service.ReadSetupConfig(zerolog.New(zerolog.NewConsoleWriter()), m.DataDir) 85 | require.NoError(t, err, "Failed to read setup.json, member: %d", m.Port) 86 | require.True(t, isRelaunch, "Expected relaunch, member: %d", m.Port) 87 | 88 | t.Logf("Verify setup.json for member: %d, %s", m.Port, m.DataDir) 89 | verifySetupJsonForMember(t, members, expectedAgents, cfg, m) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/testdata/activefailover.conf: -------------------------------------------------------------------------------- 1 | # This is a config comment 2 | 3 | starter.mode = activefailover -------------------------------------------------------------------------------- /test/testdata/invalidkeys.conf: -------------------------------------------------------------------------------- 1 | # This is a config comment 2 | 3 | starter.mode = activefailover 4 | starter.nosuchkey = activefailover -------------------------------------------------------------------------------- /test/testdata/invalidvalues.conf: -------------------------------------------------------------------------------- 1 | # This is a config comment 2 | 3 | starter.mode = 10 -------------------------------------------------------------------------------- /test/testdata/single-passthrough-persistent-new.conf: -------------------------------------------------------------------------------- 1 | 2 | [args] 3 | dbservers.database.extended-names-databases = false 4 | -------------------------------------------------------------------------------- /test/testdata/single-passthrough-persistent-old.conf: -------------------------------------------------------------------------------- 1 | 2 | [args] 3 | dbservers.database.extended-names-databases = true 4 | -------------------------------------------------------------------------------- /test/testdata/single-passthrough.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | args.all.log.level = startup=trace 4 | args.all.log.level = warning 5 | 6 | 7 | # a comment on section 8 | [starter] 9 | mode = single 10 | 11 | [args] 12 | all.log.level = queries=debug 13 | all.default-language = de_DE 14 | 15 | [args.all.rocksdb] 16 | enable-statistics = true 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/testdata/single-sections.conf: -------------------------------------------------------------------------------- 1 | 2 | # a comment on section 3 | [starter] 4 | mode = single -------------------------------------------------------------------------------- /test/testdata/single.conf: -------------------------------------------------------------------------------- 1 | # This is a config comment 2 | 3 | starter.mode = single -------------------------------------------------------------------------------- /test/timeout.go: -------------------------------------------------------------------------------- 1 | // 2 | // DISCLAIMER 3 | // 4 | // Copyright 2021 ArangoDB GmbH, Cologne, Germany 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | // Copyright holder is ArangoDB GmbH, Cologne, Germany 19 | // 20 | // Author Adam Janikowski 21 | // 22 | 23 | package test 24 | 25 | import ( 26 | "fmt" 27 | "testing" 28 | "time" 29 | 30 | "github.com/stretchr/testify/require" 31 | ) 32 | 33 | func NewThrottle(interval time.Duration) Throttle { 34 | return &throttle{ 35 | interval: interval, 36 | } 37 | } 38 | 39 | type Throttle interface { 40 | Execute(func()) 41 | } 42 | 43 | type throttle struct { 44 | last time.Time 45 | interval time.Duration 46 | } 47 | 48 | func (t *throttle) Execute(f func()) { 49 | n := time.Now() 50 | if n.After(t.last.Add(t.interval)) { 51 | f() 52 | t.last = n 53 | } 54 | } 55 | 56 | type TimeoutFunc func() error 57 | 58 | func NewTimeoutFunc(f func() error) TimeoutFunc { 59 | return f 60 | } 61 | 62 | func (f TimeoutFunc) Execute(timeout, interval time.Duration) error { 63 | if err := f(); err != nil { 64 | if IsInterrupt(err) { 65 | return nil 66 | } 67 | 68 | return err 69 | } 70 | 71 | timeoutT := time.NewTimer(timeout) 72 | defer timeoutT.Stop() 73 | 74 | intervalT := time.NewTicker(interval) 75 | defer intervalT.Stop() 76 | 77 | for { 78 | select { 79 | case <-timeoutT.C: 80 | return fmt.Errorf("timeout") 81 | case <-intervalT.C: 82 | if err := f(); err != nil { 83 | if IsInterrupt(err) { 84 | return nil 85 | } 86 | 87 | return err 88 | } 89 | } 90 | } 91 | } 92 | 93 | func (f TimeoutFunc) ExecuteWithLog(log Logger, timeout, interval time.Duration) (err error) { 94 | now := time.Now() 95 | 96 | defer func() { 97 | if err == nil { 98 | log.Log("Success - took %s", time.Now().Sub(now).String()) 99 | } else { 100 | log.Log("Error - took %s - %s", time.Now().Sub(now).String(), err.Error()) 101 | } 102 | }() 103 | 104 | err = f.Execute(timeout, interval) 105 | return 106 | } 107 | 108 | func (f TimeoutFunc) ExecuteTWithLog(t *testing.T, log Logger, timeout, interval time.Duration) { 109 | require.NoError(t, f.ExecuteWithLog(log, timeout, interval)) 110 | } 111 | 112 | func (f TimeoutFunc) ExecuteT(t *testing.T, timeout, interval time.Duration) { 113 | require.NoError(t, f.Execute(timeout, interval)) 114 | } 115 | 116 | type Interrupt struct { 117 | } 118 | 119 | func (i Interrupt) Error() string { 120 | return "interrupt" 121 | } 122 | 123 | func NewInterrupt() error { 124 | return Interrupt{} 125 | } 126 | 127 | func IsInterrupt(err error) bool { 128 | _, ok := err.(Interrupt) 129 | return ok 130 | } 131 | -------------------------------------------------------------------------------- /versioninfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "StringFileInfo": { 3 | "CompanyName": "ArangoDB GmbH", 4 | "FileDescription": "ArangoDB Starter allows to start and manage ArangoDB clusters with ease.", 5 | "LegalCopyright": "Copyright 2017-2024 ArangoDB GmbH, Cologne, Germany", 6 | "InternalName": "arangodb.exe", 7 | "Comments": "Source code can be found at https://github.com/arangodb-helper/arangodb", 8 | "ProductName": "arangodb-starter", 9 | "OriginalFilename": "arangodb.exe" 10 | } 11 | } --------------------------------------------------------------------------------