Skip to content

Instantly share code, notes, and snippets.

@XavM
Last active August 29, 2015 14:13
Show Gist options
  • Save XavM/b3032be9f38e4cdf4f82 to your computer and use it in GitHub Desktop.
Save XavM/b3032be9f38e4cdf4f82 to your computer and use it in GitHub Desktop.
dhcp_ct : A "dhcp like" client written in bash and relying on consul catalog (Requires curl, jq, ping and ... consul)
#!/bin/bash
#############
## dhcp_ct : A "dhcp like" client written in bash and relying on consul catalog (Requires curl, jq and ... consul)
## Usage : $> ./dhcp_ct datacenter host_name_prefix ip_pool
## Ex : $> ./dhcp_ct dc1 ct 192.168.0.{1..254} # <- Would register a new node in consul catalog using a free IP and HOSTNAME
## STDOUT : ct1 192.168.0.88
#############
## Fail fast and be aware of exit codes
set -eo pipefail
## Define globals
declare -r SCRIPT_NAME="${0##*/}";
declare -r CURL="curl -sS --connect-timeout 2";
declare -r CONSUL_ENDPOINT="http://consul.service.lan.com/v1";
declare SESSION_ID="";
declare RESERVED_IP="";
declare RESERVED_HOSTNAME="";
declare -a BLACKLISTED_IP="";
declare -a BLACKLISTED_HOSTNAME="";
## Delete consul session and locked keys on exit
trap clean_up EXIT
clean_up() {
[[ ${RESERVED_IP} != "" ]] \
&& delete_key "${SCRIPT_NAME}/${RESERVED_IP}"
[[ ${RESERVED_HOSTNAME} != "" ]] \
&& delete_key "${SCRIPT_NAME}/${RESERVED_HOSTNAME}"
[[ ${#BLACKLISTED_IP[@]} != 0 ]] \
&& for key in ${BLACKLISTED_IP[@]}; do
delete_key "${SCRIPT_NAME}/${key}";
done
[[ ${#BLACKLISTED_HOSTNAME[@]} != 0 ]] \
&& for key in ${BLACKLISTED_HOSTNAME[@]}; do
delete_key "${SCRIPT_NAME}/${key}";
done
[[ ${SESSION_ID} != "" ]] \
&& destroy_session "${SESSION_ID}"
}
## Get a free IP
get_ip() {
local ip_pool=${1};
## Extract known IPs as a list
local used_ips
used_ips="$(get_nodes_liste '.Address')";
## Get the first un used IP from the provided ${ip_pool}
for ip in ${ip_pool}; do
[[ ${used_ips} == *" ${ip} "* ]] \
|| {
## Temporary lock this free IP in kv store
reserve_resource ${ip} \
&& {
## Make sure the found IP is not Pingable
is_pingable "${ip}" \
&& {
echo >&2 -e "[WARNING] ${ip} already in use but unknown from consul";
BLACKLISTED_IP=("${BLACKLISTED_IP[@]}" "${ip}")
} \
|| {
RESERVED_IP=${ip};
break;
}
}
};
done;
## Exit 1 when no IP is available
[[ ${RESERVED_IP} == "" ]] \
&& {
echo >&2 "[CRITICAL] IP pool exhausted";
exit 1;
} \
|| return 0;
}
## Get a free HOSTNAME
get_hostname() {
local hostname_prefix=${1};
## Extract known host_names as a list
local used_hostnames;
used_hostnames=" $(get_nodes_liste '.Node')";
## Get the first un used host_name from the host_name_prefix
for host in ${hostname_prefix}{1..254}; do
[[ ${used_hostnames} == *" ${host} "* ]] \
|| {
## Temporary lock this free HOSTNAME in kv store
reserve_resource ${host} \
&& {
## Make sure the found hostname is not Pingable
is_pingable "${host}" \
&& {
echo >&2 "[WARNING] ${host} already in use but unknown from consul";
BLACKLISTED_HOSTNAME=("${BLACKLISTED_HOSTNAME[@]}" "${host}")
} \
|| {
RESERVED_HOSTNAME=${host};
break;
}
}
};
done;
## Exit 1 when no hostname is available
[[ ${RESERVED_HOSTNAME} == "" ]] \
&& {
echo >&2 "[CRITICAL] hostname pool exhausted";
exit 1;
} \
|| return 0;
}
## Extract known nodes from consul catalog
get_nodes_liste() {
local filter="\" \"+ .[]${1} +\" \"";
${CURL} ${CONSUL_ENDPOINT}/catalog/nodes \
| jq -j "${filter}"
}
is_pingable() {
local resource=${1};
ping -c 3 -i 0.5 -W 3 ${resource} > /dev/null 2>&1 \
&& return 0 \
|| return 1
}
## Create a consul session (needed for locks)
create_session() {
[[ "${SESSION_ID}" == "" ]] \
&& {
local consul_session
consul_session=$(${CURL} -X PUT ${CONSUL_ENDPOINT}/session/create)
SESSION_ID=$(echo ${consul_session} | /usr/bin/jq -r '.ID')
}
}
## Destroy the consul session ${SESSION_ID}
destroy_session() {
[[ "${SESSION_ID}" != "" ]] \
&& ${CURL} -X PUT "${CONSUL_ENDPOINT}/session/destroy/${SESSION_ID}" > /dev/null
}
## Create and lock a key in the consul kv store
lock_key() {
local key=$1
local value=${2:-${SCRIPT_NAME}}
local consul_lock
consul_lock=$(${CURL} -X PUT "${CONSUL_ENDPOINT}/kv/${key}?acquire=${SESSION_ID}" -d "${value}")
## Return true or false wether we can get the lock
[[ ${consul_lock} == "true" ]] \
|| {
#echo >&2 "[WARNING] Can't acquire lock on key ${key}";
return 1
}
}
## Delete (and unlock) a key in the consul kv store
delete_key() {
local key=$1
${CURL} -X DELETE "${CONSUL_ENDPOINT}/kv/${key}"
}
## Avoid any race condition between multiple "dhcp_ct" client fighting for the same IP/HOSTNAME
reserve_resource() {
local resource="${1}";
create_session;
lock_key "${SCRIPT_NAME}/${resource}" \
|| return 1
}
## This last one was necessary as '?cas=0' don't work with the consul catalog endpoint
resource_exists() {
local resource="${1}";
local known_resources;
known_resources=$(${CURL} ${CONSUL_ENDPOINT}/catalog/nodes \
| jq -j '.[] | .Address +" "+ .Node +" "');
[[ ${known_resources} == *" ${resource} "* ]] \
&& return 0 \
|| return 1;
}
## Register a new node in consul catalog with ${RESERVED_IP} and ${RESERVED_HOSTNAME}
register_node() {
local datacenter="${1}"
local host_name="$2";
local ip="$3";
local payload='{
"Datacenter": "'${datacenter}'",
"Node": "'${host_name}'",
"Address": "'${ip}'"
}'
## Don't register a node already know from consul catalog or using an IP already referenced in the catalog
( resource_exists ${host_name} || resource_exists ${ip} ) \
&& {
echo >&2 "[CRTITICAL] node ${host_name} or ip ${ip} already registered in consul catalog";
exit 1;
} \
|| ${CURL} ${CONSUL_ENDPOINT}/catalog/register -d "$payload" > /dev/null
}
main() {
local datacenter="${1}"; shift
local host_name_prefix="${1}"; shift
local ip_pool="${@}";
get_ip "${ip_pool}";
get_hostname "${host_name_prefix}";
register_node ${datacenter} ${RESERVED_HOSTNAME} ${RESERVED_IP};
## Output hostname and IP to stdout
echo "${RESERVED_HOSTNAME} ${RESERVED_IP}"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment