LoginSignup
9
4

More than 3 years have passed since last update.

nerves_pack(vintage_net含む)を使ってNerves起動時に`Node.connect()`するようにした

Last updated at Posted at 2020-03-19

vintage_netを含むnerves_packを使ってこのようなことをしていました。今回はNerves起動時にNode.connect()するようにしました。これは Nervesで起動時にNode.connect()するようにした のvintage_net版になります。個人的にはきっとこれからこっちを使うことになると思ってます。

追記

mix local.nervesしてnerves_bootstrap 1.8.0にアップデートしたら暗黙の動作が増えたようなのでバグ対応を含めbootexineris.exファイルを修正しました。

環境

ハードウェアとネットワークはこのような構成です。
今回もホスト名flamboise03を操作することにします。
nerves_packのインストールと設定は「nerves_pack(vintage_net含む)を使ってNervesのネットワーク設定をした〜SSHログインまで〜」をみてください。

ハードウェア

  • Mac OSX
  • NervesをRaspberryPi 3で稼働

ネットワーク

  • Mac OSX

    • 有線LAN→メンテナンス用、固定IPアドレス(192.168.5.100/24)
  • RaspberryPi 3(flamboise03)

    • 有線LAN(eth0)→メンテナンス用、固定IPアドレス(192.168.5.57/24)
    • 無線LAN(wlan0)→インターネット接続用、DHCP(192.168.46/24から自動割当) ネットワーク構成図.png

このようにしました

IPアドレスの設定は完了しているとして、次のファイルをそれぞれ追記、作成します。

  • lib/exineris002/application.ex:nerves起動時の設定を追記
  • lib/exineris002/bootexineris.ex:Node.connectするプログラム

前回の Nervesで起動時にNode.connect()するようにした の時はNerves起動時に接続した後、再接続する仕組みがなかったので今回は再接続の仕組みを追加しています。

lib/exineris002/application.ex

Bootexinerisモジュールにリストを渡して起動します。
リストは左から「自分のnode_name」「クッキー(共通鍵)」「接続先」としています。

lib/exineris002/application.ex
  def children(_target) do
    [
      # {Bootexineris, [node_name, cookie, conn_node]}
      {Bootexineris, ["exima", "comecomeeverybody", "kazumambp@192.168.46.100"]}
    ]
  end

