From 4fd86fbc75670878308fd6a56b7778334b52ccd8 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 25 May 2026 20:05:15 +0300 Subject: refactor(manager): extract focused collaborators from Manager God Class Extract VM lifecycle, SSH execution, WireGuard setup, model switching, end-to-end inference tests, and provisioning orchestration into separate collaborator classes. Manager becomes a thin facade (~80 lines). Also fixes CLI edge-cases: status/model-list when no VMs are active, and threads --vllm/--ollama flags through the provisioning pipeline. --- lib/hyperstack/inference_tester.rb | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 lib/hyperstack/inference_tester.rb (limited to 'lib/hyperstack/inference_tester.rb') diff --git a/lib/hyperstack/inference_tester.rb b/lib/hyperstack/inference_tester.rb new file mode 100644 index 0000000..fa8f385 --- /dev/null +++ b/lib/hyperstack/inference_tester.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'json' +require 'net/http' + +module HyperstackVM + # End-to-end inference tests over WireGuard. + class InferenceTester + def initialize(config:, out:) + @config = config + @out = out + end + + def test(state) + wg_ip = @config.wireguard_gateway_hostname + vllm_enabled = state_vllm_enabled?(state) + ollama_enabled = state_ollama_enabled?(state) + info "Running end-to-end inference tests via WireGuard (#{wg_ip})..." + test_vllm(wg_ip) if vllm_enabled + info " Ollama test: connect via SSH and run 'ollama list' to verify models." if ollama_enabled + info 'All inference tests passed.' + end + + private + + def test_vllm(wg_ip) + port = @config.ollama_port + info " Testing vLLM models list at http://#{wg_ip}:#{port}/v1/models..." + uri = URI("http://#{wg_ip}:#{port}/v1/models") + resp = Net::HTTP.get_response(uri) + raise Error, "vLLM /v1/models returned HTTP #{resp.code}" unless resp.code == '200' + + models = JSON.parse(resp.body).fetch('data', []).map { |m| m['id'] } + raise Error, 'vLLM returned an empty model list' if models.empty? + + model = models.first + info " Models loaded: #{models.join(', ')}" + info ' Testing vLLM inference...' + reply = chat(wg_ip, port, model, 'Say hello in five words.') + info " vLLM response: #{reply}" + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e + raise Error, "Cannot reach vLLM at #{wg_ip}:#{port} — is WireGuard (wg1) active? (#{e.message})" + end + + def chat(host, port, model, prompt) + uri = URI("http://#{host}:#{port}/v1/chat/completions") + req = Net::HTTP::Post.new(uri) + req['Content-Type'] = 'application/json' + req['Authorization'] = 'Bearer EMPTY' + req.body = JSON.generate( + 'model' => model, + 'messages' => [{ 'role' => 'user', 'content' => prompt }], + 'max_tokens' => 500 + ) + resp = Net::HTTP.start(uri.host, uri.port, open_timeout: 10, read_timeout: 120) { |h| h.request(req) } + raise Error, "vLLM inference returned HTTP #{resp.code}" unless resp.code == '200' + + JSON.parse(resp.body).dig('choices', 0, 'message', 'content').to_s.strip + end + + def state_vllm_enabled?(state) + recorded = state&.dig('services', 'vllm_enabled') + return recorded unless recorded.nil? + return true if state&.key?('vllm_setup_at') + @config.vllm_install_enabled? + end + + def state_ollama_enabled?(state) + recorded = state&.dig('services', 'ollama_enabled') + return recorded unless recorded.nil? + return true if state&.key?('ollama_installed_at') || state&.key?('ollama_setup_at') + @config.ollama_install_enabled? + end + + def info(message) + @out.puts(message) + end + end +end -- cgit v1.2.3