Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend release spec #46

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions lib/control_node/host.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ defmodule ControlNode.Host do
end
end

@spec init_release(SSH.t(), binary, atom) :: :ok | :failure | {:error, any}
def init_release(%SSH{} = host_spec, init_file, command) do
with {:ok, %SSH.ExecStatus{exit_code: 0}} <-
SSH.exec(host_spec, "nohup #{init_file} #{command} &", true) do
@spec init_release(SSH.t(), binary) :: :ok | :failure | {:error, any}
def init_release(%SSH{} = host_spec, exec_binary) do
with {:ok, %SSH.ExecStatus{exit_code: 0}} <- SSH.exec(host_spec, exec_binary, true) do
:ok
end
end

# TODO : check and remove
@spec stop_release(SSH.t(), binary) :: :ok | :failure | {:error, any}
def stop_release(%SSH{} = host_spec, cmd) do
with {:ok, %SSH.ExecStatus{exit_code: 0}} <- SSH.exec(host_spec, "nohup #{cmd} stop") do
with {:ok, %SSH.ExecStatus{exit_code: 0}} <- SSH.exec(host_spec, "#{cmd} stop") do
:ok
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/control_node/namespace.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule ControlNode.Namespace do
end

def start_link(namespace_spec, release_mod) do
name = :"#{namespace_spec.tag}_#{release_mod.release_name}"
name = release_mod.get_namespace_pname(namespace_spec)
Logger.debug("Starting namespace with name #{name}")
GenServer.start_link(__MODULE__, [namespace_spec, release_mod], name: name)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/control_node/namespace/connect.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule ControlNode.Namespace.Connect do
def callback_mode, do: :handle_event_function

def handle_event(any, event, state, _data) do
Logger.warn("Unexpected event #{inspect({any, event, state})}")
Logger.warning("Unexpected event #{inspect({any, event, state})}")
{:keep_state_and_data, []}
end
end
2 changes: 1 addition & 1 deletion lib/control_node/namespace/initialize.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ defmodule ControlNode.Namespace.Initialize do

{:running, 0}
else
Logger.warn("Release state loaded, expected version #{version} found #{current_version}")
Logger.warning("Release state loaded, expected version #{version} found #{current_version || "err_not_deployed"}")

{:partially_running, data.deploy_attempts}
end
Expand Down
4 changes: 2 additions & 2 deletions lib/control_node/namespace/manage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule ControlNode.Namespace.Manage do
data = %Workflow.Data{data | health_check_timer: timer_ref}
{:keep_state, data, []}
else
Logger.warn("Release health check failed")
Logger.warning("Release health check failed")

# TODO: respect max failure count before rebooting the release
if hc_spec.on_failure == :reboot do
Expand Down Expand Up @@ -106,7 +106,7 @@ defmodule ControlNode.Namespace.Manage do
end

def handle_event(any, event, state, _data) do
Logger.warn("Unexpected event #{inspect({any, event, state})}")
Logger.warning("Unexpected event #{inspect({any, event, state})}")
{:keep_state_and_data, []}
end

Expand Down
2 changes: 1 addition & 1 deletion lib/control_node/namespace/observe.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule ControlNode.Namespace.Observe do
end

def handle_event(any, event, state, _data) do
Logger.warn("Unexpected event #{inspect({any, event, state})}")
Logger.warning("Unexpected event #{inspect({any, event, state})}")
{:keep_state_and_data, []}
end
end
7 changes: 7 additions & 0 deletions lib/control_node/registry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ defmodule ControlNode.Registry do
|> File.read()
end

@doc """
Retrieves application release tar file location
"""
def location(%Local{} = registry_spec, application, version) do
Path.join(registry_spec.path, "#{application}-#{version}.tar.gz")
end

