Class: VagrantPlugins::ProviderZone::QGA::Client

Inherits:
Object
  • Object
show all
Includes:
ClientExec
Defined in:
lib/vagrant-zones/qga/client.rb

Overview

JSON-RPC client speaking the QEMU Guest Agent protocol over a UNIX socket. One socket per request to avoid stale buffers and partial reads. TODO(qga-abort): wire guest-exec-cancel on SIGINT mid-exec.

Constant Summary collapse

DEFAULT_READ_TIMEOUT =
30
PING_READ_TIMEOUT =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ClientExec

#exec, #shell

Constructor Details

#initialize(socket_path) ⇒ Client

Returns a new instance of Client.



22
23
24
# File 'lib/vagrant-zones/qga/client.rb', line 22

def initialize(socket_path)
  @socket_path = socket_path
end

Instance Attribute Details

#socket_pathObject (readonly)

Returns the value of attribute socket_path.



20
21
22
# File 'lib/vagrant-zones/qga/client.rb', line 20

def socket_path
  @socket_path
end

Instance Method Details

#hostnameObject



89
90
91
# File 'lib/vagrant-zones/qga/client.rb', line 89

def hostname
  request('guest-get-host-name')
end

#infoObject



81
82
83
# File 'lib/vagrant-zones/qga/client.rb', line 81

def info
  request('guest-info')
end

#network_interfacesObject



93
94
95
# File 'lib/vagrant-zones/qga/client.rb', line 93

def network_interfaces
  request('guest-network-get-interfaces')
end

#osinfoObject



85
86
87
# File 'lib/vagrant-zones/qga/client.rb', line 85

def osinfo
  request('guest-get-osinfo')
end

#pingObject



74
75
76
77
78
79
# File 'lib/vagrant-zones/qga/client.rb', line 74

def ping
  request('guest-ping', nil, read_timeout: PING_READ_TIMEOUT)
  true
rescue StandardError
  false
end

#request(execute, arguments = nil, read_timeout: DEFAULT_READ_TIMEOUT) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/vagrant-zones/qga/client.rb', line 50

def request(execute, arguments = nil, read_timeout: DEFAULT_READ_TIMEOUT)
  payload = { execute: execute }
  payload[:arguments] = arguments if arguments
  sock = UNIXSocket.new(@socket_path)
  begin
    sock.write("#{JSON.generate(payload)}\n")
    sock.flush
    line = nil
    Timeout.timeout(read_timeout) { line = sock.gets }
    raise Errors::QGAError, message: "empty response from qga (#{execute})" if line.nil? || line.strip.empty?

    response = JSON.parse(line)
    raise Errors::QGAError, message: "#{execute}: #{response['error']}" if response['error']

    response['return']
  ensure
    sock.close
  end
rescue Errno::ENOENT, Errno::ECONNREFUSED => e
  raise Errors::QGAError, message: "qga socket unavailable: #{e.message}"
rescue Timeout::Error
  raise Errors::QGATimeout, message: "qga read timeout for #{execute}"
end

#set_user_password(username, password, crypted: false) ⇒ Object



97
98
99
100
101
102
103
# File 'lib/vagrant-zones/qga/client.rb', line 97

def set_user_password(username, password, crypted: false)
  request('guest-set-user-password', {
            username: username,
            password: Base64.strict_encode64(password),
            crypted: crypted
          })
end

#shutdown(mode = 'powerdown') ⇒ Object



105
106
107
108
109
110
# File 'lib/vagrant-zones/qga/client.rb', line 105

def shutdown(mode = 'powerdown')
  request('guest-shutdown', { mode: mode })
rescue Errors::QGAError, Errors::QGATimeout
  # Guest disconnects mid-shutdown; treat as success.
  nil
end

#socket_present?Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/vagrant-zones/qga/client.rb', line 26

def socket_present?
  File.exist?(@socket_path) && File.socket?(@socket_path)
end

#wait_for_ready(timeout) ⇒ Object



40
41
42
43
44
45
46
47
48
# File 'lib/vagrant-zones/qga/client.rb', line 40

def wait_for_ready(timeout)
  deadline = Time.now + timeout
  loop do
    return if ping
    raise Errors::QGATimeout, message: "guest-ping did not respond within #{timeout}s" if Time.now > deadline

    sleep 2
  end
end

#wait_for_socket(timeout) ⇒ Object



30
31
32
33
34
35
36
37
38
# File 'lib/vagrant-zones/qga/client.rb', line 30

def wait_for_socket(timeout)
  deadline = Time.now + timeout
  loop do
    return if socket_present?
    raise Errors::QGATimeout, message: "qga.sock not present after #{timeout}s at #{@socket_path}" if Time.now > deadline

    sleep 1
  end
end