lib/exineris002/bootexineris.ex

  • Nerves起動時の動作(キーワード::wakeup, nodeconn/2
    • Nerves起動後1秒おきに接続しようとする
    • wlan0インタフェースにDHCPよりIPアドレスが割り当てられるのを確認後、Node.connect/1の手続きを実施
    • 接続完了後、接続確認の動作に遷移
  • 接続確認の動作(キーワード::alive, re_nodeconn/2
    • 60秒おきに接続を確認
    • 正常時 → 再度60秒接続を確認を実施 (Node.listに接続先が存在している && Node.pingで応答がある場合 → :node_alive
    • 接続が切れた後に接続先が復帰した場合 → 再接続を実施し失敗した場合は1秒おきに再接続を実施 (Node.listに接続先が存在していない && Node.pngで応答がある場合 → :node_re_conn
    • 接続が切れている場合 → 1秒おきに再接続を実施 (Node.listに接続先が存在していない && Node.pingで応答がない場合 → :node_down
lib/exineris002/bootexineris.ex
defmodule Bootexineris do
  use GenServer
  require Logger

  @interval_init_ms 1_000
  @interval_wakeup_ms 1_000
  @interval_alive_ms 60_000
  @interval_alive_false_ms 1_000

  def start_link(node_option \\ []) do
    # to init/1
    GenServer.start_link(__MODULE__, node_option, name: __MODULE__)
  end

  def init(node_option) do
    set_interval(:init, @interval_init_ms)
    {:ok, node_option}
  end

  def set_interval(msg, ms) do
    # to handle_info/2
    Process.send_after(self(), msg, ms)
  end

  def handle_info(:init, node_option) do
    init_nodeconn(wlan0_ready?(), node_option)
    {:noreply, node_option}
  end

  def handle_info(:wakeup, node_option) do
    nodeconn(node_option)
    {:noreply, node_option}
  end

  def handle_info(:alive, node_option) do
    re_nodeconn(conn_node_alive?(node_option), node_option)
    {:noreply, node_option}
  end

  defp init_nodeconn(true, [node_name, cookie, _]) do
    node_host = get_ipaddr_wlan0()
    System.cmd("epmd", ["-daemon"])
    Node.start(:"#{node_name}@#{node_host}")
    Node.set_cookie(:"#{cookie}")

    Logger.info("=== Node.start -> #{node_name}@#{node_host} ===")
    Logger.info("=== Node.set_cookie -> #{cookie} ===")

    case [node_start?(), node_set_cookie?()] do
      [true, true] ->
        Logger.info("=== init_nodeconn -> success! Node.start & Node.set ===")
        set_interval(:wakeup, @interval_wakeup_ms)

      [_, _] ->
        Logger.info("=== init_nodeconn -> false, node_start(#{inspect(node_start?())}), node_set_cookie(#{inspect(node_set_cookie?())}) ===")
        set_interval(:init, @interval_init_ms)
    end
  end

  defp init_nodeconn(false, [_, _, _]) do
    Logger.info("=== init_nodeconn -> false, wlan0_ready(#{inspect(wlan0_ready?())}) ===")
    set_interval(:init, @interval_init_ms)
  end

  defp nodeconn([_, _, conn_node]) do
    conn = Node.connect(:"#{conn_node}")
    Logger.info("=== Node.connect -> try connect to #{conn_node} ===")

    case conn do
      true ->
        Logger.info("=== nodeconn -> #{conn} ===")
        set_interval(:alive, @interval_alive_ms)

      _ ->
        set_interval(:wakeup, @interval_wakeup_ms)
    end
  end

  defp re_nodeconn(:node_alive, _) do
    set_interval(:alive, @interval_alive_ms)
  end

  defp re_nodeconn(:node_re_conn, [_, _, conn_node]) do
    conn = Node.connect(:"#{conn_node}")
    Logger.info("=== re_nodeconn Node.connect -> #{conn_node} ===")

    case conn do
      true ->
        Logger.info("=== re_nodeconn -> #{conn} ===")
        set_interval(:alive, @interval_alive_ms)

      _ ->
        set_interval(:alive, @interval_alive_false_ms)
    end
  end

  defp re_nodeconn(:node_down, [_, _, conn_node]) do
    Logger.debug("=== re_nodeconn -> false... try connect to #{conn_node} ====")
    set_interval(:alive, @interval_alive_false_ms)
  end

  def node_start?() do
    case Node.self() do
      :nonode@nohost -> false
      _ -> true
    end
  end

  def node_set_cookie?() do
    case Node.get_cookie() do
      :nocookie -> false
      _ -> true
    end
  end

  def conn_node_alive?([_, _, conn_node]) do
    case [conn_node_list_find?(conn_node), conn_node_ping?(conn_node)] do
      [true, true] -> :node_alive
      [false, true] -> :node_re_conn
      [_, _] -> :node_down
    end
  end

  def conn_node_list_find?(conn_node) do
    case Node.list() |> Enum.find(fn x -> x == :"#{conn_node}" end) do
      nil -> false
      _ -> true
    end
  end

  def conn_node_ping?(conn_node) do
    case Node.ping(:"#{conn_node}") do
      :pang -> false
      :pong -> true
    end
  end

  def wlan0_ready?() do
    case get_ipaddr_wlan0() do
      nil -> false
      _ -> true
    end
  end

  def get_ipaddr_wlan0() do
    case VintageNet.get_by_prefix(["interface", "wlan0", "addresses"]) do
      [] ->
        nil

      [list_int_wlan0_addr] ->
        list_int_wlan0_addr
        |> (fn {_, list_settings} -> list_settings end).()
        |> hd()
        |> Map.get(:address)
        |> VintageNet.IP.ip_to_string()
    end
  end
end

確認

OSXのターミナルでiexを起動します。

osx
$ iex --name kazumambp@192.168.46.100 --cookie comecomeeverybody

iex(kazumambp@192.168.46.100)> Node.list
[]

次にNervesを起動します。
bootexineris.exがうまく動いていれば自動的にNode.connectして、OSXとNervesのNode.listにお互いが登録されているはずです。

nerves
$ ssh 192.168.5.55

iex> …①
iex(exima@192.168.46.11)> …②
iex(exima@192.168.46.11)> Node.list …③
[:"kazumambp@192.168.46.100"]

①Nerves起動直後はNode.startしてないのでこの表示
②wlan0がIPアドレスをDHCPサーバより取得するとNode.startしてNode.set_cookie後、Node.connectする
③Node.listして接続の確認

NervesでNode.connectできていることを確認できたのでOSXでも確認します。

osx
iex(kazumambp@192.168.46.100)> Node.list
[:"exima@192.168.46.11"]

Node.connectしていることを確認できました!!

結果は省きますが、以下の機能もあります。

  • Node.connect先のOSXがNervesより後に起動した場合の接続
  • Node.connect先のOSXがダウンして再起動した場合の再接続

まとめ

Nerves起動時にNode.connect()できるようになりました!
vintage_netを使った今回の方が 前回 より関連するファイルが少なく導入が楽なのかなぁという印象です。安定しててほしいなぁ。

defdefpの使い分けが適当になってきてるので整理したいなー。

9
4
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4