The easiest way to get started is by starting with an existing agent and remove most of the code in set set_power_status(), get_power_status(), and get_list() and then update the connection and main() code.
Good choices for existing agents to use as base for new agents:
- fence_vmware_rest for REST API
- fence_ilo for other HTTP(S) APIs
- fence_ilo_ssh for SSH
- fence_ipmilan for command tools
- fence_aws/fence_azure (does imports in separate library)/fence_gce for external libraries
## git
### General
- Commit messages should start with "fence_<name>: ", "fencing: " (or other library name), "build: ".\
If in doubt run `git log` to find examples.
- PRs without correct prefix will get squashed to correct it as part of the merge process.
- If any parameters or descriptions has been updated you'll have to run `./autogen.sh && ./configure` followed by `make xml-upload`.
- Build requirements can be easily installed on Fedora by running `sudo dnf builddep fence-agents-all`.
### Add agent
- Go to <https://github.com/ClusterLabs/fence-agents> and click Fork to create your own Fork.
git commit -a -c "fence_<name>: fix <issue-description>"
git push
```
- Click link and create Pull Request.
## Development
### Import libraries/add state dictionary
To develop you need to import the fencing library and some generic libraries, and define expected values for power states that will be translated translated to "on", "off", or "error".
The state dictionary contains translations from expected values from the fence device and their corresponding value in fencing (on/off/error).
Example:
```
import sys
import atexit
import logging
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS
state = {"POWERED_ON": "on", 'POWERED_OFF': "off", 'SUSPENDED': "off"}
```
***only import used functions/return values from the fencing library in the 2nd `from fencing import` line***
### Logging and failing
Use logging.error(), logging.warn(), logging.debug(), and logging.info() to log output.
- logging.info() should only be used when needed to avoid spamming the logs.
- logging.debug() is good for debugging (shown when you use -v or set verbose=1).
- `if options["--verbose-level"] > 1:` can be used to only print additional logging.debug() messages when -vv (or more v's) or verbose=2 or higher.
Use `fail(<error-code>)` or `fail_usage("Failed: <error message>")` to exit with error code message or log Failed: <error message> with generic error code.
- fail() logs the specified error message from <https://github.com/ClusterLabs/fence-agents/blob/main/lib/fencing.py.py#L574> and exit with generic error code.
- EC_* error codes can be imported from the fencing library:\
- To exit with specific error codes either use logging.error() followed by sys.exit(<error-code>) or fail(<error-code>, stop=False), which allows you to return or sys.exit() with other error codes.
- These functions defines the code to get or set status, and get list of nodes, which are run by the fencing library to turn off/on nodes or get a list of nodes available.
```
def get_power_status(conn, options):
# code to get power status
result = ...
return state[result]
def set_power_status(conn, options):
action = {
"on" : "start",
"off" : "stop"
}[options["--action"]]
try:
# code to set power status. use "action" variable, which translates on and off to the equivalent command that the device expects
"WARNING! This fence agent might report success before the node is powered off, when cycle method is used. " \
"You should use -m/method onoff if your fence device works correctly with that option."
...
result = fence_action(None, options, set_power_status, get_power_status, None, reboot_cycle)
```
### define_new_opts()
Specifies device specific parameters with defaults.
- getopt is ":" for parameters that require a value and "" for parameters that get set without a value (e.g. -v)
- do not use short short opts (e.g. "a:" or "a") as they are reserved for the fencing library
- set required to 1 if the parameter is required (except for -o metadata or -h/--help)
- Use `if "--<parameter>" in options:` to check value-less parameters like --verbose and `if options.get("--<parameter>") == "<expected-value>":` to check the value of parameters.
```
def define_new_opts():
all_opt["api_path"] = {
"getopt" : ":",
"longopt" : "api-path",
"help" : "--api-path=[path] The path part of the API URL",
"default" : "/rest",
"required" : "0",
"shortdesc" : "The path part of the API URL",
"order" : 2}
all_opt["filter"] = {
"getopt" : ":",
"longopt" : "filter",
"help" : "--filter=[filter] Filter to only return relevant VMs"