diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfde9066..38b60272 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,19 @@ jobs: name: Dependencies runs-on: warp-ubuntu-2204-x64-2x container: - image: alpine:3.18 + image: alpine:3.20 + + services: + postgres: + image: postgres:15.4 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Cancel Previous Runs @@ -39,7 +51,7 @@ jobs: deps _build priv/plts - key: ${{ runner.os }}-uplink-${{ hashFiles('mix.lock') }}-v2 + key: ${{ runner.os }}-uplink-${{ hashFiles('mix.lock') }}-v4 - name: Install Dependencies if: steps.mix-cache.outputs.cache-hit != 'true' @@ -50,14 +62,21 @@ jobs: mix deps.get mix deps.compile MIX_ENV=test mix deps.compile + MIX_ENV=test mix opsmo.embed mix dialyzer --plt + env: + POSTGRES_HOST: postgres + POSTGRES_USERNAME: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} + static_code_analysis: name: Static Code Analysis needs: deps runs-on: warp-ubuntu-2204-x64-2x container: - image: alpine:3.18 + image: alpine:3.20 steps: - name: Cancel Previous Runs @@ -85,7 +104,7 @@ jobs: deps _build priv/plts - key: ${{ runner.os }}-uplink-${{ hashFiles('mix.lock') }}-v2 + key: ${{ runner.os }}-uplink-${{ hashFiles('mix.lock') }}-v4 - name: Check Code Format run: mix format --check-formatted @@ -98,7 +117,7 @@ jobs: needs: deps runs-on: warp-ubuntu-2204-x64-2x container: - image: alpine:3.18 + image: alpine:3.20 services: postgres: @@ -138,10 +157,12 @@ jobs: deps _build priv/plts - key: ${{ runner.os }}-uplink-${{ hashFiles('mix.lock') }}-v2 + key: ${{ runner.os }}-uplink-${{ hashFiles('mix.lock') }}-v4 - name: Run Tests - run: mix test --trace --slowest 10 + run: | + mix test --trace --slowest 10 + env: POSTGRES_HOST: postgres POSTGRES_USERNAME: postgres diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 8f19a5e6..e3e4ecd8 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Pakman uses: upmaru/pakman@v8 with: - alpine: v3.18 + alpine: v3.20 - name: Bootstrap Configuration run: | @@ -64,7 +64,7 @@ jobs: - name: Setup Pakman uses: upmaru/pakman@v8 with: - alpine: v3.18 + alpine: v3.20 - name: Merge Artifact run: | diff --git a/.tool-versions b/.tool-versions index aaca8380..12a3f2f1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.14.4-otp-25 -erlang 25.3.2.7 +erlang 26.2.5 +elixir 1.16.3-otp-26 caddy 2.7.4 diff --git a/config/config.exs b/config/config.exs index 8a58d56d..25d2cf53 100644 --- a/config/config.exs +++ b/config/config.exs @@ -6,6 +6,13 @@ config :uplink, Uplink.Cache, backend: :shards ] +config :opsmo, :mode, :inference + +config :opsmo, + models: %{ + "crpm" => "0.3.7" + } + config :uplink, Uplink.Internal, port: 4080 config :uplink, Uplink.Router, port: 4040 diff --git a/config/test.exs b/config/test.exs index f7056499..e585c690 100644 --- a/config/test.exs +++ b/config/test.exs @@ -47,4 +47,4 @@ config :uplink, :drivers, aws_s3: Uplink.Drivers.Bucket.AwsMock # config :plug, :validate_header_keys_during_test, false # Print only warnings and errors during test # Disable logging in tests -config :logger, level: :warn +config :logger, level: :warning diff --git a/instellar.yml b/instellar.yml index 4884001a..653e9edc 100644 --- a/instellar.yml +++ b/instellar.yml @@ -10,12 +10,12 @@ dependencies: - inotify-tools - s6 - uplink-openrc - -stack: alpine/3.18 + +stack: alpine/3.20 build: - destinations: - - '_build/prod/rel/uplink/*' + destinations: + - "_build/prod/rel/uplink/*" command: | export MIX_ENV=prod @@ -25,24 +25,24 @@ build: mix release -run: +run: name: uplink commands: - - name: migrate - binary: uplink - call: 'eval Uplink.Release.Tasks.migrate' - - name: console - binary: uplink - call: remote - - name: logs - binary: tail - path: /usr/bin - call: -f -n 100 /var/log/uplink/current + - name: migrate + binary: uplink + call: "eval Uplink.Release.Tasks.migrate" + - name: console + binary: uplink + call: remote + - name: logs + binary: tail + path: /usr/bin + call: -f -n 100 /var/log/uplink/current services: - - name: web - binary: uplink - start: - call: 'start' + - name: web + binary: uplink + start: + call: "start" hook: post-install: | @@ -63,7 +63,7 @@ kits: main: true name: lite max_instances_count: 1 - ports: + ports: - name: web target: 4040 variables: diff --git a/lib/uplink/application.ex b/lib/uplink/application.ex index 4e9da693..9101dd75 100644 --- a/lib/uplink/application.ex +++ b/lib/uplink/application.ex @@ -26,24 +26,29 @@ defmodule Uplink.Application do topologies = Application.get_env(:libcluster, :topologies, []) - children = [ - {Uplink.Cache, []}, - {Cluster.Supervisor, [topologies, [name: Uplink.ClusterSupervisor]]}, - {Task.Supervisor, name: Uplink.TaskSupervisor}, - {Plug.Cowboy, plug: Uplink.Internal, scheme: :http, port: internal_port}, - {Pogo.DynamicSupervisor, - name: @pipeline_supervisor, scope: :uplink, sync_interval: sync_interval}, - {Uplink.Monitors, []}, - { - Plug.Cowboy, - plug: Uplink.Router, - scheme: :https, - port: port, - key: {:RSAPrivateKey, key}, - cert: cert - }, - {Uplink.Data.Provisioner, []} - ] + children = + [ + {Uplink.Cache, []}, + {Cluster.Supervisor, [topologies, [name: Uplink.ClusterSupervisor]]}, + {Task.Supervisor, name: Uplink.TaskSupervisor}, + {Plug.Cowboy, + plug: Uplink.Internal, scheme: :http, port: internal_port}, + {Pogo.DynamicSupervisor, + name: @pipeline_supervisor, + scope: :uplink, + sync_interval: sync_interval}, + {Uplink.Monitors, []}, + { + Plug.Cowboy, + plug: Uplink.Router, + scheme: :https, + port: port, + key: {:RSAPrivateKey, key}, + cert: cert + }, + {Uplink.Data.Provisioner, []} + ] + |> Opsmo.append_model_spec(Opsmo.CRPM) opts = [strategy: :one_for_one, name: Uplink.Supervisor] Supervisor.start_link(children, opts) diff --git a/lib/uplink/availability.ex b/lib/uplink/availability.ex new file mode 100644 index 00000000..8bc390dd --- /dev/null +++ b/lib/uplink/availability.ex @@ -0,0 +1,231 @@ +defmodule Uplink.Availability do + alias Uplink.Metrics + alias Uplink.Pipelines + + alias Uplink.Clients.LXD + alias Uplink.Clients.Instellar + + alias __MODULE__.Query + alias __MODULE__.Response + alias __MODULE__.Resource + alias __MODULE__.Placeability + alias __MODULE__.Requirement + + defdelegate process_requirement(params), + to: Requirement.Manager, + as: :process + + @spec check!(list(%Requirement{})) :: + {:ok, list(%Resource{})} | {:error, any()} + def check!(requirements) when is_list(requirements) do + case get_monitor() do + %{"attributes" => _attributes} = monitor -> + check(monitor, requirements) + + {:ok, monitors} -> + monitors + |> List.first() + |> check(requirements) + + _ -> + raise "No monitor found" + end + end + + def check(%{"attributes" => _} = monitor, requirements) + when is_map(monitor) do + indices = + Query.index_types() + |> Enum.map(&Metrics.index/1) + + nodes = + LXD.list_cluster_members() + |> Enum.map(fn member -> + LXD.get_node(member.server_name) + end) + + query = Query.build(nodes, indices) + + Metrics.query!(monitor, query) + |> case do + %{status: 200, body: %{"responses" => responses}} -> + resources = + nodes + |> Response.parse(responses) + |> Enum.map(&Resource.parse/1) + + # Extract instance metrics from responses + nodes_instances_metrics = + Enum.map(nodes, &extract_node_instances_metrics(&1, responses)) + + requirements = + Enum.map( + requirements, + &update_requirement_with_actual_usage( + &1, + nodes, + nodes_instances_metrics + ) + ) + + resources = compute_placeability(resources, requirements) + + {:ok, resources} + + _ -> + {:error, :could_not_query_metrics} + end + end + + defp get_monitor do + monitors = Pipelines.get_monitors(:metrics) + + if monitors == [] do + Instellar.list_monitors() + else + List.first(monitors) + end + end + + defp compute_placeability(resources, requirements) do + template = %{"cpu" => [], "memory" => [], "disk" => []} + + inputs = Enum.reduce(resources, template, &to_inputs(&1, &2, requirements)) + + predictions = Opsmo.predict(Opsmo.CRPM, inputs) + + resources + |> Enum.zip(predictions) + |> Enum.map(&patch_with_placeability/1) + end + + defp to_inputs(resource, acc, requirements) do + requirement = Enum.find(requirements, fn r -> r.node == resource.node end) + + zero = Decimal.new("0") + + requested_cpu = + if requirement.actual_cpu && Decimal.gt?(requirement.actual_cpu, zero) do + Decimal.to_float(requirement.actual_cpu) + else + Decimal.to_float(requirement.cpu) + end + + requested_memory = + if requirement.actual_memory && + Decimal.gt?(requirement.actual_memory, zero) do + Decimal.to_float(requirement.actual_memory) + else + Decimal.to_float(requirement.memory) + end + + requested_disk = + if requirement.actual_disk && Decimal.gt?(requirement.actual_disk, zero) do + Decimal.to_float(requirement.actual_disk) + else + Decimal.to_float(requirement.disk) + end + + %{ + "cpu" => + acc["cpu"] ++ + [[requested_cpu, Decimal.to_float(resource.used.load_norm_5)]], + "memory" => + acc["memory"] ++ + [ + [ + requested_memory, + Decimal.to_float(resource.used.memory), + Decimal.to_float(resource.total.memory_normalized) + ] + ], + "disk" => + acc["disk"] ++ + [[requested_disk, Decimal.to_float(resource.used.storage)]] + } + end + + defp patch_with_placeability({resource, prediction}) do + %{resource | placeability: Placeability.parse(prediction)} + end + + defp extract_node_instances_metrics(node, responses) do + instances_metrics = + responses + |> Enum.flat_map(fn response -> + aggregations = response["aggregations"] + aggregations[node.name]["buckets"] + end) + |> Enum.group_by(&Map.get(&1, "key")) + + %{"node" => node.name, "metrics" => instances_metrics} + end + + defp update_requirement_with_actual_usage( + requirement, + nodes, + instance_metrics + ) do + metrics = + instance_metrics + |> Enum.filter(fn m -> m["node"] == requirement.node end) + |> Enum.flat_map(fn m -> m["metrics"] end) + |> Enum.filter(fn {key, _} -> key in requirement.instances end) + + summed_metrics = + Enum.reduce(metrics, %{cpu: 0.0, memory: 0.0, disk: 0.0}, fn {_key, + values}, + acc -> + cpu = Enum.find(values, fn v -> Map.has_key?(v, "load_norm_5") end) + + memory = + Enum.find(values, fn v -> Map.has_key?(v, "memory_used_bytes") end) + + disk = + Enum.find(values, fn v -> Map.has_key?(v, "filesystem_used_bytes") end) + + cpu = cpu["load_norm_5"] + memory = memory["memory_used_bytes"] + disk = disk["filesystem_used_bytes"] + + cpu = cpu["top"] + memory = memory["top"] + disk = disk["top"] + + cpu = List.first(cpu) + memory = List.first(memory) + disk = List.first(disk) + + cpu = cpu["metrics"]["system.load.norm.5"] || 0.0 + memory = memory["metrics"]["system.memory.actual.used.bytes"] || 0.0 + disk = disk["metrics"]["system.filesystem.used.bytes"] || 0.0 + + Map.merge(acc, %{ + cpu: acc.cpu + cpu, + memory: acc.memory + memory, + disk: acc.disk + disk + }) + end) + + node = Enum.find(nodes, fn n -> n.name == requirement.node end) + + mean_metrics = + if length(metrics) > 0 do + %{ + cpu: summed_metrics.cpu / length(metrics), + memory: summed_metrics.memory / length(metrics), + disk: summed_metrics.disk / length(metrics) + } + else + %{cpu: 0.0, memory: 0.0, disk: 0.0} + end + + %{ + requirement + | actual_cpu: Decimal.new("#{mean_metrics.cpu}"), + actual_memory: + Decimal.new("#{mean_metrics.memory / node.total_memory}"), + actual_disk: Decimal.new("#{mean_metrics.disk / node.total_storage}") + } + end +end diff --git a/lib/uplink/availability/attribute.ex b/lib/uplink/availability/attribute.ex new file mode 100644 index 00000000..67e2705a --- /dev/null +++ b/lib/uplink/availability/attribute.ex @@ -0,0 +1,3 @@ +defmodule Uplink.Availability.Attribute do + defstruct [:field, :name] +end diff --git a/lib/uplink/availability/placeability.ex b/lib/uplink/availability/placeability.ex new file mode 100644 index 00000000..c7f34195 --- /dev/null +++ b/lib/uplink/availability/placeability.ex @@ -0,0 +1,24 @@ +defmodule Uplink.Availability.Placeability do + use Ecto.Schema + import Ecto.Changeset + + @derive Jason.Encoder + + @primary_key false + embedded_schema do + field :cpu, :decimal + field :memory, :decimal + field :disk, :decimal + end + + def changeset(placeability, params) do + placeability + |> cast(params, [:cpu, :memory, :disk]) + end + + def parse(params) do + %__MODULE__{} + |> changeset(params) + |> apply_action!(:insert) + end +end diff --git a/lib/uplink/availability/query.ex b/lib/uplink/availability/query.ex new file mode 100644 index 00000000..e2e8eeb3 --- /dev/null +++ b/lib/uplink/availability/query.ex @@ -0,0 +1,121 @@ +defmodule Uplink.Availability.Query do + alias Uplink.Clients.LXD.Node + alias Uplink.Availability.Attribute + + @metrics_mappings %{ + "metrics-system.memory-" => :memory, + "metrics-system.load-" => :load, + "metrics-system.filesystem-" => :filesystem + } + + @metrics_aggregate_attributes %{ + memory: [ + %Attribute{ + name: "memory_used_bytes", + field: "system.memory.actual.used.bytes" + } + ], + load: [ + %Attribute{ + name: "load_norm_5", + field: "system.load.norm.5" + } + ], + filesystem: [ + %Attribute{ + name: "filesystem_used_bytes", + field: "system.filesystem.used.bytes" + } + ] + } + + def index_types do + Map.values(@metrics_mappings) + end + + @spec build([Node.t()] | Node.t(), [String.t()]) :: String.t() + def build(nodes, indices) when is_list(nodes) do + nodes + |> Enum.map(fn node -> + build(node, indices) + end) + |> Enum.join("\n") + end + + def build(%Node{name: key}, indices) + when is_list(indices) do + valid_prefixes = Map.keys(@metrics_mappings) + + indices + |> Enum.filter(fn index -> + Enum.any?(valid_prefixes, fn prefix -> + index =~ prefix + end) + end) + |> Enum.flat_map(fn index -> + {_prefix, type} = + Enum.find(@metrics_mappings, fn {prefix, _type} -> + index =~ prefix + end) + + attributes = Map.fetch!(@metrics_aggregate_attributes, type) + + aggregates = %{ + key => %{ + terms: %{ + field: "host.name", + size: 1000 + }, + aggs: Enum.reduce(attributes, %{}, &build_aggregate/2) + } + } + + query = %{ + size: 0, + query: %{ + term: %{ + "cloud.instance.id" => key + } + }, + aggs: Enum.reduce(attributes, aggregates, &build_sum(key, &1, &2)) + } + + [%{index: index}, query] + end) + |> Enum.map(&Jason.encode_to_iodata!/1) + |> Enum.join("\n") + end + + def retrieval_keys do + @metrics_aggregate_attributes + |> Map.values() + |> List.flatten() + |> Enum.map(& &1.name) + end + + defp build_aggregate(attribute, acc) do + query = %{ + top_metrics: %{ + metrics: [ + %{field: attribute.field} + ], + sort: %{ + "@timestamp" => "desc" + }, + size: 1 + } + } + + Map.put(acc, attribute.name, query) + end + + defp build_sum(key, attribute, acc) do + sum = %{ + sum_bucket: %{ + buckets_path: "#{key}>#{attribute.name}[#{attribute.field}]" + } + } + + Map.put(acc, attribute.name, sum) + end +end diff --git a/lib/uplink/availability/requirement.ex b/lib/uplink/availability/requirement.ex new file mode 100644 index 00000000..2aed1d96 --- /dev/null +++ b/lib/uplink/availability/requirement.ex @@ -0,0 +1,32 @@ +defmodule Uplink.Availability.Requirement do + use Ecto.Schema + import Ecto.Changeset + + @derive Jason.Encoder + + @primary_key false + embedded_schema do + field :node, :string + field :instances, {:array, :string} + + field :cpu, :decimal + field :memory, :decimal + field :disk, :decimal + + field :actual_cpu, :decimal + field :actual_memory, :decimal + field :actual_disk, :decimal + end + + def changeset(requirement, params) do + requirement + |> cast(params, [:node, :instances, :cpu, :memory, :disk]) + |> validate_required([:node, :instances, :cpu, :memory, :disk]) + end + + def parse(params) do + %__MODULE__{} + |> changeset(params) + |> apply_action!(:insert) + end +end diff --git a/lib/uplink/availability/requirement/manager.ex b/lib/uplink/availability/requirement/manager.ex new file mode 100644 index 00000000..c1744882 --- /dev/null +++ b/lib/uplink/availability/requirement/manager.ex @@ -0,0 +1,42 @@ +defmodule Uplink.Availability.Requirement.Manager do + alias Uplink.Clients.LXD + + alias Uplink.Availability.Requirement + alias Uplink.Availability.Requirement.Params + + def process(params) do + case Params.parse(params) do + {:ok, %Params{} = params} -> + instance_names = + LXD.list_instances(project: params.project) + |> Enum.map(fn instance -> + instance.name + end) + + requirements = + LXD.list_cluster_members() + |> Enum.map(fn member -> + LXD.get_node(member.server_name) + end) + |> Enum.map(fn node -> + %{ + "node" => node.name, + "instances" => instance_names, + "cpu" => normalize(params.cpu, node.cpu_cores_count), + "memory" => normalize(params.memory, node.total_memory), + "disk" => normalize(params.disk, node.total_storage) + } + end) + |> Enum.map(&Requirement.parse(&1)) + + {:ok, requirements} + + {:error, changeset} -> + {:error, changeset} + end + end + + defp normalize(required, total) do + Decimal.div(required, Decimal.new(total)) + end +end diff --git a/lib/uplink/availability/requirement/params.ex b/lib/uplink/availability/requirement/params.ex new file mode 100644 index 00000000..d1de17ab --- /dev/null +++ b/lib/uplink/availability/requirement/params.ex @@ -0,0 +1,23 @@ +defmodule Uplink.Availability.Requirement.Params do + use Ecto.Schema + import Ecto.Changeset + + embedded_schema do + field :project, :string + field :cpu, :decimal + field :memory, :decimal + field :disk, :decimal + end + + def changeset(params_struct, params) do + params_struct + |> cast(params, [:project, :cpu, :memory, :disk]) + |> validate_required([:project, :cpu, :memory, :disk]) + end + + def parse(params) do + %__MODULE__{} + |> changeset(params) + |> apply_action(:insert) + end +end diff --git a/lib/uplink/availability/resource.ex b/lib/uplink/availability/resource.ex new file mode 100644 index 00000000..0ad125f9 --- /dev/null +++ b/lib/uplink/availability/resource.ex @@ -0,0 +1,81 @@ +defmodule Uplink.Availability.Resource do + use Ecto.Schema + import Ecto.Changeset + + alias Uplink.Availability.Placeability + + @derive Jason.Encoder + + @primary_key false + embedded_schema do + field :node, :string + + embeds_one :total, Total, primary_key: false do + @derive Jason.Encoder + + field :cpu_cores, :integer + field :memory_bytes, :decimal + field :memory_normalized, :decimal + + field :storage_bytes, :decimal + end + + embeds_one :used, Used, primary_key: false do + @derive Jason.Encoder + + field :load_norm_5, :decimal + field :memory, :decimal + field :storage, :decimal + end + + embeds_one :available, Available, primary_key: false do + @derive Jason.Encoder + + field :processing, :decimal + field :memory, :decimal + field :storage, :decimal + end + + embeds_one :placeability, Placeability + end + + def changeset(resource, params) do + resource + |> cast(params, [:node]) + |> cast_embed(:total, with: &total_changeset/2) + |> cast_embed(:used, with: &used_changeset/2) + |> cast_embed(:available, with: &available_changeset/2) + |> cast_embed(:placeability) + end + + def total_changeset(available, params) do + available + |> cast(params, [ + :cpu_cores, + :memory_bytes, + :memory_normalized, + :storage_bytes + ]) + end + + def used_changeset(used, params) do + used + |> cast(params, [:load_norm_5, :memory, :storage]) + end + + def available_changeset(available, params) do + available + |> cast(params, [:processing, :memory, :storage]) + end + + def placeability_changeset(placeability, params) do + placeability + |> cast(params, [:cpu, :memory, :disk, :score]) + end + + def parse(params) do + %__MODULE__{} + |> changeset(params) + |> apply_action!(:insert) + end +end diff --git a/lib/uplink/availability/response.ex b/lib/uplink/availability/response.ex new file mode 100644 index 00000000..33384b76 --- /dev/null +++ b/lib/uplink/availability/response.ex @@ -0,0 +1,80 @@ +defmodule Uplink.Availability.Response do + alias Uplink.Availability.Query + + def parse(nodes, responses) when is_list(responses) do + retrieval_keys = Query.retrieval_keys() + + params = %{ + retrieval_keys: retrieval_keys, + responses: responses + } + + Enum.map(nodes, &parse_node(&1, params)) + end + + defp parse_node(node, %{retrieval_keys: retrieval_keys, responses: responses}) do + usage_params = + Enum.filter(responses, fn response -> + %{"aggregations" => aggregations} = response + + Map.has_key?(aggregations, node.name) + end) + |> Enum.reduce(%{}, fn response, acc -> + %{"aggregations" => aggregations} = response + + relevant_key = + aggregations + |> Map.keys() + |> Enum.filter(&Enum.member?(retrieval_keys, &1)) + |> List.first() + + result = + Map.get(aggregations, relevant_key) + |> Map.fetch!("value") + + Map.put(acc, relevant_key, result) + end) + + load_norm_5 = Map.get(usage_params, "load_norm_5", 0.0) + used_memory_bytes = Map.get(usage_params, "memory_used_bytes", 0) + used_storage_bytes = Map.get(usage_params, "filesystem_used_bytes", 0) + + memory_normalized = + node.total_memory + # Convert bytes to megabytes + |> div(1024 * 1024) + |> trunc() + |> Opsmo.CRPM.normalize_memory() + |> Nx.to_number() + + %{ + "node" => node.name, + "total" => %{ + "cpu_cores" => node.cpu_cores_count, + "memory_bytes" => node.total_memory, + "memory_normalized" => memory_normalized, + "storage_bytes" => node.total_storage + }, + "used" => %{ + "load_norm_5" => load_norm_5, + "memory" => normalize_value(used_memory_bytes, node.total_memory), + "storage" => normalize_value(used_storage_bytes, node.total_storage) + }, + "available" => %{ + "processing" => 1 - load_norm_5, + "memory" => compute_available(used_memory_bytes, node.total_memory), + "storage" => compute_available(used_storage_bytes, node.total_storage) + } + } + end + + defp normalize_value(used, total) do + used = Decimal.new("#{used}") + total = Decimal.new("#{total}") + Decimal.div(used, total) + end + + defp compute_available(used, total) do + Decimal.sub(Decimal.new("1"), normalize_value(used, total)) + end +end diff --git a/lib/uplink/availability/router.ex b/lib/uplink/availability/router.ex new file mode 100644 index 00000000..09e23e6c --- /dev/null +++ b/lib/uplink/availability/router.ex @@ -0,0 +1,36 @@ +defmodule Uplink.Availability.Router do + use Plug.Router + use Uplink.Web + + alias Uplink.Secret + alias Uplink.Availability + + plug :match + + plug Plug.Parsers, + parsers: [:urlencoded, :json], + body_reader: {Uplink.Web.CacheBodyReader, :read_body, []}, + json_decoder: Jason + + plug Secret.VerificationPlug + + plug :dispatch + + post "/resources" do + %{ + "requirement" => requirement_params + } = conn.body_params + + with {:ok, requirements} <- + Availability.process_requirement(requirement_params), + {:ok, resources} <- Availability.check!(requirements) do + json(conn, :ok, resources) + else + {:error, %Ecto.Changeset{} = error} -> + json(conn, :unprocessable_entity, handle_changeset(error)) + + {:error, reason} -> + json(conn, :service_unavailable, %{error: %{message: reason}}) + end + end +end diff --git a/lib/uplink/clients/lxd/cluster/member.ex b/lib/uplink/clients/lxd/cluster/member.ex index 9281b42f..b769948f 100644 --- a/lib/uplink/clients/lxd/cluster/member.ex +++ b/lib/uplink/clients/lxd/cluster/member.ex @@ -2,6 +2,8 @@ defmodule Uplink.Clients.LXD.Cluster.Member do use Ecto.Schema import Ecto.Changeset + @type t :: %__MODULE__{} + @valid_attrs ~w( roles failure_domain diff --git a/lib/uplink/clients/lxd/node.ex b/lib/uplink/clients/lxd/node.ex index d1e840dc..42c47f9e 100644 --- a/lib/uplink/clients/lxd/node.ex +++ b/lib/uplink/clients/lxd/node.ex @@ -2,6 +2,8 @@ defmodule Uplink.Clients.LXD.Node do use Ecto.Schema import Ecto.Changeset + @type t :: %__MODULE__{} + @derive Jason.Encoder @primary_key false diff --git a/lib/uplink/data/provisioner.ex b/lib/uplink/data/provisioner.ex index 506194f7..9adf10e3 100644 --- a/lib/uplink/data/provisioner.ex +++ b/lib/uplink/data/provisioner.ex @@ -148,7 +148,9 @@ defmodule Uplink.Data.Provisioner do client = LXD.client() - Formation.Lxd.Alpine.provision_postgresql(client, project: state.project) + Formation.Lxd.Alpine.provision_postgresql(client, + project: state.project + ) Process.send_after(self(), {:bootstrap, state.mode, env}, 5_000) diff --git a/lib/uplink/metrics.ex b/lib/uplink/metrics.ex index 5fd6e2e2..04a49500 100644 --- a/lib/uplink/metrics.ex +++ b/lib/uplink/metrics.ex @@ -5,21 +5,23 @@ defmodule Uplink.Metrics do to: __MODULE__.Instance, as: :metrics + def query!(%{"attributes" => attributes} = monitor, query) do + headers = headers(monitor) + endpoint = Map.fetch!(attributes, "endpoint") + query = query <> "\n" + + request = request(endpoint, headers) + + Req.post!(request, url: "/_msearch", body: query) + end + + def query!(_, _), do: {:error, :invalid_monitor} + def push!(%{"attributes" => attributes} = monitor, documents) do headers = headers(monitor) endpoint = Map.fetch!(attributes, "endpoint") - request = - Req.new( - base_url: endpoint, - connect_options: [ - protocols: [:http1], - transport_opts: [ - verify: :verify_none - ] - ], - headers: headers - ) + request = request(endpoint, headers) Req.post!(request, url: "/_bulk", body: documents) end @@ -30,7 +32,20 @@ defmodule Uplink.Metrics do "metrics-system.#{type}-uplink-#{uplink_id}" end - defp headers(%{"attributes" => %{"uid" => uid, "token" => token}}) do + def request(endpoint, headers) do + Req.new( + base_url: endpoint, + connect_options: [ + protocols: [:http1], + transport_opts: [ + verify: :verify_none + ] + ], + headers: headers + ) + end + + def headers(%{"attributes" => %{"uid" => uid, "token" => token}}) do encoded_token = Base.encode64("#{uid}:#{token}") [ diff --git a/lib/uplink/nodes/router.ex b/lib/uplink/nodes/router.ex deleted file mode 100644 index 115b1829..00000000 --- a/lib/uplink/nodes/router.ex +++ /dev/null @@ -1,28 +0,0 @@ -defmodule Uplink.Nodes.Router do - use Plug.Router - use Uplink.Web - - alias Uplink.Secret - alias Uplink.Clients.LXD - - plug :match - - plug Plug.Parsers, - parsers: [:urlencoded, :json], - body_reader: {Uplink.Web.CacheBodyReader, :read_body, []}, - json_decoder: Jason - - plug Secret.VerificationPlug - - plug :dispatch - - post "/" do - nodes = - LXD.list_cluster_members() - |> Enum.map(fn member -> - LXD.get_node(member.server_name) - end) - - json(conn, :ok, nodes) - end -end diff --git a/lib/uplink/packages/instance/bootstrap.ex b/lib/uplink/packages/instance/bootstrap.ex index a0635ae8..9bf1dfe3 100644 --- a/lib/uplink/packages/instance/bootstrap.ex +++ b/lib/uplink/packages/instance/bootstrap.ex @@ -48,9 +48,13 @@ defmodule Uplink.Packages.Instance.Bootstrap do "actor_id" => actor_id } }) do - Cache.put_new({:install, install_id, "completed"}, [], ttl: :timer.hours(24)) + Cache.put_new({:install, install_id, "completed"}, [], + ttl: :timer.hours(24) + ) - Cache.put_new({:install, install_id, "executing"}, [], ttl: :timer.hours(24)) + Cache.put_new({:install, install_id, "executing"}, [], + ttl: :timer.hours(24) + ) %Actor{} = actor = Repo.get(Actor, actor_id) diff --git a/lib/uplink/packages/instance/upgrade.ex b/lib/uplink/packages/instance/upgrade.ex index c138f328..f47a7386 100644 --- a/lib/uplink/packages/instance/upgrade.ex +++ b/lib/uplink/packages/instance/upgrade.ex @@ -42,9 +42,13 @@ defmodule Uplink.Packages.Instance.Upgrade do } } = job ) do - Cache.put_new({:install, install_id, "completed"}, [], ttl: :timer.hours(24)) + Cache.put_new({:install, install_id, "completed"}, [], + ttl: :timer.hours(24) + ) - Cache.put_new({:install, install_id, "executing"}, [], ttl: :timer.hours(24)) + Cache.put_new({:install, install_id, "executing"}, [], + ttl: :timer.hours(24) + ) %Actor{} = actor = Repo.get(Actor, actor_id) diff --git a/lib/uplink/packages/metadata.ex b/lib/uplink/packages/metadata.ex index 7f9d96a4..68fc3f4e 100644 --- a/lib/uplink/packages/metadata.ex +++ b/lib/uplink/packages/metadata.ex @@ -114,7 +114,10 @@ defmodule Uplink.Packages.Metadata do package |> cast(params, [:slug]) |> validate_required([:slug]) - |> cast_embed(:organization, required: true, with: &organization_changeset/2) + |> cast_embed(:organization, + required: true, + with: &organization_changeset/2 + ) |> cast_embed(:credential, required: true, with: &package_credential_changeset/2 diff --git a/lib/uplink/router.ex b/lib/uplink/router.ex index 6bb94085..170b27bc 100644 --- a/lib/uplink/router.ex +++ b/lib/uplink/router.ex @@ -6,7 +6,7 @@ defmodule Uplink.Router do alias Uplink.Installations alias Uplink.Cache alias Uplink.Monitors - alias Uplink.Nodes + alias Uplink.Availability alias Uplink.Packages.{ Instance, @@ -28,7 +28,7 @@ defmodule Uplink.Router do forward "/components", to: Components.Router forward "/cache", to: Cache.Router forward "/monitors", to: Monitors.Router - forward "/nodes", to: Nodes.Router + forward "/availability", to: Availability.Router match _ do send_resp(conn, 404, "not found") diff --git a/mix.exs b/mix.exs index 11590f61..cc22c167 100644 --- a/mix.exs +++ b/mix.exs @@ -58,6 +58,7 @@ defmodule Uplink.MixProject do # Clustering {:libcluster, "~> 3.0"}, {:pogo, "~> 0.3.0"}, + {:libring, "~> 1.7", override: true}, # One time password {:pot, "~> 1.0.2"}, @@ -73,6 +74,9 @@ defmodule Uplink.MixProject do {:broadway, "~> 1.0"}, {:prometheus_parser, "~> 0.1.10"}, + # Opsmo + {:opsmo, "~> 0.3"}, + # Test {:bypass, "~> 2.1", only: :test}, {:mox, "~> 1.0", only: :test}, diff --git a/mix.lock b/mix.lock index 40b53ba5..3a39d9e9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,58 +1,68 @@ %{ "aws": {:hex, :aws, "0.13.3", "d2e932c2588e2b15fca04f345dfced6e07b81d6534e65783de23190c57891df7", [:mix], [{:aws_signature, "~> 0.3", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4ad46a204370d3ef8eab102776a00070ec6fcc4414db7f6b39f3fc0d99564e18"}, - "aws_signature": {:hex, :aws_signature, "0.3.2", "adf33bc4af00b2089b7708bf20e3246f09c639a905a619b3689f0a0a22c3ef8f", [:rebar3], [], "hexpm", "b0daf61feb4250a8ab0adea60db3e336af732ff71dd3fb22e45ae3dcbd071e44"}, + "aws_signature": {:hex, :aws_signature, "0.3.3", "5844bee0d3cc42eefd21d236bbfaa8aa9b16e2f2b7ee79edaecb321db3fb6adf", [:rebar3], [], "hexpm", "87e8f42b8e49002aa8d0350a71d13d69ea91b9afb4ca9b526ae36db1d585c924"}, + "axon": {:hex, :axon, "0.7.0", "2e2c6d93b4afcfa812566b8922204fa022b60081e86ebd411df4db7ea30f5457", [:mix], [{:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}, {:kino_vega_lite, "~> 0.1.7", [hex: :kino_vega_lite, repo: "hexpm", optional: true]}, {:nx, "~> 0.9", [hex: :nx, repo: "hexpm", optional: false]}, {:polaris, "~> 0.1", [hex: :polaris, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "ee9857a143c9486597ceff434e6ca833dc1241be6158b01025b8217757ed1036"}, "broadway": {:hex, :broadway, "1.1.0", "8ed3aea01fd6f5640b3e1515b90eca51c4fc1fac15fb954cdcf75dc054ae719c", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25e315ef1afe823129485d981dcc6d9b221cea30e625fd5439e9b05f44fb60e4"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, - "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, + "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, + "complex": {:hex, :complex, "0.6.0", "b0130086a7a8c33574d293b2e0e250f4685580418eac52a5658a4bd148f3ccf1", [:mix], [], "hexpm", "0a5fa95580dcaf30fcd60fe1aaf24327c0fe401e98c24d892e172e79498269f9"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, - "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, - "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, - "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "eventful": {:hex, :eventful, "0.2.3", "dc795c95b2d00d90b3a5d58c66bd0188b39be08b9da61a743ac40186fd313034", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a1607d9d98ebc45e47573a81c11adfb571259a1db900748c6bee2dc25e5e171c"}, + "exla": {:hex, :exla, "0.9.2", "2b5cb7334f79fedc301502a793ffd10bc1ec8de2c61eebabcabf213fc98ae7e6", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:nx, "~> 0.9.0", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.8.0", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "e51085e196b466d235e93d9f5ea2cbf7d90315d216aa02e996f99bcaaa19c593"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "formation": {:hex, :formation, "0.15.2", "0ff349edbc11b7b9f4bb385f12d37f278907b796a3c19ff8cd9d105c17d1eb3f", [:mix], [{:aws, "~> 0.13.0", [hex: :aws, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:finch, "~> 0.18.0", [hex: :finch, repo: "hexpm", optional: false]}, {:lexdee, "~> 2.3", [hex: :lexdee, repo: "hexpm", optional: false]}, {:mustache, "~> 0.5.0", [hex: :mustache, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17.1", [hex: :postgrex, repo: "hexpm", optional: false]}, {:tesla, "~> 1.7.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c233e3ffc307665fab34ddb75aa2201c1daa7d5d4519fa12b6f9a5ccc5be44a9"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "lexdee": {:hex, :lexdee, "2.4.4", "1763f4e0ab7a97cadc25622faaf80ae63186f12bf5e36d1f92bd8d820e31fa16", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: false]}, {:mint_web_socket, "~> 1.0.2", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:tesla, "~> 1.7.0", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.8.1", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm", "d460902b4f0a686485850cc4fd1eb7c9325a90bbf43106aac06a3b567bdc5e82"}, - "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, - "libring": {:hex, :libring, "1.6.0", "d5dca4bcb1765f862ab59f175b403e356dec493f565670e0bacc4b35e109ce0d", [:mix], [], "hexpm", "5e91ece396af4bce99953d49ee0b02f698cd38326d93cd068361038167484319"}, + "lexdee": {:hex, :lexdee, "2.4.5", "f53d83e91b70f84f0eb27add7b21169810ab656015033abc3a65a6d354cfd8d3", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: false]}, {:mint_web_socket, "~> 1.0.2", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:tesla, "~> 1.7.0", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.8.1", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm", "1f5926d838a0bb0b9df45d37f2ec1fdf3ba477c30172beea6df500a8dfd59d4b"}, + "libcluster": {:hex, :libcluster, "3.5.0", "5ee4cfde4bdf32b2fef271e33ce3241e89509f4344f6c6a8d4069937484866ba", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebf6561fcedd765a4cd43b4b8c04b1c87f4177b5fb3cbdfe40a780499d72f743"}, + "libring": {:hex, :libring, "1.7.0", "4f245d2f1476cd7ed8f03740f6431acba815401e40299208c7f5c640e1883bda", [:mix], [], "hexpm", "070e3593cb572e04f2c8470dd0c119bc1817a7a0a7f88229f43cf0345268ec42"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"}, - "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, + "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, "mustache": {:hex, :mustache, "0.5.1", "1bfee8a68a8e72d47616541a93a632ae1684c1abc17c9eb5f7bfecb78d7496e5", [:mix], [], "hexpm", "524c9bbb6080a52d7b6806436b4e269e0224c785a228faf3293ef30a75016bfa"}, - "nebulex": {:hex, :nebulex, "2.5.1", "8ffbde30643e76d6cec712281ca68ab05f73170de9e758a39bc7e4e6987f608f", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "8d0d3800d98c68ee19b229b7fe35fac0192ab5963a573612cf74a388e083bccf"}, + "nebulex": {:hex, :nebulex, "2.6.4", "4b00706e0e676474783d988962abf74614480e13c0a32645acb89bb32b660e09", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "25bdabf3fb86035c8151bba60bda20f80f96ae0261db7bd4090878ff63b03581"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"}, + "nx": {:hex, :nx, "0.9.2", "17563029c01bf749aad3c31234326d7665abd0acc33ee2acbe531a4759f29a8a", [:mix], [{:complex, "~> 0.5", [hex: :complex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "914d74741617d8103de8ab1f8c880353e555263e1c397b8a1109f79a3716557f"}, + "oban": {:hex, :oban, "2.19.1", "fc376dcb04782973e384ce675539271363eef65222b23b2a8611e78c0744e5f7", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5f27ba9e79b9af623dacd79d597504176e8a7d24f3f8b5570ead2f6cedd3c5ec"}, + "opsmo": {:hex, :opsmo, "0.3.11", "7f2d425094ef1111f6aad119c093a25af9cc2d43ae7cfb1bffbf6a0593c44f50", [:mix], [{:axon, "~> 0.7", [hex: :axon, repo: "hexpm", optional: false]}, {:nx, "~> 0.9", [hex: :nx, repo: "hexpm", optional: false]}, {:req, "~> 0.5.0", [hex: :req, repo: "hexpm", optional: false]}, {:safetensors, "~> 0.1", [hex: :safetensors, repo: "hexpm", optional: false]}], "hexpm", "a3ced36971b1113af97c58ea41d8159b9a2034edb715d53cf3f6df8a179ef234"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "pogo": {:hex, :pogo, "0.3.0", "4983ae7c52735af088fb3733c17482ca801975bb1f15c32c2c6f08086b1ac47e", [:mix], [{:libring, "~> 1.6.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "a810511e242538e59369efca5aa8bc315dc80ae641b667252ea7930a6dc0ce1e"}, + "polaris": {:hex, :polaris, "0.1.0", "dca61b18e3e801ecdae6ac9f0eca5f19792b44a5cb4b8d63db50fc40fc038d22", [:mix], [{:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "13ef2b166650e533cb24b10e2f3b8ab4f2f449ba4d63156e8c569527f206e2c2"}, "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "prometheus_parser": {:hex, :prometheus_parser, "0.1.10", "d1657b308506261b17f111429b38c427d7e4699b9b77601113ccec658c8cb7f9", [:mix], [{:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3db190840fc9634a8b1a478cbaebd6ef6118d8e4970c71c80a8d11cd24613640"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"}, - "shards": {:hex, :shards, "1.1.0", "ed3032e63ae99f0eaa6d012b8b9f9cead48b9a810b3f91aeac266cfc4118eff6", [:make, :rebar3], [], "hexpm", "1d188e565a54a458a7a601c2fd1e74f5cfeba755c5a534239266d28b7ff124c7"}, + "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, + "safetensors": {:hex, :safetensors, "0.1.3", "7ff3c22391e213289c713898481d492c9c28a49ab1d0705b72630fb8360426b2", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "fe50b53ea59fde4e723dd1a2e31cfdc6013e69343afac84c6be86d6d7c562c14"}, + "shards": {:hex, :shards, "1.1.1", "8b42323457d185b26b15d05187784ce6c5d1e181b35c46fca36c45f661defe02", [:make, :rebar3], [], "hexpm", "169a045dae6668cda15fbf86d31bf433d0dbbaec42c8c23ca4f8f2d405ea8eda"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "x509": {:hex, :x509, "0.8.10", "5d1ec6d5f4db31982f9dc34e6a1eebd631d04599e0b6c1c259f1dadd4495e11f", [:mix], [], "hexpm", "a191221665af28b9bdfff0c986ef55f80e126d8ce751bbdf6cefa846410140c0"}, + "xla": {:hex, :xla, "0.8.0", "fef314d085dd3ee16a0816c095239938f80769150e15db16dfaa435553d7cb16", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "739c61c8d93b97e12ba0369d10e76130224c208f1a76ad293e3581f056833e57"}, } diff --git a/test/fixtures/elastic/availability.json b/test/fixtures/elastic/availability.json new file mode 100644 index 00000000..dfd42e23 --- /dev/null +++ b/test/fixtures/elastic/availability.json @@ -0,0 +1,505 @@ +{ + "responses": [ + { + "_shards": { "failed": 0, "skipped": 0, "successful": 3, "total": 3 }, + "aggregations": { + "arrakis": { + "buckets": [ + { + "doc_count": 7010, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 43072706160 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "livebook" + }, + { + "doc_count": 7009, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 457936032 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "insterra-testing" + }, + { + "doc_count": 7008, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { + "system.filesystem.used.bytes": 4137743538512 + }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "nas-box" + }, + { + "doc_count": 7007, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 223143856 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "apm" + }, + { + "doc_count": 7007, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 1684621920 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "elastic" + }, + { + "doc_count": 7007, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 1629082992 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "kibana" + }, + { + "doc_count": 7007, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 286888960 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "minio" + }, + { + "doc_count": 7007, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 502670816 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "postgresql" + }, + { + "doc_count": 6977, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 22915831552 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "win11" + }, + { + "doc_count": 627, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 141514000 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "uplink" + }, + { + "doc_count": 457, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 122049801248 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "immich" + }, + { + "doc_count": 457, + "filesystem_used_bytes": { + "top": [ + { + "metrics": { "system.filesystem.used.bytes": 14625002368 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + }, + "key": "ollama" + } + ], + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0 + }, + "filesystem_used_bytes": { "value": 4345332738416.0 } + }, + "hits": { + "hits": [], + "max_score": null, + "total": { "relation": "gte", "value": 10000 } + }, + "status": 200, + "timed_out": false, + "took": 5 + }, + { + "_shards": { "failed": 0, "skipped": 0, "successful": 3, "total": 3 }, + "aggregations": { + "arrakis": { + "buckets": [ + { + "doc_count": 749, + "key": "apm", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "elastic", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.001 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "insterra-testing", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "kibana", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.001 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "livebook", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.001 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "minio", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "nas-box", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "postgresql", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.001 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 749, + "key": "win11", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 513, + "key": "uplink", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 415, + "key": "immich", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.001 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + }, + { + "doc_count": 415, + "key": "ollama", + "load_norm_5": { + "top": [ + { + "metrics": { "system.load.norm.5": 0.0 }, + "sort": ["2025-01-10T04:34:00.250Z"] + } + ] + } + } + ], + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0 + }, + "load_norm_5": { "value": 0.005 } + }, + "hits": { + "hits": [], + "max_score": null, + "total": { "relation": "eq", "value": 8084 } + }, + "status": 200, + "timed_out": false, + "took": 1 + }, + { + "_shards": { "failed": 0, "skipped": 0, "successful": 3, "total": 3 }, + "aggregations": { + "arrakis": { + "buckets": [ + { + "doc_count": 7010, + "key": "livebook", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 213258240 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7009, + "key": "insterra-testing", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 33275904 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7008, + "key": "nas-box", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 206090240 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7007, + "key": "apm", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 91762688 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7007, + "key": "elastic", + "memory_used_bytes": { + "top": [ + { + "metrics": { + "system.memory.actual.used.bytes": 36014866432 + }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7007, + "key": "kibana", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 612876288 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7007, + "key": "minio", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 187273216 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 7007, + "key": "postgresql", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 100323328 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 6977, + "key": "win11", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 0 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 627, + "key": "uplink", + "memory_used_bytes": { + "top": [ + { + "metrics": { "system.memory.actual.used.bytes": 1126400 }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 457, + "key": "immich", + "memory_used_bytes": { + "top": [ + { + "metrics": { + "system.memory.actual.used.bytes": 1360203776 + }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + }, + { + "doc_count": 457, + "key": "ollama", + "memory_used_bytes": { + "top": [ + { + "metrics": { + "system.memory.actual.used.bytes": 1026732032 + }, + "sort": ["2025-01-10T05:56:30.214Z"] + } + ] + } + } + ], + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0 + }, + "memory_used_bytes": { "value": 39847788544.0 } + }, + "hits": { + "hits": [], + "max_score": null, + "total": { "relation": "gte", "value": 10000 } + }, + "status": 200, + "timed_out": false, + "took": 6 + } + ], + "took": 7 +} diff --git a/test/fixtures/instellar/monitors/list.json b/test/fixtures/instellar/monitors/list.json new file mode 100644 index 00000000..4574f6c3 --- /dev/null +++ b/test/fixtures/instellar/monitors/list.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "attributes": { + "current_state": "active", + "endpoint": "https://elastic:9200", + "expires_at": "2024-11-21T03:14:17Z", + "id": 1, + "token": "some-token", + "type": "metrics", + "uid": "some-uid" + }, + "id": "1", + "links": { + "self": "http://localhost:4000/uplink/self/monitors/1" + }, + "relationships": {}, + "type": "monitors" + } + ] +} diff --git a/test/fixtures/lxd/cluster/members/arrakis.json b/test/fixtures/lxd/cluster/members/arrakis.json new file mode 100644 index 00000000..751a9d10 --- /dev/null +++ b/test/fixtures/lxd/cluster/members/arrakis.json @@ -0,0 +1,24 @@ +{ + "type": "sync", + "status": "Success", + "status_code": 200, + "operation": "", + "error_code": 0, + "error": "", + "metadata": [ + { + "roles": [ + "database" + ], + "failure_domain": "", + "description": "", + "config": null, + "server_name": "arrakis", + "url": "https://10.130.0.3:8443", + "database": true, + "status": "Online", + "message": "Fully operational", + "architecture": "x86_64" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/lxd/resources/arrakis.json b/test/fixtures/lxd/resources/arrakis.json new file mode 100644 index 00000000..301a30e0 --- /dev/null +++ b/test/fixtures/lxd/resources/arrakis.json @@ -0,0 +1,2094 @@ +{ + "cpu": { + "architecture": "x86_64", + "sockets": [ + { + "cache": [ + { "level": 1, "size": 32768, "type": "Data" }, + { "level": 1, "size": 32768, "type": "Instruction" }, + { "level": 2, "size": 262144, "type": "Unified" }, + { "level": 3, "size": 47185920, "type": "Unified" } + ], + "cores": [ + { + "core": 0, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 0, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 18, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 1, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 1, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 19, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 2, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 2, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 20, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 3, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 21, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 3, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 4, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 22, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 4, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 8, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 23, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 5, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 9, + "die": 0, + "frequency": 1203, + "threads": [ + { + "id": 24, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 6, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 10, + "die": 0, + "frequency": 1201, + "threads": [ + { + "id": 25, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 7, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 11, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 26, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 8, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 16, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 27, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 9, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 17, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 10, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 28, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 18, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 11, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 29, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 19, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 12, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 30, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 20, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 13, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 31, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 24, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 14, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 32, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 25, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 15, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 33, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 26, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 16, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 34, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + }, + { + "core": 27, + "die": 0, + "frequency": 1200, + "threads": [ + { + "id": 17, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 0 + }, + { + "id": 35, + "isolated": false, + "numa_node": 0, + "online": true, + "thread": 1 + } + ] + } + ], + "frequency": 1200, + "frequency_minimum": 1200, + "frequency_turbo": 3600, + "name": "Intel(R) Xeon(R) CPU E5-2697 v4 @ 2.30GHz", + "socket": 0, + "vendor": "GenuineIntel" + } + ], + "total": 36 + }, + "gpu": { + "cards": [ + { + "driver": "simple-framebuffer", + "driver_version": "6.8.0-51-generic", + "drm": { + "card_device": "226:0", + "card_name": "card0", + "control_device": "226:0", + "control_name": "controlD64", + "id": 0 + }, + "numa_node": 0 + }, + { + "driver": "nvidia", + "driver_version": "555.42.02", + "drm": { + "card_device": "226:1", + "card_name": "card1", + "control_device": "226:1", + "control_name": "controlD65", + "id": 1, + "render_device": "226:128", + "render_name": "renderD128" + }, + "numa_node": 0, + "nvidia": { + "architecture": "5.2", + "brand": "Quadro", + "card_device": "195:0", + "card_name": "nvidia0", + "cuda_version": "12.5", + "model": "Quadro M2000", + "nvrm_version": "555.42.02", + "uuid": "GPU-5b8b9ed0-66ba-2fea-0089-8d0a5cbec855" + }, + "pci_address": "0000:01:00.0", + "product": "GM206GL [Quadro M2000]", + "product_id": "1430", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de" + }, + { + "driver": "nvidia", + "driver_version": "555.42.02", + "drm": { + "card_device": "226:2", + "card_name": "card2", + "control_device": "226:2", + "control_name": "controlD66", + "id": 2, + "render_device": "226:129", + "render_name": "renderD129" + }, + "numa_node": 0, + "nvidia": { + "architecture": "8.6", + "brand": "NvidiaRTX", + "card_device": "195:1", + "card_name": "nvidia1", + "cuda_version": "12.5", + "model": "NVIDIA RTX A4500", + "nvrm_version": "555.42.02", + "uuid": "GPU-8de47821-9ed2-75e0-acc4-4646a03f8b35" + }, + "pci_address": "0000:03:00.0", + "product": "GA102GL [RTX A4500]", + "product_id": "2232", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de" + }, + { + "driver": "nvidia", + "driver_version": "555.42.02", + "drm": { + "card_device": "226:3", + "card_name": "card3", + "control_device": "226:3", + "control_name": "controlD67", + "id": 3, + "render_device": "226:130", + "render_name": "renderD130" + }, + "numa_node": 0, + "nvidia": { + "architecture": "8.6", + "brand": "NvidiaRTX", + "card_device": "195:2", + "card_name": "nvidia2", + "cuda_version": "12.5", + "model": "NVIDIA RTX A4500", + "nvrm_version": "555.42.02", + "uuid": "GPU-b03a51a0-2c93-7763-ca0d-ee40362c402c" + }, + "pci_address": "0000:04:00.0", + "product": "GA102GL [RTX A4500]", + "product_id": "2232", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de" + } + ], + "total": 4 + }, + "memory": { + "hugepages_size": 2097152, + "hugepages_total": 0, + "hugepages_used": 0, + "nodes": [ + { + "hugepages_total": 0, + "hugepages_used": 0, + "numa_node": 0, + "total": 137438953472, + "used": 123366297600 + } + ], + "total": 137438953472, + "used": 64379613184 + }, + "network": { + "cards": [ + { + "driver": "r8169", + "driver_version": "6.8.0-51-generic", + "firmware_version": "rtl8168g-2_0.0.1 02/06/13", + "numa_node": 0, + "pci_address": "0000:07:00.0", + "ports": [ + { + "address": "00:e9:a8:78:16:0e", + "auto_negotiation": true, + "id": "enp7s0", + "link_detected": true, + "link_duplex": "full", + "link_speed": 1000, + "port": 0, + "port_type": "twisted pair", + "protocol": "ethernet", + "supported_modes": [ + "10baseT/Half", + "10baseT/Full", + "100baseT/Half", + "100baseT/Full", + "1000baseT/Full" + ], + "supported_ports": ["twisted pair", "media-independent"], + "transceiver_type": "external" + } + ], + "product": "RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller", + "product_id": "8168", + "vendor": "Realtek Semiconductor Co., Ltd.", + "vendor_id": "10ec" + } + ], + "total": 1 + }, + "pci": { + "devices": [ + { + "driver": "", + "driver_version": "", + "iommu_group": 30, + "numa_node": 0, + "pci_address": "0000:00:00.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DMI2", + "product_id": "6f00", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 31, + "numa_node": 0, + "pci_address": "0000:00:01.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D PCI Express Root Port 1", + "product_id": "6f02", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 31, + "numa_node": 0, + "pci_address": "0000:00:01.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D PCI Express Root Port 1", + "product_id": "6f03", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 32, + "numa_node": 0, + "pci_address": "0000:00:02.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D PCI Express Root Port 2", + "product_id": "6f04", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 33, + "numa_node": 0, + "pci_address": "0000:00:03.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D PCI Express Root Port 3", + "product_id": "6f08", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 34, + "numa_node": 0, + "pci_address": "0000:00:05.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Map/VTd_Misc/System Management", + "product_id": "6f28", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 35, + "numa_node": 0, + "pci_address": "0000:00:05.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D IIO Hot Plug", + "product_id": "6f29", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 36, + "numa_node": 0, + "pci_address": "0000:00:05.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D IIO RAS/Control Status/Global Errors", + "product_id": "6f2a", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 37, + "numa_node": 0, + "pci_address": "0000:00:05.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D I/O APIC", + "product_id": "6f2c", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "xhci_hcd", + "driver_version": "6.8.0-51-generic", + "iommu_group": 38, + "numa_node": 0, + "pci_address": "0000:00:14.0", + "product": "8 Series/C220 Series Chipset Family USB xHCI", + "product_id": "8c31", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 39, + "numa_node": 0, + "pci_address": "0000:00:16.0", + "product": "8 Series/C220 Series Chipset Family MEI Controller #1", + "product_id": "8c3a", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "ehci-pci", + "driver_version": "6.8.0-51-generic", + "iommu_group": 40, + "numa_node": 0, + "pci_address": "0000:00:1a.0", + "product": "8 Series/C220 Series Chipset Family USB EHCI #2", + "product_id": "8c2d", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "snd_hda_intel", + "driver_version": "6.8.0-51-generic", + "iommu_group": 0, + "numa_node": 0, + "pci_address": "0000:00:1b.0", + "product": "8 Series/C220 Series Chipset High Definition Audio Controller", + "product_id": "8c20", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 41, + "numa_node": 0, + "pci_address": "0000:00:1c.0", + "product": "8 Series/C220 Series Chipset Family PCI Express Root Port #1", + "product_id": "8c10", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 42, + "numa_node": 0, + "pci_address": "0000:00:1c.1", + "product": "8 Series/C220 Series Chipset Family PCI Express Root Port #2", + "product_id": "8c12", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "pcieport", + "driver_version": "6.8.0-51-generic", + "iommu_group": 43, + "numa_node": 0, + "pci_address": "0000:00:1c.2", + "product": "8 Series/C220 Series Chipset Family PCI Express Root Port #3", + "product_id": "8c14", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "ehci-pci", + "driver_version": "6.8.0-51-generic", + "iommu_group": 44, + "numa_node": 0, + "pci_address": "0000:00:1d.0", + "product": "8 Series/C220 Series Chipset Family USB EHCI #1", + "product_id": "8c26", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "lpc_ich", + "driver_version": "6.8.0-51-generic", + "iommu_group": 45, + "numa_node": 0, + "pci_address": "0000:00:1f.0", + "product": "Z87 Express LPC Controller", + "product_id": "8c44", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "ahci", + "driver_version": "3.0", + "iommu_group": 45, + "numa_node": 0, + "pci_address": "0000:00:1f.2", + "product": "8 Series/C220 Series Chipset Family 6-port SATA Controller 1 [AHCI mode]", + "product_id": "8c02", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "i801_smbus", + "driver_version": "6.8.0-51-generic", + "iommu_group": 45, + "numa_node": 0, + "pci_address": "0000:00:1f.3", + "product": "8 Series/C220 Series Chipset Family SMBus Controller", + "product_id": "8c22", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "nvidia", + "driver_version": "555.42.02", + "iommu_group": 31, + "numa_node": 0, + "pci_address": "0000:01:00.0", + "product": "GM206GL [Quadro M2000]", + "product_id": "1430", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de", + "vpd": {} + }, + { + "driver": "snd_hda_intel", + "driver_version": "6.8.0-51-generic", + "iommu_group": 31, + "numa_node": 0, + "pci_address": "0000:01:00.1", + "product": "GM206 High Definition Audio Controller", + "product_id": "0fba", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de", + "vpd": {} + }, + { + "driver": "nvme", + "driver_version": "1.0", + "iommu_group": 31, + "numa_node": 0, + "pci_address": "0000:02:00.0", + "product": "", + "product_id": "5017", + "vendor": "Sandisk Corp", + "vendor_id": "15b7", + "vpd": {} + }, + { + "driver": "nvidia", + "driver_version": "555.42.02", + "iommu_group": 32, + "numa_node": 0, + "pci_address": "0000:03:00.0", + "product": "GA102GL [RTX A4500]", + "product_id": "2232", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de", + "vpd": {} + }, + { + "driver": "snd_hda_intel", + "driver_version": "6.8.0-51-generic", + "iommu_group": 32, + "numa_node": 0, + "pci_address": "0000:03:00.1", + "product": "GA102 High Definition Audio Controller", + "product_id": "1aef", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de", + "vpd": {} + }, + { + "driver": "nvidia", + "driver_version": "555.42.02", + "iommu_group": 33, + "numa_node": 0, + "pci_address": "0000:04:00.0", + "product": "GA102GL [RTX A4500]", + "product_id": "2232", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de", + "vpd": {} + }, + { + "driver": "snd_hda_intel", + "driver_version": "6.8.0-51-generic", + "iommu_group": 33, + "numa_node": 0, + "pci_address": "0000:04:00.1", + "product": "GA102 High Definition Audio Controller", + "product_id": "1aef", + "vendor": "NVIDIA Corporation", + "vendor_id": "10de", + "vpd": {} + }, + { + "driver": "xhci_hcd", + "driver_version": "6.8.0-51-generic", + "iommu_group": 46, + "numa_node": 0, + "pci_address": "0000:06:00.0", + "product": "VL805/806 xHCI USB 3.0 Controller", + "product_id": "3483", + "vendor": "VIA Technologies, Inc.", + "vendor_id": "1106", + "vpd": {} + }, + { + "driver": "r8169", + "driver_version": "6.8.0-51-generic", + "iommu_group": 47, + "numa_node": 0, + "pci_address": "0000:07:00.0", + "product": "RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller", + "product_id": "8168", + "vendor": "Realtek Semiconductor Co., Ltd.", + "vendor_id": "10ec", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 1, + "numa_node": 0, + "pci_address": "0000:ff:0b.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D R3 QPI Link 0/1", + "product_id": "6f81", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 1, + "numa_node": 0, + "pci_address": "0000:ff:0b.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D R3 QPI Link 0/1", + "product_id": "6f36", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 1, + "numa_node": 0, + "pci_address": "0000:ff:0b.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D R3 QPI Link 0/1", + "product_id": "6f37", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 1, + "numa_node": 0, + "pci_address": "0000:ff:0b.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D R3 QPI Link Debug", + "product_id": "6f76", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe0", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe1", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe2", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe3", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe4", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe5", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe6", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 2, + "numa_node": 0, + "pci_address": "0000:ff:0c.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe7", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe8", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fe9", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fea", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6feb", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fec", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fed", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fee", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 3, + "numa_node": 0, + "pci_address": "0000:ff:0d.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6fef", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 4, + "numa_node": 0, + "pci_address": "0000:ff:0e.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ff0", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 4, + "numa_node": 0, + "pci_address": "0000:ff:0e.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ff1", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ff8", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ff9", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ffa", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ffb", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ffc", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ffd", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 5, + "numa_node": 0, + "pci_address": "0000:ff:0f.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Caching Agent", + "product_id": "6ffe", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 6, + "numa_node": 0, + "pci_address": "0000:ff:10.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D R2PCIe Agent", + "product_id": "6f1d", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 6, + "numa_node": 0, + "pci_address": "0000:ff:10.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D R2PCIe Agent", + "product_id": "6f34", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 6, + "numa_node": 0, + "pci_address": "0000:ff:10.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Ubox", + "product_id": "6f1e", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 6, + "numa_node": 0, + "pci_address": "0000:ff:10.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Ubox", + "product_id": "6f7d", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 6, + "numa_node": 0, + "pci_address": "0000:ff:10.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Ubox", + "product_id": "6f1f", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 7, + "numa_node": 0, + "pci_address": "0000:ff:12.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Home Agent 0", + "product_id": "6fa0", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 7, + "numa_node": 0, + "pci_address": "0000:ff:12.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Home Agent 0", + "product_id": "6f30", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 7, + "numa_node": 0, + "pci_address": "0000:ff:12.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Home Agent 1", + "product_id": "6f60", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 7, + "numa_node": 0, + "pci_address": "0000:ff:12.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Home Agent 1", + "product_id": "6f38", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 8, + "numa_node": 0, + "pci_address": "0000:ff:13.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Target Address/Thermal/RAS", + "product_id": "6fa8", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 9, + "numa_node": 0, + "pci_address": "0000:ff:13.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Target Address/Thermal/RAS", + "product_id": "6f71", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 10, + "numa_node": 0, + "pci_address": "0000:ff:13.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel Target Address Decoder", + "product_id": "6faa", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 11, + "numa_node": 0, + "pci_address": "0000:ff:13.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel Target Address Decoder", + "product_id": "6fab", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 12, + "numa_node": 0, + "pci_address": "0000:ff:13.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 0/1 Broadcast", + "product_id": "6fae", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 12, + "numa_node": 0, + "pci_address": "0000:ff:13.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Global Broadcast", + "product_id": "6faf", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 13, + "numa_node": 0, + "pci_address": "0000:ff:14.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel 0 Thermal Control", + "product_id": "6fb0", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 14, + "numa_node": 0, + "pci_address": "0000:ff:14.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel 1 Thermal Control", + "product_id": "6fb1", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 15, + "numa_node": 0, + "pci_address": "0000:ff:14.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel 0 Error", + "product_id": "6fb2", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 16, + "numa_node": 0, + "pci_address": "0000:ff:14.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 0 - Channel 1 Error", + "product_id": "6fb3", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 17, + "numa_node": 0, + "pci_address": "0000:ff:14.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 0/1 Interface", + "product_id": "6fbc", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 17, + "numa_node": 0, + "pci_address": "0000:ff:14.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 0/1 Interface", + "product_id": "6fbd", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 17, + "numa_node": 0, + "pci_address": "0000:ff:14.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 0/1 Interface", + "product_id": "6fbe", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 17, + "numa_node": 0, + "pci_address": "0000:ff:14.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 0/1 Interface", + "product_id": "6fbf", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 18, + "numa_node": 0, + "pci_address": "0000:ff:16.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Target Address/Thermal/RAS", + "product_id": "6f68", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 19, + "numa_node": 0, + "pci_address": "0000:ff:16.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Target Address/Thermal/RAS", + "product_id": "6f79", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 20, + "numa_node": 0, + "pci_address": "0000:ff:16.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Channel Target Address Decoder", + "product_id": "6f6a", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 21, + "numa_node": 0, + "pci_address": "0000:ff:16.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Channel Target Address Decoder", + "product_id": "6f6b", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 22, + "numa_node": 0, + "pci_address": "0000:ff:16.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 2/3 Broadcast", + "product_id": "6f6e", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 22, + "numa_node": 0, + "pci_address": "0000:ff:16.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Global Broadcast", + "product_id": "6f6f", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 23, + "numa_node": 0, + "pci_address": "0000:ff:17.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 1 - Channel 0 Thermal Control", + "product_id": "6fd0", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "bdx_uncore", + "driver_version": "6.8.0-51-generic", + "iommu_group": 24, + "numa_node": 0, + "pci_address": "0000:ff:17.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 1 - Channel 1 Thermal Control", + "product_id": "6fd1", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 25, + "numa_node": 0, + "pci_address": "0000:ff:17.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 1 - Channel 0 Error", + "product_id": "6fd2", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 26, + "numa_node": 0, + "pci_address": "0000:ff:17.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Memory Controller 1 - Channel 1 Error", + "product_id": "6fd3", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 27, + "numa_node": 0, + "pci_address": "0000:ff:17.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 2/3 Interface", + "product_id": "6fb8", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 27, + "numa_node": 0, + "pci_address": "0000:ff:17.5", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 2/3 Interface", + "product_id": "6fb9", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 27, + "numa_node": 0, + "pci_address": "0000:ff:17.6", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 2/3 Interface", + "product_id": "6fba", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 27, + "numa_node": 0, + "pci_address": "0000:ff:17.7", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D DDRIO Channel 2/3 Interface", + "product_id": "6fbb", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 28, + "numa_node": 0, + "pci_address": "0000:ff:1e.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6f98", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 28, + "numa_node": 0, + "pci_address": "0000:ff:1e.1", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6f99", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 28, + "numa_node": 0, + "pci_address": "0000:ff:1e.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6f9a", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 28, + "numa_node": 0, + "pci_address": "0000:ff:1e.3", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6fc0", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 28, + "numa_node": 0, + "pci_address": "0000:ff:1e.4", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6f9c", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 29, + "numa_node": 0, + "pci_address": "0000:ff:1f.0", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6f88", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + }, + { + "driver": "", + "driver_version": "", + "iommu_group": 29, + "numa_node": 0, + "pci_address": "0000:ff:1f.2", + "product": "Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D Power Control Unit", + "product_id": "6f8a", + "vendor": "Intel Corporation", + "vendor_id": "8086", + "vpd": {} + } + ], + "total": 102 + }, + "storage": { + "disks": [ + { + "block_size": 512, + "device": "259:0", + "device_id": "nvme-eui.e8238fa6bf530001001b448b4b303d58", + "device_path": "pci-0000:02:00.0-nvme-1", + "firmware_version": "731100WD", + "id": "nvme0n1", + "model": "WD_BLACK SN770 500GB", + "mounted": true, + "numa_node": 0, + "partitions": [ + { + "device": "259:1", + "id": "nvme0n1p1", + "mounted": true, + "partition": 1, + "read_only": false, + "size": 1127219200 + }, + { + "device": "259:2", + "id": "nvme0n1p2", + "mounted": true, + "partition": 2, + "read_only": false, + "size": 498978521088 + } + ], + "pci_address": "0000:02:00.0", + "read_only": false, + "removable": false, + "rpm": 0, + "serial": "22361C807768", + "size": 500107862016, + "type": "nvme", + "wwn": "eui.e8238fa6bf530001001b448b4b303d58" + }, + { + "block_size": 4096, + "device": "8:0", + "device_id": "wwn-0x5000c500cfb3b627", + "device_path": "pci-0000:00:1f.2-ata-1.0", + "firmware_version": "EN01", + "id": "sda", + "model": "ST4000NE001-2MA1", + "mounted": false, + "numa_node": 0, + "partitions": [ + { + "device": "8:1", + "id": "sda1", + "mounted": false, + "partition": 1, + "read_only": false, + "size": 4000776716288 + }, + { + "device": "8:9", + "id": "sda9", + "mounted": false, + "partition": 9, + "read_only": false, + "size": 8388608 + } + ], + "read_only": false, + "removable": false, + "rpm": 1, + "serial": "WJG0HZ9J", + "size": 4000787030016, + "type": "scsi" + }, + { + "block_size": 4096, + "device": "8:16", + "device_id": "wwn-0x5000c500cfb31951", + "device_path": "pci-0000:00:1f.2-ata-2.0", + "firmware_version": "EN01", + "id": "sdb", + "model": "ST4000NE001-2MA1", + "mounted": false, + "numa_node": 0, + "partitions": [ + { + "device": "8:17", + "id": "sdb1", + "mounted": false, + "partition": 1, + "read_only": false, + "size": 4000776716288 + }, + { + "device": "8:25", + "id": "sdb9", + "mounted": false, + "partition": 9, + "read_only": false, + "size": 8388608 + } + ], + "read_only": false, + "removable": false, + "rpm": 1, + "serial": "WJG172CL", + "size": 4000787030016, + "type": "scsi" + }, + { + "block_size": 4096, + "device": "8:32", + "device_id": "wwn-0x5000c500a3080d4f", + "device_path": "pci-0000:00:1f.2-ata-3.0", + "firmware_version": "SC60", + "id": "sdc", + "model": "ST4000VN008-2DR1", + "mounted": false, + "numa_node": 0, + "partitions": [ + { + "device": "8:33", + "id": "sdc1", + "mounted": false, + "partition": 1, + "read_only": false, + "size": 4000776716288 + }, + { + "device": "8:41", + "id": "sdc9", + "mounted": false, + "partition": 9, + "read_only": false, + "size": 8388608 + } + ], + "read_only": false, + "removable": false, + "rpm": 1, + "serial": "ZGY04H9X", + "size": 4000787030016, + "type": "scsi" + }, + { + "block_size": 4096, + "device": "8:48", + "device_id": "wwn-0x5000c500cfb34eef", + "device_path": "pci-0000:00:1f.2-ata-4.0", + "firmware_version": "EN01", + "id": "sdd", + "model": "ST4000NE001-2MA1", + "mounted": false, + "numa_node": 0, + "partitions": [ + { + "device": "8:49", + "id": "sdd1", + "mounted": false, + "partition": 1, + "read_only": false, + "size": 4000776716288 + }, + { + "device": "8:57", + "id": "sdd9", + "mounted": false, + "partition": 9, + "read_only": false, + "size": 8388608 + } + ], + "read_only": false, + "removable": false, + "rpm": 1, + "serial": "WJG1724B", + "size": 4000787030016, + "type": "scsi" + }, + { + "block_size": 4096, + "device": "8:64", + "device_id": "wwn-0x5000c500cfb3c516", + "device_path": "pci-0000:00:1f.2-ata-5.0", + "firmware_version": "EN01", + "id": "sde", + "model": "ST4000NE001-2MA1", + "mounted": false, + "numa_node": 0, + "partitions": [ + { + "device": "8:65", + "id": "sde1", + "mounted": false, + "partition": 1, + "read_only": false, + "size": 4000776716288 + }, + { + "device": "8:73", + "id": "sde9", + "mounted": false, + "partition": 9, + "read_only": false, + "size": 8388608 + } + ], + "read_only": false, + "removable": false, + "rpm": 1, + "serial": "WJG0DQ7P", + "size": 4000787030016, + "type": "scsi" + }, + { + "block_size": 4096, + "device": "8:80", + "device_id": "wwn-0x5000c500a30819cb", + "device_path": "pci-0000:00:1f.2-ata-6.0", + "firmware_version": "SC60", + "id": "sdf", + "model": "ST4000VN008-2DR1", + "mounted": false, + "numa_node": 0, + "partitions": [ + { + "device": "8:81", + "id": "sdf1", + "mounted": false, + "partition": 1, + "read_only": false, + "size": 4000776716288 + }, + { + "device": "8:89", + "id": "sdf9", + "mounted": false, + "partition": 9, + "read_only": false, + "size": 8388608 + } + ], + "read_only": false, + "removable": false, + "rpm": 1, + "serial": "ZGY0494K", + "size": 4000787030016, + "type": "scsi" + } + ], + "total": 21 + }, + "system": { + "chassis": { + "serial": "Default string", + "type": "Desktop", + "vendor": "Default string", + "version": "Default string" + }, + "family": "Default string", + "firmware": { + "date": "07/08/2021", + "vendor": "American Megatrends Inc.", + "version": "5.11" + }, + "motherboard": { + "product": "X99-E8I", + "serial": "1234567890", + "vendor": "JINGSHA", + "version": "1.0" + }, + "product": "X99-E8I", + "serial": "", + "sku": "Default string", + "type": "physical", + "uuid": "03000200-0400-0500-0006-000700080009", + "vendor": "JINGSHA", + "version": "X99-E8I" + }, + "usb": { + "devices": [ + { + "bus_address": 3, + "device_address": 3, + "interfaces": [ + { + "class": "Human Interface Device", + "class_id": 3, + "driver": "usbfs", + "driver_version": "6.8.0-51-generic", + "number": 0, + "subclass": "", + "subclass_id": 0 + } + ], + "product": "AK500-DIGITAL", + "product_id": "0003", + "serial": "", + "speed": 12, + "vendor": "", + "vendor_id": "3633" + } + ], + "total": 1 + } +} diff --git a/test/uplink/availability/router_test.exs b/test/uplink/availability/router_test.exs new file mode 100644 index 00000000..9eceb740 --- /dev/null +++ b/test/uplink/availability/router_test.exs @@ -0,0 +1,210 @@ +defmodule Uplink.Availability.RouterTest do + use ExUnit.Case + use Plug.Test + + alias Uplink.Cache + alias Uplink.Availability.Router + + @opts Router.init([]) + + @valid_body Jason.encode!(%{ + "actor" => %{ + "provider" => "instellar", + "identifier" => "zacksiri", + "id" => "1" + }, + "requirement" => %{ + "project" => "test", + "cpu" => 1, + "memory" => 128_000_000, + "disk" => 300_000_000 + } + }) + + @invalid_body Jason.encode!(%{ + "actor" => %{ + "provider" => "instellar", + "identifier" => "zacksiri", + "id" => "1" + }, + "requirement" => %{ + "project" => "test" + } + }) + + setup do + bypass = Bypass.open() + + Application.put_env( + :uplink, + Uplink.Clients.Instellar, + endpoint: "http://localhost:#{bypass.port}/uplink" + ) + + Cache.put(:self, %{ + "credential" => %{ + "endpoint" => "http://localhost:#{bypass.port}" + }, + "uplink" => %{"id" => 1} + }) + + cluster_members_response = + File.read!("test/fixtures/lxd/cluster/members/arrakis.json") + + monitors_list_response = + File.read!("test/fixtures/instellar/monitors/list.json") + + %{"data" => [monitor]} = Jason.decode!(monitors_list_response) + + %{"attributes" => attributes} = monitor + + attributes = + Map.put(attributes, "endpoint", "http://localhost:#{bypass.port}") + + monitors_list_response = + Jason.encode_to_iodata!(%{ + "data" => [ + %{"attributes" => attributes} + ] + }) + + resources_response = File.read!("test/fixtures/lxd/resources/arrakis.json") + + availability_query_response = + File.read!("test/fixtures/elastic/availability.json") + + instances_response = File.read!("test/fixtures/lxd/instances/list.json") + + Cache.delete(:cluster_members) + Cache.delete({:monitors, :metrics}) + + {:ok, + bypass: bypass, + resources_response: resources_response, + cluster_members_response: cluster_members_response, + availability_query_response: availability_query_response, + monitors_list_response: monitors_list_response, + instances_response: instances_response} + end + + describe "POST /resources" do + setup %{ + bypass: bypass, + resources_response: resource_response, + availability_query_response: availability_query_response, + cluster_members_response: cluster_members_response, + monitors_list_response: monitors_list_response, + instances_response: instances_response + } do + Bypass.expect_once(bypass, "GET", "/uplink/self/monitors", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, monitors_list_response) + end) + + Bypass.expect_once(bypass, "GET", "/1.0/cluster/members", fn conn -> + assert %{"recursion" => "1"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, cluster_members_response) + end) + + Bypass.expect(bypass, "GET", "/1.0/resources", fn conn -> + assert %{"target" => _target} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, resource_response) + end) + + Bypass.expect_once(bypass, "POST", "/_msearch", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, availability_query_response) + end) + + Bypass.expect_once(bypass, "GET", "/1.0/instances", fn conn -> + assert %{"project" => "test"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, instances_response) + end) + + signature = + :crypto.mac(:hmac, :sha256, Uplink.Secret.get(), @valid_body) + |> Base.encode16() + |> String.downcase() + + {:ok, signature: signature} + end + + test "can successfully fetch resources with availability", %{ + signature: signature + } do + conn = + conn(:post, "/resources", @valid_body) + |> put_req_header("x-uplink-signature-256", "sha256=#{signature}") + |> put_req_header("content-type", "application/json") + |> Router.call(@opts) + + assert %{"data" => resources} = Jason.decode!(conn.resp_body) + + [resource] = resources + + assert %{ + "node" => _node, + "total" => _total, + "used" => _used, + "available" => _available, + "placeability" => _placeability + } = resource + end + end + + describe "invalid request" do + setup do + signature = + :crypto.mac(:hmac, :sha256, Uplink.Secret.get(), @invalid_body) + |> Base.encode16() + |> String.downcase() + + {:ok, signature: signature} + end + + test "return error when signature is invalid", %{ + signature: signature + } do + conn = + conn(:post, "/resources", Jason.encode!(%{})) + |> put_req_header("x-uplink-signature-256", "sha256=#{signature}") + |> put_req_header("content-type", "application/json") + |> Router.call(@opts) + + assert %{"data" => error} = Jason.decode!(conn.resp_body) + + assert %{"error" => %{"message" => "invalid signature"}} = error + end + + test "return error when requirement is invalid", %{ + signature: signature + } do + conn = + conn(:post, "/resources", @invalid_body) + |> put_req_header("x-uplink-signature-256", "sha256=#{signature}") + |> put_req_header("content-type", "application/json") + |> Router.call(@opts) + + assert %{"data" => errors} = Jason.decode!(conn.resp_body) + + assert %{ + "errors" => %{ + "cpu" => ["can't be blank"], + "disk" => ["can't be blank"], + "memory" => ["can't be blank"] + } + } = errors + end + end +end diff --git a/test/uplink/availability_test.exs b/test/uplink/availability_test.exs new file mode 100644 index 00000000..bad775f1 --- /dev/null +++ b/test/uplink/availability_test.exs @@ -0,0 +1,192 @@ +defmodule Uplink.AvailabilityTest do + use ExUnit.Case + + alias Uplink.Cache + alias Uplink.Availability + + setup do + bypass = Bypass.open() + + Application.put_env( + :uplink, + Uplink.Clients.Instellar, + endpoint: "http://localhost:#{bypass.port}/uplink" + ) + + Cache.put(:self, %{ + "credential" => %{ + "endpoint" => "http://localhost:#{bypass.port}" + }, + "uplink" => %{"id" => 1} + }) + + cluster_members_response = + File.read!("test/fixtures/lxd/cluster/members/arrakis.json") + + monitors_list_response = + File.read!("test/fixtures/instellar/monitors/list.json") + + %{"data" => [monitor]} = Jason.decode!(monitors_list_response) + + %{"attributes" => attributes} = monitor + + attributes = + Map.put(attributes, "endpoint", "http://localhost:#{bypass.port}") + + monitors_list_response = + Jason.encode_to_iodata!(%{ + "data" => [ + %{"attributes" => attributes} + ] + }) + + availability_query_response = + File.read!("test/fixtures/elastic/availability.json") + + instances_response = File.read!("test/fixtures/lxd/instances/list.json") + + resource_response = File.read!("test/fixtures/lxd/resources/arrakis.json") + + Cache.delete(:cluster_members) + Cache.delete({:monitors, :metrics}) + + {:ok, + bypass: bypass, + cluster_members_response: cluster_members_response, + availability_query_response: availability_query_response, + monitors_list_response: monitors_list_response, + instances_response: instances_response, + resource_response: resource_response} + end + + describe "check availability of the nodes in the cluster" do + setup %{ + bypass: bypass, + availability_query_response: availability_query_response, + cluster_members_response: cluster_members_response, + monitors_list_response: monitors_list_response, + instances_response: instances_response, + resource_response: resource_response + } do + Bypass.expect_once(bypass, "GET", "/uplink/self/monitors", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, monitors_list_response) + end) + + Bypass.expect_once(bypass, "GET", "/1.0/cluster/members", fn conn -> + assert %{"recursion" => "1"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, cluster_members_response) + end) + + Bypass.expect(bypass, "GET", "/1.0/resources", fn conn -> + assert %{"target" => "arrakis"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, resource_response) + end) + + Bypass.expect_once(bypass, "POST", "/_msearch", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, availability_query_response) + end) + + Bypass.expect_once(bypass, "GET", "/1.0/instances", fn conn -> + assert %{"project" => "test"} = conn.query_params + + conn + |> Plug.Conn.put_resp_header("content-type", "application/json") + |> Plug.Conn.resp(200, instances_response) + end) + + :ok + end + + test "return availability check result when all resources are available" do + {:ok, requirements} = + Availability.process_requirement(%{ + "project" => "test", + "cpu" => 1, + "memory" => 128_000_000, + "disk" => 300_000_000 + }) + + assert {:ok, resources} = Availability.check!(requirements) + + assert [%Availability.Resource{placeability: placeability}] = resources + + assert %Availability.Placeability{ + cpu: Decimal.new("1.0"), + memory: Decimal.new("0.9999998211860657"), + disk: Decimal.new("1.0") + } == placeability + end + + test "return availability check result when all memory is not available" do + {:ok, requirements} = + Availability.process_requirement(%{ + "project" => "test", + "cpu" => 1, + "memory" => 64_000_000_000, + "disk" => 300_000_000 + }) + + assert {:ok, resources} = Availability.check!(requirements) + + assert [%Availability.Resource{placeability: placeability}] = resources + + assert %Availability.Placeability{ + cpu: Decimal.new("1.0"), + memory: Decimal.new("0.0"), + disk: Decimal.new("1.0") + } == placeability + end + + test "return availability check result when cpu and memory is not available" do + {:ok, requirements} = + Availability.process_requirement(%{ + "project" => "test", + "cpu" => 36, + "memory" => 64_000_000_000, + "disk" => 300_000_000 + }) + + assert {:ok, resources} = Availability.check!(requirements) + + assert [%Availability.Resource{placeability: placeability}] = resources + + assert %Availability.Placeability{ + cpu: Decimal.new("7.951236069532033E-35"), + memory: Decimal.new("0.0"), + disk: Decimal.new("1.0") + } == placeability + end + + test "return availability check result when cpu, memory and disk is not available" do + {:ok, requirements} = + Availability.process_requirement(%{ + "project" => "test", + "cpu" => 36, + # 64GB requested + "memory" => 64_000_000_000, + # 24TB requested + "disk" => 24_000_000_000_000 + }) + + assert {:ok, resources} = Availability.check!(requirements) + + assert [%Availability.Resource{placeability: placeability}] = resources + + assert %Availability.Placeability{ + cpu: Decimal.new("7.951236069532033E-35"), + memory: Decimal.new("0.0"), + disk: Decimal.new("0.0") + } == placeability + end + end +end diff --git a/test/uplink/metrics/pipeline_test.exs b/test/uplink/metrics/pipeline_test.exs index b36be1d2..0d116fdf 100644 --- a/test/uplink/metrics/pipeline_test.exs +++ b/test/uplink/metrics/pipeline_test.exs @@ -4,13 +4,12 @@ defmodule Uplink.Metrics.PipelineTest do import Uplink.Scenarios.Pipeline import AssertAsync - alias Uplink.Cache alias Uplink.Pipelines setup [:self, :messages] setup do - Cache.put_new({:monitors, :metrics}, []) + Pipelines.update_monitors(:metrics, []) Pipelines.start(Uplink.Metrics.Pipeline) diff --git a/test/uplink/nodes/router_test.exs b/test/uplink/nodes/router_test.exs deleted file mode 100644 index 6ba03364..00000000 --- a/test/uplink/nodes/router_test.exs +++ /dev/null @@ -1,91 +0,0 @@ -defmodule Uplink.Nodes.RouterTest do - use ExUnit.Case - use Plug.Test - - alias Uplink.Cache - alias Uplink.Nodes.Router - - @opts Router.init([]) - - @valid_body Jason.encode!(%{ - "actor" => %{ - "provider" => "instellar", - "identifier" => "zacksiri", - "id" => "1" - } - }) - - setup do - bypass = Bypass.open() - - Cache.put(:self, %{ - "credential" => %{ - "endpoint" => "http://localhost:#{bypass.port}" - } - }) - - members_response = File.read!("test/fixtures/lxd/cluster/members/list.json") - - resource_response = File.read!("test/fixtures/lxd/resources/show.json") - - Cache.delete(:cluster_members) - - {:ok, - bypass: bypass, - members_response: members_response, - resource_response: resource_response} - end - - describe "list nodes" do - setup do - signature = - :crypto.mac(:hmac, :sha256, Uplink.Secret.get(), @valid_body) - |> Base.encode16() - |> String.downcase() - - {:ok, signature: signature} - end - - test "can successfully fetch list of nodes", %{ - bypass: bypass, - signature: signature, - members_response: members_response, - resource_response: resource_response - } do - Bypass.expect_once(bypass, "GET", "/1.0/cluster/members", fn conn -> - assert %{"recursion" => "1"} = conn.query_params - - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, members_response) - end) - - Bypass.expect_once(bypass, "GET", "/1.0/resources", fn conn -> - assert %{"target" => "ubuntu-s-1vcpu-1gb-sgp1-01"} = conn.params - - conn - |> Plug.Conn.put_resp_header("content-type", "application/json") - |> Plug.Conn.resp(200, resource_response) - end) - - conn = - conn(:post, "/", @valid_body) - |> put_req_header("x-uplink-signature-256", "sha256=#{signature}") - |> put_req_header("content-type", "application/json") - |> Router.call(@opts) - - assert %{"data" => nodes} = Jason.decode!(conn.resp_body) - - [node] = nodes - - assert %{ - "cpu_cores_count" => _, - "name" => _, - "total_memory" => _, - "total_storage" => _ - } = node - - assert conn.status == 200 - end - end -end