From 00022cd692bb910d75619cef0f90990517cff274 Mon Sep 17 00:00:00 2001 From: Frank Hunleth Date: Thu, 21 Apr 2022 20:47:06 -0400 Subject: [PATCH] Extract PropertyTable to its own library *Backwards incompatible - please read* PropertyTable is now its own library and it has been simplified. While some API affecting changes to PropertyTable were made in the library to fix bugs, improve performance and make it more generic, this preserves the PropertyTable semantics as much as possible to avoid application code needing to change. I don't believe any user code should need to change. The backwards incompatible part of this change is that all references to `VintageNet.PropertyTable` need to be changed to `PropertyTable`. This shouldn't affect most code, but does affect `vintage_net_wifi`. This fixes #352. --- lib/vintage_net.ex | 16 +- lib/vintage_net/application.ex | 2 +- lib/vintage_net/info.ex | 2 +- lib/vintage_net/interface.ex | 13 +- lib/vintage_net/interface/udhcpd.ex | 4 +- lib/vintage_net/interfaces_monitor/info.ex | 10 +- lib/vintage_net/property_table.ex | 166 ------------- lib/vintage_net/property_table/supervisor.ex | 30 --- lib/vintage_net/property_table/table.ex | 205 ---------------- lib/vintage_net/route/properties.ex | 6 +- lib/vintage_net/route_manager.ex | 2 +- mix.exs | 1 + mix.lock | 1 + .../connectivity/internet_checker_test.exs | 14 +- test/vintage_net/interface_test.exs | 38 +-- .../predictable_interface_name_test.exs | 2 +- test/vintage_net/property_table_test.exs | 230 ------------------ test/vintage_net/route/properties_test.exs | 4 +- 18 files changed, 59 insertions(+), 687 deletions(-) delete mode 100644 lib/vintage_net/property_table.ex delete mode 100644 lib/vintage_net/property_table/supervisor.ex delete mode 100644 lib/vintage_net/property_table/table.ex delete mode 100644 test/vintage_net/property_table_test.exs diff --git a/lib/vintage_net.ex b/lib/vintage_net.ex index 6199ee24..ae728e41 100644 --- a/lib/vintage_net.ex +++ b/lib/vintage_net.ex @@ -17,7 +17,7 @@ defmodule VintageNet do [github.com/nerves-networking/vintage_net](https://github.com/nerves-networking/vintage_net) for more information. """ - alias VintageNet.{Info, Interface, PropertyTable} + alias VintageNet.{Info, Interface} @typedoc """ A name for the network interface @@ -244,11 +244,9 @@ defmodule VintageNet do Patterns are list of strings that optionally specify `:_` at a position in the list to match any value. """ - @spec match(PropertyTable.property_with_wildcards()) :: [ - {PropertyTable.property(), PropertyTable.value()} - ] + @spec match(PropertyTable.pattern()) :: [{PropertyTable.property(), PropertyTable.value()}] def match(pattern) do - PropertyTable.match(VintageNet, pattern) + PropertyTable.match(VintageNet, pattern ++ [:"$"]) |> Enum.sort() end @doc """ @@ -260,8 +258,8 @@ defmodule VintageNet do @spec get_by_prefix(PropertyTable.property()) :: [ {PropertyTable.property(), PropertyTable.value()} ] - def get_by_prefix(prefix) do - PropertyTable.get_by_prefix(VintageNet, prefix) + def get_by_prefix(pattern) do + PropertyTable.match(VintageNet, pattern) |> Enum.sort() end @doc """ @@ -287,7 +285,7 @@ defmodule VintageNet do VintageNet.subscribe(["interface", :_, "addresses"]) ``` """ - @spec subscribe(PropertyTable.property_with_wildcards()) :: :ok + @spec subscribe(PropertyTable.pattern()) :: :ok def subscribe(name) do PropertyTable.subscribe(VintageNet, name) end @@ -295,7 +293,7 @@ defmodule VintageNet do @doc """ Stop subscribing to property change messages """ - @spec unsubscribe(PropertyTable.property_with_wildcards()) :: :ok + @spec unsubscribe(PropertyTable.pattern()) :: :ok def unsubscribe(name) do PropertyTable.unsubscribe(VintageNet, name) end diff --git a/lib/vintage_net/application.ex b/lib/vintage_net/application.ex index 530882c4..c317ba95 100644 --- a/lib/vintage_net/application.ex +++ b/lib/vintage_net/application.ex @@ -17,7 +17,7 @@ defmodule VintageNet.Application do properties = load_initial_configurations() |> Enum.map(&config_to_property/1) children = [ - {VintageNet.PropertyTable, properties: properties, name: VintageNet}, + {PropertyTable, properties: properties, name: VintageNet, tuple_events: true}, {VintageNet.PredictableInterfaceName, hw_path_ifnames}, VintageNet.PowerManager.Supervisor, {BEAMNotify, diff --git a/lib/vintage_net/info.ex b/lib/vintage_net/info.ex index 05fd0102..a95e912f 100644 --- a/lib/vintage_net/info.ex +++ b/lib/vintage_net/info.ex @@ -165,7 +165,7 @@ defmodule VintageNet.Info do defp sanitize_configuration(data), do: data defp format_if_attribute(ifname, name, print_name, print_since? \\ false) do - case VintageNet.PropertyTable.fetch_with_timestamp(VintageNet, ["interface", ifname, name]) do + case PropertyTable.fetch_with_timestamp(VintageNet, ["interface", ifname, name]) do {:ok, value, timestamp} -> [ " ", diff --git a/lib/vintage_net/interface.ex b/lib/vintage_net/interface.ex index d0efd16b..2bf1d4b2 100644 --- a/lib/vintage_net/interface.ex +++ b/lib/vintage_net/interface.ex @@ -11,7 +11,7 @@ defmodule VintageNet.Interface do use GenStateMachine alias VintageNet.Interface.{CommandRunner, RawConfig} - alias VintageNet.{Persistence, PredictableInterfaceName, PropertyTable, RouteManager} + alias VintageNet.{Persistence, PredictableInterfaceName, RouteManager} alias VintageNet.PowerManager.PMControl alias VintageNet.Technology.Null @@ -110,7 +110,7 @@ defmodule VintageNet.Interface do normalized_config = raw_config.source_config, :changed <- configuration_changed(ifname, normalized_config), :ok <- persist_configuration(ifname, normalized_config, options), - PropertyTable.put(VintageNet, ["interface", ifname, "config"], normalized_config), + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "config"], normalized_config), {:error, :already_started} <- maybe_start_interface(ifname) do GenStateMachine.call(via_name(raw_config.ifname), {:configure, raw_config}) end @@ -223,7 +223,10 @@ defmodule VintageNet.Interface do nil -> # No configuration, so use a null config and fix the property table raw_config = null_raw_config(ifname) - PropertyTable.put(VintageNet, ["interface", ifname, "config"], raw_config.source_config) + + :ok = + PropertyTable.put(VintageNet, ["interface", ifname, "config"], raw_config.source_config) + raw_config config -> @@ -731,8 +734,8 @@ defmodule VintageNet.Interface do ifname = data.ifname config = data.config - PropertyTable.put(VintageNet, ["interface", ifname, "type"], config.type) - PropertyTable.put(VintageNet, ["interface", ifname, "state"], state) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "type"], config.type) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "state"], state) if state != :configured do # Once a state is `:configured`, then the configuration provides the connection diff --git a/lib/vintage_net/interface/udhcpd.ex b/lib/vintage_net/interface/udhcpd.ex index 30bade2d..2874a682 100644 --- a/lib/vintage_net/interface/udhcpd.ex +++ b/lib/vintage_net/interface/udhcpd.ex @@ -8,7 +8,7 @@ defmodule VintageNet.Interface.Udhcpd do def lease_update(ifname, lease_file) do case parse_leases(lease_file) do {:ok, leases} -> - VintageNet.PropertyTable.put( + PropertyTable.put( VintageNet, ["interface", ifname, "dhcpd", "leases"], leases @@ -17,7 +17,7 @@ defmodule VintageNet.Interface.Udhcpd do {:error, _} -> Logger.error("#{ifname}: Failed to handle lease update from #{lease_file}") - VintageNet.PropertyTable.clear_prefix(VintageNet, ["interface", ifname, "dhcpd", "leases"]) + PropertyTable.clear_all(VintageNet, ["interface", ifname, "dhcpd", "leases"]) end end diff --git a/lib/vintage_net/interfaces_monitor/info.ex b/lib/vintage_net/interfaces_monitor/info.ex index c312e199..dfb21243 100644 --- a/lib/vintage_net/interfaces_monitor/info.ex +++ b/lib/vintage_net/interfaces_monitor/info.ex @@ -1,7 +1,7 @@ defmodule VintageNet.InterfacesMonitor.Info do @moduledoc false - alias VintageNet.{IP, PropertyTable} + alias VintageNet.IP @link_if_properties [:lower_up, :mac_address] @address_if_properties [:addresses] @@ -151,8 +151,8 @@ defmodule VintageNet.InterfacesMonitor.Info do """ @spec update_present(t()) :: t() def update_present(%__MODULE__{ifname: ifname} = info) do - PropertyTable.put(VintageNet, ["interface", ifname, "present"], true) - PropertyTable.put(VintageNet, ["interface", ifname, "hw_path"], info.hw_path) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "present"], true) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "hw_path"], info.hw_path) info end @@ -173,7 +173,7 @@ defmodule VintageNet.InterfacesMonitor.Info do end defp update_link_property(ifname, property, value) do - PropertyTable.put(VintageNet, ["interface", ifname, to_string(property)], value) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, to_string(property)], value) end @doc """ @@ -182,7 +182,7 @@ defmodule VintageNet.InterfacesMonitor.Info do @spec update_address_properties(t()) :: t() def update_address_properties(%__MODULE__{ifname: ifname, addresses: address_reports} = info) do addresses = address_reports_to_property(address_reports) - PropertyTable.put(VintageNet, ["interface", ifname, "addresses"], addresses) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "addresses"], addresses) info end diff --git a/lib/vintage_net/property_table.ex b/lib/vintage_net/property_table.ex deleted file mode 100644 index 39c84cd2..00000000 --- a/lib/vintage_net/property_table.ex +++ /dev/null @@ -1,166 +0,0 @@ -defmodule VintageNet.PropertyTable do - @moduledoc """ - PropertyTables are in-memory key-value stores - - Users can subscribe to keys or groups of keys to be notified of changes. - - Keys are hierarchically layed out with each key being represented as a list - for the path to the key. For example, to get the current state of the network - interface `eth0`, you would get the value of the key, `["net", "ethernet", - "eth0"]`. - - Values can be any Elixir data structure except for `nil`. `nil` is used to - identify non-existent keys. Therefore, setting a property to `nil` deletes - the property. - - Users can get and listen for changes in multiple keys by specifying prefix - paths. For example, if you wants to get every network property, run: - - PropertyTable.get_by_prefix(table, ["net"]) - - Likewise, you can subscribe to changes in the network status by running: - - PropertyTable.subscribe(table, ["net"]) - - Properties can include metadata. `PropertyTable` only specifies that metadata - is a map. - """ - - alias VintageNet.PropertyTable.Table - - @typedoc """ - A table_id identifies a group of properties - """ - @type table_id() :: atom() - - @typedoc """ - Properties - """ - @type property :: [String.t()] - @type property_with_wildcards :: [String.t() | :_] - @type value :: any() - @type property_value :: {property(), value()} - @type metadata :: map() - - @type options :: [name: table_id(), properties: [property_value()]] - - @spec start_link(options()) :: {:ok, pid} | {:error, term} - def start_link(options) do - name = Keyword.get(options, :name) - - unless !is_nil(name) and is_atom(name) do - raise ArgumentError, "expected :name to be given and to be an atom, got: #{inspect(name)}" - end - - VintageNet.PropertyTable.Supervisor.start_link(options) - end - - @doc """ - Returns a specification to start a property_table under a supervisor. - See `Supervisor`. - """ - @spec child_spec(keyword()) :: Supervisor.child_spec() - def child_spec(opts) do - %{ - id: Keyword.get(opts, :name, PropertyTable), - start: {VintageNet.PropertyTable, :start_link, [opts]}, - type: :supervisor - } - end - - @doc """ - Subscribe to receive events - """ - @spec subscribe(table_id(), property_with_wildcards()) :: :ok - def subscribe(table, name) when is_list(name) do - assert_property_with_wildcards(name) - - registry = VintageNet.PropertyTable.Supervisor.registry_name(table) - {:ok, _} = Registry.register(registry, :property_registry, name) - - :ok - end - - @doc """ - Stop subscribing to a property - """ - @spec unsubscribe(table_id(), property_with_wildcards()) :: :ok - def unsubscribe(table, name) when is_list(name) do - registry = VintageNet.PropertyTable.Supervisor.registry_name(table) - Registry.unregister(registry, :property_registry) - end - - @doc """ - Get the current value of a property - """ - @spec get(table_id(), property(), value()) :: value() - def get(table, name, default \\ nil) when is_list(name) do - assert_property(name) - Table.get(table, name, default) - end - - @doc """ - Fetch a property with the time that it was set - - Timestamps come from `System.monotonic_time()` - """ - @spec fetch_with_timestamp(table_id(), property()) :: {:ok, value(), integer()} | :error - def fetch_with_timestamp(table, name) when is_list(name) do - assert_property(name) - Table.fetch_with_timestamp(table, name) - end - - @doc """ - Get a list of all properties matching the specified prefix - """ - @spec get_by_prefix(table_id(), property()) :: [{property(), value()}] - def get_by_prefix(table, prefix) when is_list(prefix) do - assert_property(prefix) - - Table.get_by_prefix(table, prefix) - end - - @doc """ - Get a list of all properties matching the specified prefix - """ - @spec match(table_id(), property_with_wildcards()) :: [{property(), value()}] - def match(table, pattern) when is_list(pattern) do - assert_property_with_wildcards(pattern) - - Table.match(table, pattern) - end - - @doc """ - Update a property and notify listeners - """ - @spec put(table_id(), property(), value(), metadata()) :: :ok - def put(table, name, value, metadata \\ %{}) when is_list(name) do - Table.put(table, name, value, metadata) - end - - @doc """ - Clear out a property - """ - defdelegate clear(table, name), to: Table - - @doc """ - Clear out all properties under a prefix - """ - defdelegate clear_prefix(table, name), to: Table - - defp assert_property(name) do - Enum.each(name, fn - v when is_binary(v) -> :ok - :_ -> raise ArgumentError, "Wildcards not allowed in this property" - _ -> raise ArgumentError, "Property should be a list of strings" - end) - end - - defp assert_property_with_wildcards(name) do - Enum.each(name, fn - v when is_binary(v) -> :ok - :_ -> :ok - _ -> raise ArgumentError, "Property should be a list of strings" - end) - end -end diff --git a/lib/vintage_net/property_table/supervisor.ex b/lib/vintage_net/property_table/supervisor.ex deleted file mode 100644 index 338e935f..00000000 --- a/lib/vintage_net/property_table/supervisor.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule VintageNet.PropertyTable.Supervisor do - @moduledoc false - use Supervisor - - alias VintageNet.PropertyTable - - @spec start_link(PropertyTable.options()) :: Supervisor.on_start() - def start_link(options) do - Supervisor.start_link(__MODULE__, options) - end - - @impl Supervisor - def init(options) do - name = Keyword.fetch!(options, :name) - properties = Keyword.get(options, :properties, []) - registry_name = registry_name(name) - - children = [ - {VintageNet.PropertyTable.Table, {name, registry_name, properties}}, - {Registry, [keys: :duplicate, name: registry_name]} - ] - - Supervisor.init(children, strategy: :one_for_one) - end - - @spec registry_name(PropertyTable.table_id()) :: Registry.registry() - def registry_name(name) do - Module.concat(PropertyTable.Registry, name) - end -end diff --git a/lib/vintage_net/property_table/table.ex b/lib/vintage_net/property_table/table.ex deleted file mode 100644 index 3d5511a8..00000000 --- a/lib/vintage_net/property_table/table.ex +++ /dev/null @@ -1,205 +0,0 @@ -defmodule VintageNet.PropertyTable.Table do - @moduledoc false - use GenServer - - alias VintageNet.PropertyTable - - @spec start_link( - {PropertyTable.table_id(), Registry.registry(), [PropertyTable.property_value()]} - ) :: GenServer.on_start() - def start_link({table, _registry_name, _properties} = args) do - GenServer.start_link(__MODULE__, args, name: table) - end - - @spec get(PropertyTable.table_id(), PropertyTable.property(), PropertyTable.value()) :: - PropertyTable.value() - def get(table, name, default) do - case :ets.lookup(table, name) do - [{^name, value, _timestamp}] -> value - [] -> default - end - end - - @spec fetch_with_timestamp(PropertyTable.table_id(), PropertyTable.property()) :: - {:ok, PropertyTable.value(), integer()} | :error - def fetch_with_timestamp(table, name) do - case :ets.lookup(table, name) do - [{^name, value, timestamp}] -> {:ok, value, timestamp} - [] -> :error - end - end - - @spec get_by_prefix(PropertyTable.table_id(), PropertyTable.property()) :: [ - {PropertyTable.property(), PropertyTable.value()} - ] - def get_by_prefix(table, prefix) do - matchspec = {append(prefix), :"$2", :_} - - :ets.match(table, matchspec) - |> Enum.map(fn [k, v] -> {prefix ++ k, v} end) - |> Enum.sort() - end - - @spec match(PropertyTable.table_id(), PropertyTable.property_with_wildcards()) :: [ - {PropertyTable.property(), PropertyTable.value()} - ] - def match(table, pattern) do - :ets.match(table, {:"$1", :"$2", :_}) - |> Enum.filter(fn [k, _v] -> - is_property_match?(pattern, k) - end) - |> Enum.map(fn [k, v] -> {k, v} end) - |> Enum.sort() - end - - @dialyzer {:nowarn_function, append: 1} - defp append([]), do: :"$1" - defp append([h]), do: [h | :"$1"] - defp append([h | t]), do: [h | append(t)] - - @doc """ - Update or add a property - - If the property changed, this will send events to all listeners. - """ - @spec put( - PropertyTable.table_id(), - PropertyTable.property(), - PropertyTable.value(), - PropertyTable.metadata() - ) :: - :ok - - def put(table, name, nil, _metadata) do - clear(table, name) - end - - def put(table, name, value, metadata) do - GenServer.call(table, {:put, name, value, System.monotonic_time(), metadata}) - end - - @doc """ - Clear a property - - If the property changed, this will send events to all listeners. - """ - @spec clear(PropertyTable.table_id(), PropertyTable.property()) :: :ok - def clear(table, name) when is_list(name) do - GenServer.call(table, {:clear, name}) - end - - @doc """ - Clear out all of the properties under a prefix - """ - @spec clear_prefix(PropertyTable.table_id(), PropertyTable.property()) :: :ok - def clear_prefix(table, name) when is_list(name) do - GenServer.call(table, {:clear_prefix, name}) - end - - @impl GenServer - def init({table, registry_name, properties}) do - ^table = :ets.new(table, [:named_table, read_concurrency: true]) - - # Insert the initial properties - timestamp = System.monotonic_time() - Enum.each(properties, fn {name, value} -> :ets.insert(table, {name, value, timestamp}) end) - - state = %{table: table, registry: registry_name} - {:ok, state} - end - - @impl GenServer - def handle_call({:put, name, value, timestamp, metadata}, _from, state) do - case :ets.lookup(state.table, name) do - [{^name, ^value, _last_change}] -> - # No change, so no notifications - :ok - - [{^name, old_value, last_change}] -> - timestamp_metadata = %{ - old_timestamp: last_change, - new_timestamp: timestamp - } - - updated_metadata = Map.merge(timestamp_metadata, metadata) - - :ets.insert(state.table, {name, value, timestamp}) - dispatch(state, name, old_value, value, updated_metadata) - - [] -> - :ets.insert(state.table, {name, value, timestamp}) - dispatch(state, name, nil, value, metadata) - end - - {:reply, :ok, state} - end - - @impl GenServer - def handle_call({:clear, name}, _from, state) do - case :ets.lookup(state.table, name) do - [{^name, old_value, _timestamp}] -> - :ets.delete(state.table, name) - dispatch(state, name, old_value, nil, %{}) - - [] -> - :ok - end - - {:reply, :ok, state} - end - - @impl GenServer - def handle_call({:clear_prefix, prefix}, _from, state) do - to_delete = get_by_prefix(state.table, prefix) - metadata = %{} - - # Delete everything first and then send notifications so - # if handlers call "get", they won't see something that - # will be deleted shortly. - Enum.each(to_delete, fn {name, _value} -> - :ets.delete(state.table, name) - end) - - Enum.each(to_delete, fn {name, value} -> - dispatch(state, name, value, nil, metadata) - end) - - {:reply, :ok, state} - end - - defp dispatch(state, name, old_value, new_value, metadata) do - message = {state.table, name, old_value, new_value, metadata} - - Registry.match(state.registry, :property_registry, :_) - |> Enum.each(fn {pid, match} -> - is_property_prefix_match?(match, name) && send(pid, message) - end) - end - - # Check if the first parameter is a prefix of the second parameter with - # wildcards - defp is_property_prefix_match?([], _name), do: true - - defp is_property_prefix_match?([value | match_rest], [value | name_rest]) do - is_property_prefix_match?(match_rest, name_rest) - end - - defp is_property_prefix_match?([:_ | match_rest], [_any | name_rest]) do - is_property_prefix_match?(match_rest, name_rest) - end - - defp is_property_prefix_match?(_match, _name), do: false - - # Check if the first parameter matches the second parameter with wildcards - defp is_property_match?([], []), do: true - - defp is_property_match?([value | match_rest], [value | name_rest]) do - is_property_match?(match_rest, name_rest) - end - - defp is_property_match?([:_ | match_rest], [_any | name_rest]) do - is_property_match?(match_rest, name_rest) - end - - defp is_property_match?(_match, _name), do: false -end diff --git a/lib/vintage_net/route/properties.ex b/lib/vintage_net/route/properties.ex index 8e7e6912..608e527c 100644 --- a/lib/vintage_net/route/properties.ex +++ b/lib/vintage_net/route/properties.ex @@ -25,7 +25,7 @@ defmodule VintageNet.Route.Properties do |> Enum.sort() |> Enum.map(fn {_metric, ifname} -> ifname end) - VintageNet.PropertyTable.put(VintageNet, ["available_interfaces"], interfaces) + PropertyTable.put(VintageNet, ["available_interfaces"], interfaces) end @doc """ @@ -36,7 +36,7 @@ defmodule VintageNet.Route.Properties do @spec update_best_connection(Calculator.interface_infos()) :: :ok def update_best_connection(infos) do best = best_connection(infos) - VintageNet.PropertyTable.put(VintageNet, ["connection"], best) + PropertyTable.put(VintageNet, ["connection"], best) end defp best_connection(infos) when infos == %{} do @@ -65,7 +65,7 @@ defmodule VintageNet.Route.Properties do @spec update_connection_status(Calculator.interface_infos()) :: :ok def update_connection_status(infos) do Enum.each(infos, fn {ifname, %{status: status}} -> - VintageNet.PropertyTable.put(VintageNet, ["interface", ifname, "connection"], status) + PropertyTable.put(VintageNet, ["interface", ifname, "connection"], status) end) end end diff --git a/lib/vintage_net/route_manager.ex b/lib/vintage_net/route_manager.ex index a6536a9c..cf73cb61 100644 --- a/lib/vintage_net/route_manager.ex +++ b/lib/vintage_net/route_manager.ex @@ -200,7 +200,7 @@ defmodule VintageNet.RouteManager do Logger.info("RouteManager: clear_route #{ifname}") # Need to force the property to disconnected since we're removing it from the map. - VintageNet.PropertyTable.put(VintageNet, ["interface", ifname, "connection"], :disconnected) + :ok = PropertyTable.put(VintageNet, ["interface", ifname, "connection"], :disconnected) new_state = %{state | interfaces: Map.delete(state.interfaces, ifname)} diff --git a/mix.exs b/mix.exs index 5d126f8b..580594be 100644 --- a/mix.exs +++ b/mix.exs @@ -103,6 +103,7 @@ defmodule VintageNet.MixProject do {:beam_notify, "~> 1.0 or ~> 0.2.0"}, {:gen_state_machine, "~> 2.0.0 or ~> 2.1.0 or ~> 3.0.0"}, {:muontrap, "~> 1.0 or ~> 0.5.1 or ~> 0.6.0"}, + {:property_table, "~> 0.1.0"}, # Build dependencies {:credo, "~> 1.2", only: :test, runtime: false}, {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index fe6d01ed..bdf4070e 100644 --- a/mix.lock +++ b/mix.lock @@ -22,6 +22,7 @@ "muontrap": {:hex, :muontrap, "1.0.0", "53a05c37f71cc5070aaa0858a774ae1f500160b7186a70565521a14ef7843c5a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0d3cd6e335986f9c2af1b61f583375b0f0d91cea95b7ec7bc720f330b4dc9b49"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "property_table": {:hex, :property_table, "0.1.0", "4df1097253cae17b7c095dff5c4dd035cdb2c38e1e3f05254a1f71ef0a5625eb", [:mix], [], "hexpm", "5de1e6e5af6ae25ad5d017af5700d203c993af9efa1e1c2bdbddfed145bfe265"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/vintage_net/connectivity/internet_checker_test.exs b/test/vintage_net/connectivity/internet_checker_test.exs index 8a7058fa..c89f4978 100644 --- a/test/vintage_net/connectivity/internet_checker_test.exs +++ b/test/vintage_net/connectivity/internet_checker_test.exs @@ -11,8 +11,8 @@ defmodule VintageNet.Connectivity.InternetCheckerTest do lower_up = ["interface", ifname, "lower_up"] # Set up - VintageNet.PropertyTable.clear(VintageNet, property) - VintageNet.PropertyTable.clear(VintageNet, lower_up) + PropertyTable.clear(VintageNet, property) + PropertyTable.clear(VintageNet, lower_up) VintageNet.subscribe(property) start_supervised!({InternetChecker, ifname}) @@ -35,8 +35,8 @@ defmodule VintageNet.Connectivity.InternetCheckerTest do # Set up a situation where the InternetChecker will see take a guess that # the connection is disconnected and then fix it self when it sees the # lower_up being true and then detect the internet. - VintageNet.PropertyTable.put(VintageNet, property, :disconnected) - VintageNet.PropertyTable.put(VintageNet, lower_up, true) + PropertyTable.put(VintageNet, property, :disconnected) + PropertyTable.put(VintageNet, lower_up, true) VintageNet.subscribe(property) @@ -54,15 +54,15 @@ defmodule VintageNet.Connectivity.InternetCheckerTest do # Set up a situation where the InternetChecker will start with thinking the # internet is connected, but then change its mind when the interface goes away. - VintageNet.PropertyTable.put(VintageNet, property, :internet) - VintageNet.PropertyTable.put(VintageNet, lower_up, true) + PropertyTable.put(VintageNet, property, :internet) + PropertyTable.put(VintageNet, lower_up, true) VintageNet.subscribe(property) start_supervised!({InternetChecker, ifname}) Process.sleep(250) - VintageNet.PropertyTable.put(VintageNet, lower_up, false) + PropertyTable.put(VintageNet, lower_up, false) assert_receive {VintageNet, ^property, _old_value, :disconnected, _meta}, 1_000 end diff --git a/test/vintage_net/interface_test.exs b/test/vintage_net/interface_test.exs index dc6c4354..6ab9c061 100644 --- a/test/vintage_net/interface_test.exs +++ b/test/vintage_net/interface_test.exs @@ -17,7 +17,7 @@ defmodule VintageNet.InterfaceTest do end) # Make the test interface available - VintageNet.PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) + PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) end defp configure_only(config, ifname \\ @ifname) do @@ -417,14 +417,14 @@ defmodule VintageNet.InterfaceTest do assert Process.whereis(ItIsMe) != nil # "remove" the interface - VintageNet.PropertyTable.clear(VintageNet, ["interface", @ifname, "present"]) + PropertyTable.clear(VintageNet, ["interface", @ifname, "present"]) Process.sleep(10) assert Process.whereis(ItIsMe) == nil # bring the interface back and it should start again - VintageNet.PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) + PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) assert_receive :i_am_started assert Process.whereis(ItIsMe) != nil @@ -528,14 +528,14 @@ defmodule VintageNet.InterfaceTest do assert File.exists?("testing") # "remove" the interface - VintageNet.PropertyTable.clear(VintageNet, ["interface", @ifname, "present"]) + PropertyTable.clear(VintageNet, ["interface", @ifname, "present"]) Process.sleep(10) refute File.exists?("testing") # bring the interface back - VintageNet.PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) + PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) Process.sleep(10) @@ -558,22 +558,22 @@ defmodule VintageNet.InterfaceTest do refute File.exists?("testing") # Add one dependent - VintageNet.PropertyTable.put(VintageNet, ["interface", "test1", "present"], true) + PropertyTable.put(VintageNet, ["interface", "test1", "present"], true) Process.sleep(10) refute File.exists?("testing") # Add the other dependent - VintageNet.PropertyTable.put(VintageNet, ["interface", "test2", "present"], true) + PropertyTable.put(VintageNet, ["interface", "test2", "present"], true) Process.sleep(10) assert File.exists?("testing") # clean up - VintageNet.PropertyTable.clear(VintageNet, ["interface", "test1", "present"]) - VintageNet.PropertyTable.clear(VintageNet, ["interface", "test2", "present"]) + PropertyTable.clear(VintageNet, ["interface", "test1", "present"]) + PropertyTable.clear(VintageNet, ["interface", "test2", "present"]) end) end @@ -585,58 +585,58 @@ defmodule VintageNet.InterfaceTest do required_ifnames: ["test1", "test2"] } - VintageNet.PropertyTable.put(VintageNet, ["interface", "test1", "present"], true) - VintageNet.PropertyTable.put(VintageNet, ["interface", "test2", "present"], true) + PropertyTable.put(VintageNet, ["interface", "test1", "present"], true) + PropertyTable.put(VintageNet, ["interface", "test2", "present"], true) configure_and_wait(config) assert File.exists?("testing") # "remove" one interface - VintageNet.PropertyTable.clear(VintageNet, ["interface", "test2", "present"]) + PropertyTable.clear(VintageNet, ["interface", "test2", "present"]) Process.sleep(10) refute File.exists?("testing") # bring the interface back - VintageNet.PropertyTable.put(VintageNet, ["interface", "test2", "present"], true) + PropertyTable.put(VintageNet, ["interface", "test2", "present"], true) Process.sleep(10) assert File.exists?("testing") # "remove" the other interface - VintageNet.PropertyTable.clear(VintageNet, ["interface", "test1", "present"]) + PropertyTable.clear(VintageNet, ["interface", "test1", "present"]) Process.sleep(10) refute File.exists?("testing") # bring the interface back - VintageNet.PropertyTable.put(VintageNet, ["interface", "test1", "present"], true) + PropertyTable.put(VintageNet, ["interface", "test1", "present"], true) Process.sleep(10) assert File.exists?("testing") # "removing" the base interface doesn't do anything since it's not required - VintageNet.PropertyTable.clear(VintageNet, ["interface", @ifname, "present"]) + PropertyTable.clear(VintageNet, ["interface", @ifname, "present"]) Process.sleep(10) assert File.exists?("testing") # bring the interface back - VintageNet.PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) + PropertyTable.put(VintageNet, ["interface", @ifname, "present"], true) Process.sleep(10) assert File.exists?("testing") # clean up - VintageNet.PropertyTable.clear(VintageNet, ["interface", "test1", "present"]) - VintageNet.PropertyTable.clear(VintageNet, ["interface", "test2", "present"]) + PropertyTable.clear(VintageNet, ["interface", "test1", "present"]) + PropertyTable.clear(VintageNet, ["interface", "test2", "present"]) end) end diff --git a/test/vintage_net/predictable_interface_name_test.exs b/test/vintage_net/predictable_interface_name_test.exs index 342d9e3c..d42cc344 100644 --- a/test/vintage_net/predictable_interface_name_test.exs +++ b/test/vintage_net/predictable_interface_name_test.exs @@ -1,8 +1,8 @@ defmodule VintageNet.PredictableInterfaceNameTest do use ExUnit.Case, async: false + alias PropertyTable alias VintageNet.PredictableInterfaceName - alias VintageNet.PropertyTable alias VintageNetTest.CapturingInterfaceRenamer doctest PredictableInterfaceName diff --git a/test/vintage_net/property_table_test.exs b/test/vintage_net/property_table_test.exs deleted file mode 100644 index e6b539ef..00000000 --- a/test/vintage_net/property_table_test.exs +++ /dev/null @@ -1,230 +0,0 @@ -defmodule VintageNet.PropertyTableTest do - use ExUnit.Case, async: true - - alias VintageNet.PropertyTable - - doctest PropertyTable - - test "start with initial properties", %{test: table} do - name1 = ["test", "a", "b"] - name2 = ["test", "c"] - - {:ok, _pid} = - start_supervised({PropertyTable, properties: [{name1, 1}, {name2, 2}], name: table}) - - assert PropertyTable.get(table, name1) == 1 - assert PropertyTable.get(table, name2) == 2 - end - - test "wildcard subscription", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - PropertyTable.subscribe(table, ["a", :_, "c"]) - - # Exact match - PropertyTable.put(table, ["a", "b", "c"], 88) - assert_receive {^table, ["a", "b", "c"], nil, 88, _} - - # Prefix match - PropertyTable.put(table, ["a", "b", "c", "d"], 88) - assert_receive {^table, ["a", "b", "c", "d"], nil, 88, _} - - # No match - PropertyTable.put(table, ["x", "b", "c"], 88) - refute_receive {^table, ["x", "b", "c"], _, _, _} - - PropertyTable.put(table, ["a", "b", "d"], 88) - refute_receive {^table, ["a", "b", "d"], _, _, _} - end - - test "getting invalid properties raises", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - # Wildcards aren't allowed - assert_raise ArgumentError, fn -> PropertyTable.get(table, [:_, "a"]) end - assert_raise ArgumentError, fn -> PropertyTable.get(table, [:_]) end - - # Non-string lists aren't allowed - assert_raise ArgumentError, fn -> PropertyTable.get(table, ['nope']) end - assert_raise ArgumentError, fn -> PropertyTable.get(table, ["a", 5]) end - end - - test "sending events", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test"] - PropertyTable.subscribe(table, name) - - PropertyTable.put(table, name, 99) - assert_receive {table, ^name, nil, 99, _} - - PropertyTable.put(table, name, 100) - assert_receive {table, ^name, 99, 100, _} - - PropertyTable.clear(table, name) - assert_receive {table, ^name, 100, nil, _} - - PropertyTable.unsubscribe(table, name) - end - - test "setting properties to nil clears them", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test"] - - PropertyTable.put(table, name, 124) - assert PropertyTable.get_by_prefix(table, []) == [{name, 124}] - - PropertyTable.put(table, name, nil) - assert PropertyTable.get_by_prefix(table, []) == [] - end - - test "generic subscribers receive events", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test", "a", "b"] - - PropertyTable.subscribe(table, []) - PropertyTable.put(table, name, 101) - assert_receive {table, ^name, nil, 101, _} - PropertyTable.unsubscribe(table, []) - end - - test "duplicate events are dropped", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test", "a", "b"] - - PropertyTable.subscribe(table, name) - PropertyTable.put(table, name, 102) - PropertyTable.put(table, name, 102) - assert_receive {^table, ^name, nil, 102, _} - refute_receive {^table, ^name, _, 102, _} - - PropertyTable.unsubscribe(table, name) - end - - test "getting the latest", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test", "a", "b"] - assert PropertyTable.get(table, name) == nil - - PropertyTable.put(table, name, 105) - assert PropertyTable.get(table, name) == 105 - - PropertyTable.put(table, name, 106) - assert PropertyTable.get(table, name) == 106 - end - - test "fetching data with timestamps", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test", "a", "b"] - assert :error == PropertyTable.fetch_with_timestamp(table, name) - - PropertyTable.put(table, name, 105) - now = System.monotonic_time() - assert {:ok, value, timestamp} = PropertyTable.fetch_with_timestamp(table, name) - assert value == 105 - - # Check that PropertyTable takes the timestamp synchronously. - # If it doesn't, then this will fail randomly. - assert now > timestamp - - # Check that it didn't take too long to capture the time - assert now - timestamp < 1_000_000 - end - - test "getting a subtree", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - name = ["test", "a", "b"] - name2 = ["test", "a", "c"] - - assert PropertyTable.get_by_prefix(table, []) == [] - - PropertyTable.put(table, name, 105) - assert PropertyTable.get_by_prefix(table, []) == [{name, 105}] - - PropertyTable.put(table, name2, 106) - assert PropertyTable.get_by_prefix(table, []) == [{name, 105}, {name2, 106}] - assert PropertyTable.get_by_prefix(table, ["test"]) == [{name, 105}, {name2, 106}] - assert PropertyTable.get_by_prefix(table, ["test", "a"]) == [{name, 105}, {name2, 106}] - assert PropertyTable.get_by_prefix(table, name) == [{name, 105}] - assert PropertyTable.get_by_prefix(table, name2) == [{name2, 106}] - end - - test "clearing a subtree", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - PropertyTable.put(table, ["a", "b", "c"], 1) - PropertyTable.put(table, ["a", "b", "d"], 2) - PropertyTable.put(table, ["a", "b", "e"], 3) - PropertyTable.put(table, ["f", "g"], 4) - - PropertyTable.clear_prefix(table, ["a"]) - assert PropertyTable.get_by_prefix(table, []) == [{["f", "g"], 4}] - end - - test "match using wildcards", %{test: table} do - {:ok, _pid} = start_supervised({PropertyTable, name: table}) - PropertyTable.put(table, ["a", "b", "c"], 1) - PropertyTable.put(table, ["A", "b", "c"], 2) - PropertyTable.put(table, ["a", "B", "c"], 3) - PropertyTable.put(table, ["a", "b", "C"], 4) - - # These next properties should never match since we only match on 3 elements below - PropertyTable.put(table, ["a", "b"], 5) - PropertyTable.put(table, ["a", "b", "c", "d"], 6) - - # Exact match - assert PropertyTable.match(table, ["a", "b", "c"]) == [{["a", "b", "c"], 1}] - - # Wildcard one place - assert PropertyTable.match(table, [:_, "b", "c"]) == [ - {["A", "b", "c"], 2}, - {["a", "b", "c"], 1} - ] - - assert PropertyTable.match(table, ["a", :_, "c"]) == [ - {["a", "B", "c"], 3}, - {["a", "b", "c"], 1} - ] - - assert PropertyTable.match(table, ["a", "b", :_]) == [ - {["a", "b", "C"], 4}, - {["a", "b", "c"], 1} - ] - - # Wildcard two places - assert PropertyTable.match(table, [:_, :_, "c"]) == [ - {["A", "b", "c"], 2}, - {["a", "B", "c"], 3}, - {["a", "b", "c"], 1} - ] - - assert PropertyTable.match(table, ["a", :_, :_]) == [ - {["a", "B", "c"], 3}, - {["a", "b", "C"], 4}, - {["a", "b", "c"], 1} - ] - - # Wildcard three places - assert PropertyTable.match(table, [:_, :_, :_]) == [ - {["A", "b", "c"], 2}, - {["a", "B", "c"], 3}, - {["a", "b", "C"], 4}, - {["a", "b", "c"], 1} - ] - end - - test "timestamp of old and new values are provided in metadata", %{test: table} do - name = ["a", "b", "c"] - - {:ok, _pid} = start_supervised({PropertyTable, name: table, properties: [{name, 1}]}) - - PropertyTable.subscribe(table, name) - - {:ok, 1, old_timestamp} = PropertyTable.fetch_with_timestamp(table, name) - - PropertyTable.put(table, ["a", "b", "c"], 88) - - assert_receive {^table, ["a", "b", "c"], 1, 88, metadata} - - {:ok, 88, new_timestamp} = PropertyTable.fetch_with_timestamp(table, name) - - assert old_timestamp == metadata.old_timestamp - assert new_timestamp == metadata.new_timestamp - end -end diff --git a/test/vintage_net/route/properties_test.exs b/test/vintage_net/route/properties_test.exs index 3f38bbc5..17e75aea 100644 --- a/test/vintage_net/route/properties_test.exs +++ b/test/vintage_net/route/properties_test.exs @@ -8,8 +8,8 @@ defmodule VintageNet.Route.PropertiesTest do setup do # Clean up the properties we test before and after clear_all = fn -> - VintageNet.PropertyTable.clear(VintageNet, ["available_interfaces"]) - VintageNet.PropertyTable.clear(VintageNet, ["connection"]) + PropertyTable.clear(VintageNet, ["available_interfaces"]) + PropertyTable.clear(VintageNet, ["connection"]) end clear_all.()