@doc """
Stores application release tar file in the filesystem
"""
Expand Down
73 changes: 56 additions & 17 deletions lib/control_node/release.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ defmodule ControlNode.Release do
name: atom,
base_path: String.t(),
start_timeout: integer,
deploy_func: :default | function(),
init_func: :default | function(),
health_check_spec: HealthCheckSpec.t()
}
defstruct name: nil,
base_path: nil,
start_timeout: 5,
deploy_func: :default,
init_func: :default,
health_check_spec: %HealthCheckSpec{}
end

Expand Down Expand Up @@ -102,6 +106,11 @@ defmodule ControlNode.Release do
|> call(:current_version)
end

@spec get_namespace_pname(Namespace.Spec.t()) :: :atom
def get_namespace_pname(%Namespace.Spec{} = namespace_spec) do
:"#{namespace_spec.tag}_#{@release_name}"
end

@doc """
Deploy a new version of the service to the given host

Expand Down Expand Up @@ -189,14 +198,17 @@ defmodule ControlNode.Release do
@spec initialize_state(Release.Spec.t(), ControlNode.Host.SSH.t(), :atom) ::
Release.State.t()
def initialize_state(release_spec, host_spec, cookie) do
with {:ok, %Host.Info{services: services}} <- Host.info(host_spec) do
case Map.get(services, release_spec.name) do
with {:ok, host_spec} <- Host.hostname(host_spec),
{:ok, %Host.Info{services: services}} <- Host.info(host_spec),
{:ok, nodename} <- to_node_name(release_spec, host_spec) do

# Check if the nodename is registered on host
case Map.get(services, to_sname(nodename)) do
nil ->
State.new(host_spec)

service_port ->
with %Host.SSH{} = host_spec <- Host.connect(host_spec),
{:ok, host_spec} <- Host.hostname(host_spec) do
with %Host.SSH{} = host_spec <- Host.connect(host_spec) do
# Setup tunnel to release port on host
# TODO/NOTE/WARN random local port should be used to avoid having a clash
# if the releases use the same port on different hosts
Expand Down Expand Up @@ -225,7 +237,7 @@ defmodule ControlNode.Release do
%State{release_state | version: version, pid: release_pid}

_ ->
Logger.warn(
Logger.warning(
"No version found for release #{release_spec.name} on host #{host_spec.host}"
)

Expand Down Expand Up @@ -299,7 +311,7 @@ defmodule ControlNode.Release do
Node.monitor(node, false)
else
_other ->
Logger.warn("Failed to demonitor node", release_spec: release_spec, host_spec: host_spec)
Logger.warning("Failed to demonitor node", release_spec: release_spec, host_spec: host_spec)
end
end

Expand All @@ -311,15 +323,20 @@ defmodule ControlNode.Release do
end

def is_running?(release_spec, host_spec) do
Logger.debug("Checking if release #{release_spec.name} is running on host", host_spec: host_spec)
case node_info(release_spec, host_spec) do
{:ok, _} -> true
_ -> false
end
end

defp node_info(release_spec, host_spec) do
with {:ok, %Host.Info{services: services}} <- Host.info(host_spec) do
case Map.get(services, release_spec.name) do
with {:ok, %Host.Info{services: services}} <- Host.info(host_spec),
{:ok, nodename} <- to_node_name(release_spec, host_spec) do

Logger.debug("Checking for node #{nodename} on host", host_spec: host_spec)

case Map.get(services, to_sname(nodename)) do
nil ->
{:error, :release_not_running}

Expand All @@ -330,10 +347,12 @@ defmodule ControlNode.Release do
end

defp register_node(release_spec, host_spec, service_port) do
{:ok, nodename} = to_node_name(release_spec, host_spec)

# NOTE: Configure host config for inet
# This config will be used by BEAM to resolve `hostname`
Inet.add_alias_for_localhost(host_spec.hostname)
Epmd.register_release(release_spec.name, host_spec.hostname, service_port)
Epmd.register_release(to_sname(nodename), host_spec.hostname, service_port)
end

defp get_version(release_spec, host_spec) do
Expand Down Expand Up @@ -373,7 +392,7 @@ defmodule ControlNode.Release do
"""
@spec deploy(Spec.t(), Host.SSH.t(), ControlNode.Registry.Local.t(), binary) ::
:ok | {:error, Host.SSH.ExecStatus.t()}
def deploy(%Spec{} = release_spec, host_spec, registry_spec, version) do
def deploy(%Spec{deploy_func: :default} = release_spec, host_spec, registry_spec, version) do
# WARN: may not work if host OS is different from control-node OS
host_release_dir = Path.join(release_spec.base_path, version)
host_release_path = Path.join(host_release_dir, "#{release_spec.name}-#{version}.tar.gz")
Expand All @@ -382,14 +401,24 @@ defmodule ControlNode.Release do
:ok <- Host.upload_file(host_spec, host_release_path, tar_file),
:ok <- Host.extract_tar(host_spec, host_release_path, host_release_dir) do
init_file = Path.join(host_release_dir, "bin/#{release_spec.name}")
Host.init_release(host_spec, init_file, :start)
init_release(release_spec, host_spec, init_file)
end
end

