Repository: newrelic/elixir_agent (elixir)

Issue:
New Relic agent supported for services running within AWS Fargate?
Hi there,

we don't seem to be able to run the agent from our Elixir service running from AWS Fargate.

The AWS metadata endpoint:
https://github.com/newrelic/elixir_agent/blob/master/lib/new_relic/util.ex#L68

is not available from an Fargate/ECS task as per this forum post:
https://forums.aws.amazon.com/thread.jspa?messageID=830807&tstart=0

and we are getting this error upon start of the Elixir service:

```
Elixir.ArgumentError argument error 
    gen_tcp.erl:153 :gen_tcp.connect/4
    APP_PATH/deps/hackney/src/hackney_connect.erl:247 :hackney_connect.do_connect/5
    APP_PATH/deps/hackney/src/hackney_connect.erl:37 :hackney_connect.connect/5
    APP_PATH/deps/hackney/src/hackney.erl:316 :hackney.request/5
    lib/httpoison/base.ex:630 HTTPoison.Base.request/9
    lib/new_relic/util.ex:80 NewRelic.Util.aws_vendor_hash/1
    lib/new_relic/util.ex:71 NewRelic.Util.maybe_add_vendors/2
    lib/new_relic/harvest/collector/agent_run.ex:62 NewRelic.Harvest.Collector.AgentRun.connect_payload/0
```

Is Fargate not supported yet or are we missing anything?

Thanks!
Angel

**Environment**
```
Erlang/OTP 21 [erts-10.0.5] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [hipe]

Elixir 1.7.2 (compiled with Erlang/OTP 21)
```
* Agent version 1.3.1


Pull request description:
Safer AWS metadata query
Closes #74 by protecting against exceptions when attempting to see if AWS metadata is accessible.

Most of this PR is a re-shuffle of the vendor specific functions

Patch:
diff --git a/lib/new_relic/util.ex b/lib/new_relic/util.ex
index 0862187..3fabd12 100644
--- a/lib/new_relic/util.ex
+++ b/lib/new_relic/util.ex
@@ -1,8 +1,10 @@
 defmodule NewRelic.Util do
   @moduledoc false
 
+  alias NewRelic.Util.Vendor
+
   def hostname do
-    maybe_heroku_dyno_hostname() || get_hostname()
+    Vendor.maybe_heroku_dyno_hostname() || get_hostname()
   end
 
   def pid, do: System.get_pid() |> String.to_integer()
@@ -63,8 +65,8 @@ defmodule NewRelic.Util do
       total_ram_mib: get_system_memory(),
       hostname: hostname()
     }
-    |> maybe_add_boot_id()
-    |> maybe_add_vendors()
+    |> Vendor.maybe_add_linux_boot_id()
+    |> Vendor.maybe_add_cloud_vendors()
   end
 
   @mb 1024 * 1024
@@ -75,48 +77,7 @@ defmodule NewRelic.Util do
     end
   end
 
-  defp maybe_add_boot_id(util) do
-    case File.read("/proc/sys/kernel/random/boot_id") do
-      {:ok, boot_id} -> Map.put(util, "boot_id", boot_id)
-      _ -> util
-    end
-  end
-
-  @aws_url "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document"
-  def maybe_add_vendors(util, options \\ []) do
-    Keyword.get(options, :aws_url, @aws_url)
-    |> aws_vendor_hash()
-    |> case do
-      nil -> util
-      aws_hash -> Map.put(util, :vendors, %{aws: aws_hash})
-    end
-  end
-
-  @aws_vendor_data ["availabilityZone", "instanceId", "instanceType"]
-  defp aws_vendor_hash(url) do
-    case HTTPoison.get(url, [], timeout: 100) do
-      {:ok, %{status_code: 200, body: body}} ->
-        case Jason.decode(body) do
-          {:ok, data} -> Map.take(data, @aws_vendor_data)
-          _ -> nil
-        end
-
-      _error ->
-        nil
-    end
-  end
-
   defp get_hostname do
     with {:ok, name} <- :inet.gethostname(), do: to_string(name)
   end
-
-  defp maybe_heroku_dyno_hostname do
-    System.get_env("DYNO")
-    |> case do
-      nil -> nil
-      "scheduler." <> _ -> "scheduler.*"
-      "run." <> _ -> "run.*"
-      name -> name
-    end
-  end
 end
diff --git a/lib/new_relic/util/vendor.ex b/lib/new_relic/util/vendor.ex
new file mode 100644
index 0000000..3e2bc69
--- /dev/null
+++ b/lib/new_relic/util/vendor.ex
@@ -0,0 +1,46 @@
+defmodule NewRelic.Util.Vendor do
+  def maybe_add_linux_boot_id(util) do
+    case File.read("/proc/sys/kernel/random/boot_id") do
+      {:ok, boot_id} -> Map.put(util, "boot_id", boot_id)
+      _ -> util
+    end
+  end
+
+  def maybe_heroku_dyno_hostname do
+    System.get_env("DYNO")
+    |> case do
+      nil -> nil
+      "scheduler." <> _ -> "scheduler.*"
+      "run." <> _ -> "run.*"
+      name -> name
+    end
+  end
+
+  @aws_url "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document"
+  def maybe_add_cloud_vendors(util, options \\ []) do
+    Keyword.get(options, :aws_url, @aws_url)
+    |> aws_vendor_hash()
+    |> case do
+      nil -> util
+      aws_hash -> Map.put(util, :vendors, %{aws: aws_hash})
+    end
+  end
+
+  @aws_vendor_data ["availabilityZone", "instanceId", "instanceType"]
+  def aws_vendor_hash(url) do
+    case HTTPoison.get(url, [], timeout: 100) do
+      {:ok, %{status_code: 200, body: body}} ->
+        case Jason.decode(body) do
+          {:ok, data} -> Map.take(data, @aws_vendor_data)
+          _ -> nil
+        end
+
+      _error ->
+        nil
+    end
+  rescue
+    exception ->
+      NewRelic.log(:error, "Failed to fetch AWS metadata. #{inspect(exception)}")
+      nil
+  end
+end


Test patch:
diff --git a/test/util_test.exs b/test/util_test.exs
index f6c1bdf..b61d745 100644
--- a/test/util_test.exs
+++ b/test/util_test.exs
@@ -55,14 +55,21 @@ defmodule UtilTest do
     end
   end
 
+  test "minimal utilization check" do
+    assert %{metadata_version: 3} = NewRelic.Util.utilization()
+  end
+
   test "AWS utilization fast timeout" do
-    assert %{} == NewRelic.Util.maybe_add_vendors(%{}, aws_url: "http://httpbin.org/delay/10")
+    assert %{} ==
+             NewRelic.Util.Vendor.maybe_add_cloud_vendors(%{},
+               aws_url: "http://httpbin.org/delay/10"
+             )
   end
 
   test "AWS utilization info" do
     {:ok, _} = Plug.Cowboy.http(FakeAwsPlug, [], port: 8883)
 
-    util = NewRelic.Util.maybe_add_vendors(%{}, aws_url: "http://localhost:8883")
+    util = NewRelic.Util.Vendor.maybe_add_cloud_vendors(%{}, aws_url: "http://localhost:8883")
     assert get_in(util, [:vendors, :aws, "instanceId"]) == "test.id"
   end