{"id":19281746,"url":"https://github.com/scality/ballot","last_synced_at":"2025-04-22T01:31:06.540Z","repository":{"id":44933451,"uuid":"329741754","full_name":"scality/ballot","owner":"scality","description":null,"archived":false,"fork":false,"pushed_at":"2025-01-07T00:03:57.000Z","size":2855,"stargazers_count":4,"open_issues_count":12,"forks_count":0,"subscribers_count":39,"default_branch":"development/1.0","last_synced_at":"2025-04-01T17:53:34.226Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scality.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-14T21:44:18.000Z","updated_at":"2024-07-04T22:13:39.000Z","dependencies_parsed_at":"2024-06-19T22:52:25.234Z","dependency_job_id":"1e7d39f7-1d08-4270-909a-85121e0e583c","html_url":"https://github.com/scality/ballot","commit_stats":{"total_commits":18,"total_committers":1,"mean_commits":18.0,"dds":0.0,"last_synced_commit":"81505616297b98947470b1352e3a8d19598ee67d"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scality%2Fballot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scality%2Fballot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scality%2Fballot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scality%2Fballot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scality","download_url":"https://codeload.github.com/scality/ballot/tar.gz/refs/heads/development/1.0","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250161969,"owners_count":21385014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T21:24:01.752Z","updated_at":"2025-04-22T01:31:05.508Z","avatar_url":"https://github.com/scality.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ballot\n\n## Overview\n\n`ballot` ensures that it's the only one running a command, by running a leader\nelection on a ZooKeeper cluster. Others that try to run the same command (based\non ZooKeeper path) will wait until the active `ballot` releases leadership.\n\nSome example use cases include:\n\n- A distributed cron service, where jobs need to run at specific times or time\n  intervals, and cannot overlap, but still needs to failover to standby\n  instances on failure. In this case `ballot` acts as\n  [lockrun](http://www.unixwiz.net/tools/lockrun.html) would on a single-server\n  system.\n- Any singleton service, that would be a kubernetes `Deployment` of 1 replica,\n  if kubernetes is not an option, or if running on kubernetes and trying to\n  achieve failover faster than the control plane can re-schedule pods.\n\n### Examples\n\n```shell\nballot run once --candidate-id `hostname` -- /usr/bin/env LD_PRELOAD=trickle.so command-with-exclusive-resource\nballot run cron --candidate-id `hostname` --schedule='@daily' -- /usr/bin/env LD_PRELOAD=trickle.so command-with-exclusive-resource\n```\n\n## Pre-requisites\n\nA running and healthy [ZooKeeper](https://zookeeper.apache.org/) ensemble is\nrequired. Only plain unauthenticated connections are supported at this time.\n\n## Configuration\n\n`ballot` relies on\n[viper](https://github.com/spf13/viper)/[cobra](https://github.com/spf13/cobra)\nfor command line and configuration, and thus can be configured using command\nline arguments, environment variables, and configuration files. Remote\nconfiguration sources are not available at this time.\n\nSeveral configuration sources can be mixed, as long as parameters are not\nduplicated between sources. The following are all equivalent:\n\n* Running `ballot --config-file ./ballot.yaml run [...]` with `ballot.yaml`\n  containing:\n\n    ```yaml\n    ---\n    zookeeper-servers:\n    - server1\n    - server2\n    - 'server3:2181'\n    zookeeper-base-path: /com/scality/backbeat/singleton\n    zookeeper-session-timeout: 5s\n    log-level: debug\n    log-format: json\n    ```\n\n* Running\n\n    ```shell\n    env \\\n        ZOOKEEPER_SERVERS=server1,server2,server3:2181 \\\n        ZOOKEEPER_BASE_PATH=/com/scality/backbeat/singleton \\\n        ZOOKEEPER_SESSION_TIMEOUT=5s\n        LOG_LEVEL=debug \\\n        LOG_FORMAT=json \\\n        ballot run [...]\n    ```\n\n* Running\n\n    ```shell\n    ballot \\\n        --zookeeper-servers server1 \\\n        --zookeeper-servers server2 \\\n        --zookeeper-servers server3:2181 \\\n        --zookeeper-base-path /com/scality/backbeat/singleton \\\n        --zookeeper-session-timeout 5s \\\n        --log-level debug \\\n        --log-format json \\\n        run [...]\n    ```\n\nGlobal configuration:\n\n| Parameter | Description | Default | Allowed values |\n| --- | --- | --- | --- |\n| `config-file` | Configuration file | `~/.config/ballot/ballot.json`, `~/.config/ballot/ballot.yaml`, `/etc/ballot/ballot.json`, `/etc/ballot/ballot.yaml`, `./ballot.json`, `./ballot.yaml` | Any path to a valid yaml or json ballot configuration |\n| `zookeeper-servers` | List of ZooKeeper servers | `localhost` | List of `server` or `server:port` |\n| `zookeeper-base-path` | Base path to ZooKeeper election proposal nodes | `/ballot/election` | Any valid, otherwise unused ZooKeeper path |\n| `zookeeper-session-timeout` | ZooKeeper session timeout, used to detect purge stale election members | `5s` | Any duration between 2 and 20 times ZooKeeper's `tickTime` |\n| `log-level` | Log level | `info` | `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic` |\n| `log-format` | Logs format | `human` | `human`, `json`, `raw` |\n| `debug` | Debug mode with extra checks and logging | `false` | `false`, `true` |\n\n## Usage\n\n### Run Once\n\n`run once` runs the provided command. Use `--` to separate `ballot`'s own\narguments from the command's executable and arguments if they contain `-`'s\n(which is very likely).\n\nSetting parameters `on-child-success` and `on-election-failure` to `reelect` or\n`rerun` will cause the command to be run several times. In this case, `once`\nmeans \"not scheduled to run regularly\", and is not a guarantee of only one run.\n\nNote: If ZooKeeper goes down while a `ballot` process is a leader and others are\nwaiting as followers, they will all stay in their respective roles and operate\nuntil ZooKeeper comes back. They will, however, not be able to perform any\nfailover or run new commands until ZooKeeper is back up.\n\n#### Options\n\n| Parameter | Description | Default | Allowed values |\n| --- | --- | --- | --- |\n| `candidate-id` | This `ballot`'s ID, to be used for display and debugging purposes. Uniqueness of IDs between `ballot`s is not required, but makes inspecting easier. | N/A, mandatory | Any string |\n| `on-child-success` | What to do when the command we run terminates with exit code 0. | `reelect` | `rerun`, `reelect`, `exit`, `ignore` |\n| `on-child-error` | What to do when the command we run terminates with an exit code other than 0, or cannot be executed for any reason. | `reelect` | `rerun`, `reelect`, `exit`, `ignore` |\n| `on-election-failure` | What to do when the election process fails for technical reasons (unreachable ZooKeeper, for example). Use `run-anyway` only if running too many processes at once is preferable to none at all. | `retry` | `retry`, `run-anyway`, `exit` |\n| `wrap-child-logs` | Whether to display the command's logs formatted as our own logs, or have its `stdout` and `stderr` directly connected to `ballot`'s | `true` | `false`, `true` |\n\n### Run Cron\n\n`run cron` runs the provided command on a schedule. Use `--` to separate `ballot`'s own\narguments from the command's executable and arguments if they contain `-`'s\n(which is very likely).\n\nLeader election is performed once and all timed invocations happen on the node\nthat was elected leader. Elections do not happen for each invocation, unless\n`on-child-success`/`on-child-error` are set to `reelect`.\n\nThe only supported concurrency policy at the moment is to skip an invocation if\nthe previous invocation is still running.\n\nNote: If ZooKeeper goes down while a `ballot` process is a leader and others are\nwaiting as followers, they will all stay in their respective roles and operate\nuntil ZooKeeper comes back. They will, however, not be able to perform any\nfailover in the event that the leader goes down.\n\nFuture features can include adding a deadline and killing overdue jobs, allowing\ninvocation queueing, allowing concurrent invocations.\n\n#### Options\n\n| Parameter | Description | Default | Allowed values |\n| --- | --- | --- | --- |\n| `schedule` | Schedule to run on. | N/A, mandatory | [Extended Cron format](https://pkg.go.dev/github.com/robfig/cron#hdr-CRON_Expression_Format) |\n| `candidate-id` | This `ballot`'s ID, to be used for display and debugging purposes | N/A, mandatory. Uniqueness of IDs between `ballot`s is not required, but makes inspecting easier. | Any string |\n| `on-child-success` | What to do when the command we run terminates with exit code 0. | `reelect` | `rerun`, `reelect`, `exit`, `ignore` |\n| `on-child-error` | What to do when the command we run terminates with an exit code other than 0, or cannot be executed for any reason. | `reelect` | `rerun`, `reelect`, `exit`, `ignore` |\n| `on-election-failure` | What to do when the election process fails for technical reasons (unreachable ZooKeeper, for example). Use `run-anyway` only if running too many processes at once is preferable to none at all. | `retry` | `retry`, `run-anyway`, `exit` |\n| `wrap-child-logs` | Whether to display the command's logs formatted as our own logs, or have its `stdout` and `stderr` directly connected to `ballot`'s | `true` | `false`, `true` |\n\n#### Examples\n\n### Info\n\nThe info command shows the current state of the election:\n\n- The current leader\n- The list of candidates\n- Data about each entry (proposal, last seen time, host, pid, candidate id, etc)\n- Notes about each entry that could have an effect on the current election.\n  These notes often include conditional phrasing, because they are observations\n  based on heuristics, as it is often not possible to know what really is\n  happening until later.\n\n#### Options\n\nSee Global Configuration for common options.\n\n#### Examples\n\nHere is an example of an election where the leader just resigned (for example\nbecause its child process completed successfully, and the configured policy is\n`reelect`). We can see that `candidate-2` is well positioned to be the leader,\nbut is prevented by `candidate-5` which still holds a lease (`last heartbeat\n2.242563s ago`). When `candidate-5`'s heartbeat misses (2.75s from now, or\n`sessionTimeout/2` after its last heartbeat), `candidate-2` will proceed with\nleadership.\n\n```yaml\n$ ballot --zookeeper-servers localhost:2181 --zookeeper-base-path /ballot/myservice info\nleader:\n  id: candidate-5\n  host: host-5\n  pid: \"25048\"\n  proposalTime: 2022-01-07 13:40:17.537208 -0800 PST m=+1.119309658\n  proposalNode: /ballot/myservice/proposal-0000031785\n  sessionTimeout: 10s\n  notes:\n  - is leader\n  - last heartbeat 2.242563s ago\n  - possibly resigning\n  - election may be in progress\n  lastSeenTime: 2022-01-07 13:40:47.55661 -0800 PST m=+31.138087501\nallCandidates:\n- id: candidate-2\n  host: host-2\n  pid: \"5967\"\n  proposalTime: 2022-01-07 13:40:17.540773 -0800 PST m=+1.111020024\n  proposalNode: /ballot/myservice/proposal-0000031786\n  sessionTimeout: 10s\n  notes:\n  - should be leader but another candidate is claiming the role\n  - possibly waiting for previous leader resignation\n  - proposal node created 32.259036s ago\n- id: candidate-3\n  host: host-3\n  pid: \"6516\"\n  proposalTime: 2022-01-07 13:40:17.540755 -0800 PST m=+1.112006091\n  proposalNode: /ballot/myservice/proposal-0000031787\n  sessionTimeout: 10s\n  notes:\n  - proposal node created 32.259585s ago\n- id: candidate-1\n  host: host-1\n  pid: \"152\"\n  proposalTime: 2022-01-07 13:40:17.540778 -0800 PST m=+1.113612800\n  proposalNode: /ballot/myservice/proposal-0000031788\n  sessionTimeout: 10s\n  notes:\n  - proposal node created 32.260036s ago\n- id: candidate-4\n  host: host-4\n  pid: \"8053\"\n  proposalTime: 2022-01-07 13:40:17.540928 -0800 PST m=+1.123033944\n  proposalNode: /ballot/myservice/proposal-0000031789\n  sessionTimeout: 10s\n  notes:\n  - proposal node created 32.260125s ago\n- id: candidate-5\n  host: host-5\n  pid: \"4389\"\n  proposalTime: 2022-01-07 13:40:47.827953 -0800 PST m=+31.409424660\n  proposalNode: /ballot/myservice/proposal-0000031790\n  sessionTimeout: 10s\n  notes:\n  - this candidate id claimed leadership\n  - proposal node created 1.973348s ago\n\n```\n\n### Watch\n\nNot implemented yet.\n\n#### Options\n\n| Parameter | Description | Default | Allowed values |\n| --- | --- | --- | --- |\n\n#### Examples\n\n## Contributing\n\nIn order to contribute, please follow the\n[Contributing Guidelines](\nhttps://github.com/scality/Guidelines/blob/master/CONTRIBUTING.md).\n\n[Design\nDoc](https://github.com/scality/citadel/blob/development/1.0/docs/design/ballot-launcher.md).\n\nTODO: Developer instructions to be written here.\n\n## License\n\nLicensed under the [Apache 2.0 license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscality%2Fballot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscality%2Fballot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscality%2Fballot/lists"}