@spec start(Spec.t(), State.t()) :: term
def start(release_spec, %State{host: host_spec, release_path: release_path}) do
init_file = Path.join(release_path, "bin/#{release_spec.name}")
Host.init_release(host_spec, init_file, :start)
def deploy(%Spec{deploy_func: deploy_func} = release_spec, host_spec, registry_spec, version) when is_function(deploy_func) do
with {:ok, remote_init_file} <- deploy_func.(release_spec, host_spec, registry_spec, version) do
init_release(release_spec, host_spec, remote_init_file)
end
end

defp init_release(%Spec{init_func: :default}, host_spec, init_file) do
# cmd = "nohup #{init_file} start &"
cmd = "#{init_file} daemon"
Host.init_release(host_spec, cmd)
end

defp init_release(%Spec{init_func: init_func}, host_spec, init_file) do
init_func.(host_spec, init_file)
end

defp connect_and_monitor(release_spec, host_spec, cookie) do
Expand Down Expand Up @@ -426,8 +455,18 @@ defmodule ControlNode.Release do
end
end

def to_sname(nodename) do
Atom.to_string(nodename) |> String.split("@") |> hd() |> String.to_atom()
end

def to_node_name(_release_spec, %Host.SSH{hostname: nil}), do: {:error, :hostname_not_found}

def to_node_name(release_spec, host_spec),
do: {:ok, :"#{release_spec.name}@#{host_spec.hostname}"}
def to_node_name(release_spec, %Host.SSH{env_vars: env_vars} = host_spec) do
# NOTE: If the env_vars for the host defines `RELEASE_NAME` then we should
# take that over the default name
# - env_vars could be nil
sname = Map.get(env_vars || %{}, :RELEASE_NAME, release_spec.name)

{:ok, :"#{sname}@#{host_spec.hostname}"}
end
end
21 changes: 21 additions & 0 deletions test/control_node/release_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,27 @@ defmodule ControlNode.ReleaseTest do
Release.stop(release_spec, release_state)
end

test "Connects to node with custom RELEASE_NAME", %{
release_spec: release_spec,
host_spec: host_spec,
cookie: cookie
} do
custom_release_name = "service-app-dev"
host_spec = %{
host_spec | env_vars: %{RELEASE_NAME: custom_release_name}
}

assert %Release.State{
host: host_spec,
version: "0.1.0",
status: :running,
release_path: "/app/service_app/0.1.0"
} = release_state = Release.initialize_state(release_spec, host_spec, cookie)

assert :pong == Node.ping(:"#{custom_release_name}@#{host_spec.hostname}")
Release.stop(release_spec, release_state)
end

@tag :skip
# NOTE: Not sure what this test was supposed to cover :/
# remember to document next time
Expand Down
Loading