libvirt_ffi 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +78 -0
  3. data/Gemfile +7 -3
  4. data/Rakefile +6 -1
  5. data/bin/console +1 -0
  6. data/exe/libvirt +1 -0
  7. data/lib/libvirt/base_info.rb +34 -0
  8. data/lib/libvirt/connection.rb +98 -36
  9. data/lib/libvirt/domain.rb +105 -8
  10. data/lib/libvirt/domain_callback_storage.rb +13 -15
  11. data/lib/libvirt/errors.rb +65 -0
  12. data/lib/libvirt/event.rb +29 -19
  13. data/lib/libvirt/ffi/common.rb +8 -1
  14. data/lib/libvirt/ffi/domain.rb +529 -196
  15. data/lib/libvirt/ffi/error.rb +243 -0
  16. data/lib/libvirt/ffi/event.rb +30 -36
  17. data/lib/libvirt/ffi/helpers.rb +17 -0
  18. data/lib/libvirt/ffi/host.rb +122 -0
  19. data/lib/libvirt/ffi/storage.rb +149 -0
  20. data/lib/libvirt/ffi/stream.rb +19 -17
  21. data/lib/libvirt/ffi.rb +17 -0
  22. data/lib/libvirt/node_info.rb +2 -41
  23. data/lib/libvirt/storage_pool.rb +70 -0
  24. data/lib/libvirt/storage_pool_info.rb +7 -0
  25. data/lib/libvirt/storage_volume.rb +51 -0
  26. data/lib/libvirt/storage_volume_info.rb +7 -0
  27. data/lib/libvirt/stream.rb +21 -14
  28. data/lib/libvirt/util.rb +61 -8
  29. data/lib/libvirt/version.rb +1 -1
  30. data/lib/libvirt/xml/disk.rb +59 -0
  31. data/lib/libvirt/xml/domain.rb +76 -0
  32. data/lib/libvirt/xml/generic.rb +252 -0
  33. data/lib/libvirt/xml/graphics.rb +14 -0
  34. data/lib/libvirt/xml/max_vcpu.rb +12 -0
  35. data/lib/libvirt/xml/memory.rb +14 -0
  36. data/lib/libvirt/xml/storage_pool.rb +24 -0
  37. data/lib/libvirt/xml/storage_volume.rb +32 -0
  38. data/lib/libvirt/xml/vcpu.rb +12 -0
  39. data/lib/libvirt/xml.rb +23 -0
  40. data/lib/libvirt.rb +12 -12
  41. data/lib/libvirt_ffi.rb +2 -0
  42. data/libvirt.gemspec +5 -1
  43. data/test_usage/support/libvirt_async.rb +27 -35
  44. data/test_usage/support/log_formatter.rb +5 -10
  45. data/test_usage/test_domain.rb +43 -0
  46. data/test_usage/test_event_loop.rb +115 -39
  47. data/test_usage/test_libvirtd_restart.rb +63 -0
  48. data/test_usage/test_metadata.rb +104 -0
  49. data/test_usage/test_screenshot.rb +14 -13
  50. data/test_usage/test_storage.rb +52 -0
  51. metadata +42 -6
  52. data/lib/libvirt/error.rb +0 -6
  53. data/lib/libvirt/ffi/connection.rb +0 -94
  54. data/lib/libvirt/ffi/libvirt.rb +0 -17
  55. data/lib/libvirt/ffi/node_info.rb +0 -37
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class Generic
6
+ class_attribute :_root_path, instance_writer: false, default: '.'
7
+ class_attribute :_attributes_opts, instance_writer: false, default: {}
8
+
9
+ def self.inherited(subclass)
10
+ subclass._root_path = '.'
11
+ subclass._attributes_opts = _attributes_opts.dup
12
+ end
13
+
14
+ def self.root_path(path)
15
+ self._root_path = path
16
+ end
17
+
18
+ def self.attributes(*names)
19
+ options = names.extract_options!
20
+ names.each do |name|
21
+ _attributes_opts.merge!(name.to_sym => options.dup)
22
+ end
23
+ attr_accessor(*names)
24
+ end
25
+
26
+ def self.attribute(name, options = {})
27
+ _attributes_opts.merge!(name.to_sym => options.dup)
28
+ attr_accessor name
29
+ end
30
+
31
+ # @param xml [String]
32
+ # @return [Class<LibvirtXml::Generic>]
33
+ def self.load(xml)
34
+ xml_node = Nokogiri::XML(xml).xpath(_root_path).first
35
+ new(xml_node)
36
+ end
37
+
38
+ # Build xml object with attributes.
39
+ # @param attrs [Hash]
40
+ # @return [Xml::Base]
41
+ def self.build(attrs = {})
42
+ xml_node = Nokogiri::XML(nil)
43
+ obj = new(xml_node)
44
+ attrs.each { |key, val| obj.public_send("#{key}=", val) }
45
+ obj
46
+ end
47
+
48
+ # @param xml_node [Nokogiri::XML::Element]
49
+ def initialize(xml_node)
50
+ @xml_node = xml_node
51
+ parse_xml_node
52
+ end
53
+
54
+ # @param attr [Symbol,String]
55
+ # @return [Object,nil]
56
+ def [](attr)
57
+ read_attribute(attr)
58
+ end
59
+
60
+ # @param attr [Symbol,String]
61
+ # @param value [Object,nil]
62
+ # @return [Object,nil]
63
+ def []=(attr, value)
64
+ write_attribute(attr, value)
65
+ end
66
+
67
+ # @return [Hash{Symbol=>(Object,nil)}]
68
+ def to_h
69
+ _attributes_opts.map do |name, _opts|
70
+ value = public_send(name)
71
+ [name, serialize_for_hash(value)]
72
+ end.to_h
73
+ end
74
+
75
+ # @return [String]
76
+ def to_xml
77
+ @xml_node.to_xml
78
+ end
79
+
80
+ delegate :as_json, :to_json, to: :to_h
81
+
82
+ private
83
+
84
+ def parse_xml_node
85
+ _attributes_opts.each do |name, opts|
86
+ value = parse_node(name, opts)
87
+ value = decode(value, opts)
88
+ write_attribute name, value
89
+ end
90
+ end
91
+
92
+ # Parse node value using "parse_node_#{type}" method.
93
+ # @param name [Symbol]
94
+ # @param opts [Hash{Symbol=>Object}]
95
+ # @return [Object, nil]
96
+ def parse_node(name, opts)
97
+ type = opts[:type] || :text
98
+ meth = "parse_node_#{type}"
99
+
100
+ if opts[:apply]
101
+ opts[:apply].call(@xml_node, opts)
102
+ elsif respond_to?(meth, true)
103
+ send(meth, name, opts)
104
+ else
105
+ raise ArgumentError, "Invalid :type option #{type.inspect} for attribute #{name}"
106
+ end
107
+ end
108
+
109
+ # Cast value using "decode_#{type}" method.
110
+ # @param value [String]
111
+ # @param opts [Hash{Symbol=>Object}]
112
+ # @return [Object, nil]
113
+ def decode(value, opts)
114
+ return if value.nil?
115
+
116
+ cast = opts[:cast]
117
+ return value if cast.nil?
118
+
119
+ meth = "decode_#{cast}"
120
+
121
+ if opts[:array]
122
+ value.map do |val|
123
+ if cast.is_a?(Proc)
124
+ cast.call(val, opts)
125
+ elsif respond_to?(meth, true)
126
+ send(meth, val, opts)
127
+ else
128
+ raise ArgumentError, "invalid :cast option #{cast.inspect}"
129
+ end
130
+ end
131
+ end
132
+
133
+ if cast.is_a?(Proc)
134
+ cast.call(value, opts)
135
+ elsif respond_to?(meth, true)
136
+ send(meth, value, opts)
137
+ else
138
+ raise ArgumentError, "invalid :cast option #{cast.inspect}"
139
+ end
140
+ end
141
+
142
+ # @param value [String, Boolean]
143
+ # @return [Boolean]
144
+ def decode_bool(value, _opts)
145
+ return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
146
+
147
+ return true if value == 'yes'
148
+
149
+ return false if value == 'no'
150
+
151
+ nil
152
+ end
153
+
154
+ # @param value [String, Integer]
155
+ # @return [Integer]
156
+ # @raise [ArgumentError]
157
+ def decode_int(value, _opts)
158
+ Integer(value)
159
+ end
160
+
161
+ def find_nodes(name, opts)
162
+ value_name = opts[:name]&.to_sym || name
163
+ path = opts[:path] || "./#{value_name}"
164
+ path == :root ? [@xml_node] : @xml_node.xpath(path)
165
+ end
166
+
167
+ def parse_node_text(name, opts)
168
+ nodes = find_nodes(name, opts)
169
+
170
+ nodes.map(&:text) if opts[:array]
171
+
172
+ node = nodes.first
173
+ return if node.nil?
174
+
175
+ node.text
176
+ end
177
+
178
+ def parse_node_attr(name, opts)
179
+ nodes = find_nodes name, { path: :root }.merge(opts)
180
+ value_name = opts[:name]&.to_sym || name
181
+
182
+ nodes.map { |node| node[value_name.to_s] } if opts[:array]
183
+
184
+ node = nodes.first
185
+ return if node.nil?
186
+
187
+ node[value_name.to_s]
188
+ end
189
+
190
+ def parse_node_struct(name, opts)
191
+ klass = opts[:class]
192
+ raise ArgumentError, "Invalid :class option nil for attribute #{name}" if klass.nil?
193
+
194
+ nodes = find_nodes(name, opts)
195
+
196
+ nodes.map { |node| klass.new(node) } if opts[:array]
197
+
198
+ node = nodes.first
199
+ return if node.nil?
200
+
201
+ klass.new(node)
202
+ end
203
+
204
+ def parse_node_raw(name, opts)
205
+ nodes = find_nodes(name, opts)
206
+
207
+ nodes.map(&:to_xml) if opts[:array]
208
+
209
+ node = nodes.first
210
+ return if node.nil?
211
+
212
+ node.to_xml
213
+ end
214
+
215
+ def parse_node_memory(name, opts)
216
+ nodes = find_nodes(name, opts)
217
+
218
+ if opts[:array]
219
+ return [] if nodes.empty?
220
+
221
+ nodes.map { |node| Util.parse_memory node.text, node['unit'] }
222
+ end
223
+
224
+ node = nodes.first
225
+ return if node.nil?
226
+
227
+ Util.parse_memory node.text, node['unit']
228
+ end
229
+
230
+ def read_attribute(attr)
231
+ attr = attr.to_sym
232
+ raise ArgumentError, "can't find attribute #{attr}" unless _attributes_opts.key?(attr)
233
+
234
+ instance_variable_get :"@#{attr}"
235
+ end
236
+
237
+ def write_attribute(attr, value)
238
+ attr = attr.to_sym
239
+ raise ArgumentError, "can't find attribute #{attr}" unless _attributes_opts.key?(attr)
240
+
241
+ instance_variable_set :"@#{attr}", value
242
+ end
243
+
244
+ def serialize_for_hash(value)
245
+ return value.to_h if value.is_a?(Generic)
246
+ return value.map { |val| serialize_for_hash(val) } if value.is_a?(Array)
247
+
248
+ value
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class Graphics < Generic
6
+ # https://libvirt.org/formatdomain.html#elementsGraphics
7
+
8
+ attribute :type, type: :attr
9
+ attribute :listen, type: :attr
10
+ attribute :port, type: :attr
11
+ attribute :auto_port, type: :attr, name: :autoport, cast: :bool
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class MaxVcpu < Generic
6
+ attribute :value, path: :root, cast: :int
7
+ attribute :cpu_set, type: :attr, name: :cpuset
8
+ attribute :current, type: :attr, cast: :int
9
+ attribute :placement, type: :attr
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class Memory < Generic
6
+ attribute :dump_core, type: :attr, name: :dumpCore
7
+ attribute :slots, type: :attr
8
+
9
+ attribute :bytes, apply: ->(node, _opts) do
10
+ Util.parse_memory node.text, node['unit']
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class StoragePool < Generic
6
+ # https://libvirt.org/formatstorage.html
7
+
8
+ root_path './pool'
9
+
10
+ attribute :type, type: :attr
11
+ attribute :name
12
+ attribute :uuid
13
+ attribute :capacity, type: :memory
14
+ attribute :allocation, type: :memory
15
+ attribute :available, type: :memory
16
+ attribute :target_path, path: './target/path'
17
+ attribute :target_perm_mode, path: './target/permissions/mode'
18
+ attribute :target_perm_owner, path: './target/permissions/owner'
19
+ attribute :target_perm_group, path: './target/permissions/group'
20
+ attribute :target_perm_label, path: './target/permissions/label'
21
+ # TODO: source
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class StorageVolume < Generic
6
+ # https://libvirt.org/formatstorage.html
7
+
8
+ root_path './volume'
9
+
10
+ attribute :type, type: :attr
11
+ attribute :name
12
+ attribute :key
13
+ attribute :allocation, type: :memory
14
+ attribute :capacity, type: :memory
15
+ attribute :physical, type: :memory
16
+ attribute :target_path, path: './target/path'
17
+ attribute :target_format, type: :attr, name: :type, path: './target/format'
18
+ attribute :target_perm_mode, path: './target/permissions/mode'
19
+ attribute :target_perm_owner, path: './target/permissions/owner'
20
+ attribute :target_perm_group, path: './target/permissions/group'
21
+ attribute :target_perm_label, path: './target/permissions/label'
22
+ attribute :timestamp_atime, path: './timestamp/atime'
23
+ attribute :timestamp_btime, path: './timestamp/btime'
24
+ attribute :timestamp_ctime, path: './timestamp/ctime'
25
+ attribute :timestamp_mtime, path: './timestamp/mtime'
26
+ attribute :compat
27
+ # TODO: target/encryption target/nocow target/features
28
+ # todo source
29
+ # todo backingStore
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class Vcpu < Generic
6
+ attribute :id, type: :attr, cast: :int
7
+ attribute :enabled, type: :attr, cast: :bool
8
+ attribute :hot_pluggable, type: :attr, cast: :bool, name: :hotpluggable
9
+ attribute :order, type: :attr, cast: :int
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'libvirt/util'
5
+
6
+ module Libvirt
7
+ module Xml
8
+ # https://libvirt.org/format.html
9
+ # namespace for libvirt xml objects.
10
+ # does not loaded by default.
11
+ # requires nokogiri.
12
+
13
+ require 'libvirt/xml/generic'
14
+ require 'libvirt/xml/storage_pool'
15
+ require 'libvirt/xml/storage_volume'
16
+ require 'libvirt/xml/memory'
17
+ require 'libvirt/xml/graphics'
18
+ require 'libvirt/xml/disk'
19
+ require 'libvirt/xml/max_vcpu'
20
+ require 'libvirt/xml/vcpu'
21
+ require 'libvirt/xml/domain'
22
+ end
23
+ end
data/lib/libvirt.rb CHANGED
@@ -4,19 +4,18 @@ require 'ffi'
4
4
  require 'objspace'
5
5
  require 'libvirt/domain_callback_storage'
6
6
  require 'libvirt/util'
7
- require 'libvirt/error'
8
- require 'libvirt/ffi/common'
9
- require 'libvirt/ffi/libvirt'
10
- require 'libvirt/ffi/connection'
11
- require 'libvirt/ffi/domain'
12
- require 'libvirt/ffi/event'
13
- require 'libvirt/ffi/node_info'
14
- require 'libvirt/ffi/stream'
7
+ require 'libvirt/errors'
8
+ require 'libvirt/ffi'
9
+ require 'libvirt/base_info'
10
+ require 'libvirt/node_info'
11
+ require 'libvirt/storage_pool_info'
12
+ require 'libvirt/storage_volume_info'
15
13
  require 'libvirt/event'
16
14
  require 'libvirt/connection'
17
15
  require 'libvirt/domain'
18
- require 'libvirt/node_info'
19
16
  require 'libvirt/stream'
17
+ require 'libvirt/storage_pool'
18
+ require 'libvirt/storage_volume'
20
19
  require 'libvirt/version'
21
20
 
22
21
  module Libvirt
@@ -28,10 +27,11 @@ module Libvirt
28
27
  class << self
29
28
  def lib_version
30
29
  version_ptr = ::FFI::MemoryPointer.new(:ulong)
31
- code = FFI::Libvirt.virGetVersion(version_ptr, nil, nil)
32
- raise Error, 'failed to get version' if code < 0
30
+ code = FFI::Host.virGetVersion(version_ptr, nil, nil)
31
+ raise Errors::LibError, 'failed to get version' if code.negative?
32
+
33
33
  version_number = version_ptr.get_ulong(0)
34
- Libvirt::Util.parse_version(version_number)
34
+ Util.parse_version(version_number)
35
35
  end
36
36
 
37
37
  def logger
data/lib/libvirt_ffi.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'libvirt'
data/libvirt.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'lib/libvirt/version'
2
4
 
3
5
  Gem::Specification.new do |spec|
@@ -17,7 +19,7 @@ Gem::Specification.new do |spec|
17
19
 
18
20
  # Specify which files should be added to the gem when it is released.
19
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
23
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
24
  end
23
25
  spec.bindir = 'exe'
@@ -25,4 +27,6 @@ Gem::Specification.new do |spec|
25
27
  spec.require_paths = ['lib']
26
28
 
27
29
  spec.add_dependency 'ffi', '>= 1.0'
30
+
31
+ spec.add_development_dependency 'rubocop', '~> 0.80.1'
28
32
  end
@@ -1,12 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module LibvirtAsync
2
4
  class << self
3
- def logger=(logger)
4
- @logger = logger
5
- end
5
+ attr_writer :logger
6
6
 
7
- def logger
8
- @logger
9
- end
7
+ attr_reader :logger
10
8
  end
11
9
 
12
10
  module WithDbg
@@ -26,12 +24,11 @@ module LibvirtAsync
26
24
  end
27
25
 
28
26
  module Util
29
- def create_task(parent = nil, reactor = nil, &block)
27
+ def self.create_task(parent = nil, reactor = nil, &block)
30
28
  parent = Async::Task.current? if parent == :current
31
29
  reactor ||= Async::Task.current.reactor
32
30
  Async::Task.new(reactor, parent, &block)
33
31
  end
34
- module_function :create_task
35
32
  end
36
33
 
37
34
  class Handle
@@ -77,12 +74,8 @@ module LibvirtAsync
77
74
  def register
78
75
  dbg { "#{self.class}#register handle_id=#{handle_id}, fd=#{fd}" }
79
76
 
80
- if (events & Libvirt::EVENT_HANDLE_ERROR) != 0
81
- dbg { "#{self.class}#register skip EVENT_HANDLE_ERROR handle_id=#{handle_id}, fd=#{fd}" }
82
- end
83
- if (events & Libvirt::EVENT_HANDLE_HANGUP) != 0
84
- dbg { "#{self.class}#register skip EVENT_HANDLE_HANGUP handle_id=#{handle_id}, fd=#{fd}" }
85
- end
77
+ dbg { "#{self.class}#register skip EVENT_HANDLE_ERROR handle_id=#{handle_id}, fd=#{fd}" } if (events & Libvirt::EVENT_HANDLE_ERROR) != 0
78
+ dbg { "#{self.class}#register skip EVENT_HANDLE_HANGUP handle_id=#{handle_id}, fd=#{fd}" } if (events & Libvirt::EVENT_HANDLE_HANGUP) != 0
86
79
 
87
80
  interest = events_to_interest(events)
88
81
  dbg { "#{self.class}#register parse handle_id=#{handle_id}, fd=#{fd}, events=#{events}, interest=#{interest}" }
@@ -99,7 +92,7 @@ module LibvirtAsync
99
92
  io = IO.new(fd, io_mode, autoclose: false)
100
93
  @monitor = Monitor.new(io)
101
94
 
102
- while @monitor.readiness == nil
95
+ while @monitor.readiness.nil?
103
96
  cancelled = wait_io(interest)
104
97
 
105
98
  if cancelled
@@ -117,7 +110,6 @@ module LibvirtAsync
117
110
 
118
111
  dbg { "#{self.class}#register_handle async not ready readiness=#{@monitor.readiness}, handle_id=#{handle_id}, fd=#{fd}" }
119
112
  end
120
-
121
113
  end
122
114
 
123
115
  dbg { "#{self.class}#register_handle invokes fiber=0x#{task.fiber.object_id.to_s(16)} handle_id=#{handle_id}, fd=#{fd}" }
@@ -197,8 +189,6 @@ module LibvirtAsync
197
189
  :r
198
190
  elsif writable
199
191
  :w
200
- else
201
- nil
202
192
  end
203
193
  end
204
194
 
@@ -223,8 +213,6 @@ module LibvirtAsync
223
213
  Libvirt::EVENT_HANDLE_READABLE
224
214
  when :w
225
215
  Libvirt::EVENT_HANDLE_WRITABLE
226
- else
227
- nil
228
216
  end
229
217
  end
230
218
  end
@@ -277,14 +265,15 @@ module LibvirtAsync
277
265
  dbg { "#{self.class}#initialize timer_id=#{timer_id}, interval=#{interval}" }
278
266
 
279
267
  @timer_id = timer_id
280
- @interval = interval.to_f / 1000.to_f
268
+ @interval = interval / 1000.0
281
269
  @opaque = opaque
282
270
  @last_fired = Time.now.to_f
283
271
  @monitor = nil
284
272
  end
285
273
 
286
274
  def wait_time
287
- return if interval < 0
275
+ return if interval.negative?
276
+
288
277
  last_fired + interval
289
278
  end
290
279
 
@@ -358,15 +347,12 @@ module LibvirtAsync
358
347
  end
359
348
 
360
349
  def wait_timer(timeout)
361
- begin
362
- @monitor.wait(timeout)
363
- false
364
- rescue Monitor::Cancelled => e
365
- dbg { "#{self.class}#wait_timer cancelled #{e.class} #{e.message}" }
366
- true
367
- end
350
+ @monitor.wait(timeout)
351
+ false
352
+ rescue Monitor::Cancelled => e
353
+ dbg { "#{self.class}#wait_timer cancelled #{e.class} #{e.message}" }
354
+ true
368
355
  end
369
-
370
356
  end
371
357
 
372
358
  class Implementations
@@ -396,12 +382,12 @@ module LibvirtAsync
396
382
  def print_debug_info
397
383
  str = [
398
384
  "#{self.class}:0x#{object_id.to_s(16)}",
399
- "handles = [",
385
+ 'handles = [',
400
386
  @handles.map(&:to_s).join("\n"),
401
- "]",
402
- "timers = [",
387
+ ']',
388
+ 'timers = [',
403
389
  @timers.map(&:to_s).join("\n"),
404
- "]"
390
+ ']'
405
391
  ].join("\n")
406
392
  Libvirt.logger&.debug { str }
407
393
  end
@@ -432,10 +418,16 @@ module LibvirtAsync
432
418
  remove_handle: method(:remove_handle).to_proc,
433
419
  add_timer: method(:add_timer).to_proc,
434
420
  update_timer: method(:update_timer).to_proc,
435
- remove_timer: method(:remove_timer).to_proc
421
+ remove_timer: method(:remove_timer).to_proc,
422
+ schedule: method(:schedule).to_proc
436
423
  )
437
424
  end
438
425
 
426
+ def schedule(&block)
427
+ task = Async::Task.new(Async::Task.current.reactor, nil, &block)
428
+ task.reactor << task.fiber
429
+ end
430
+
439
431
  def add_handle(fd, events, opaque)
440
432
  # add a handle to be tracked by this object. The application is
441
433
  # expected to maintain a list of internal handle IDs (integers); this
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class LogFormatter
2
- LOG_FORMAT = "%s, %s [%s/%s/%s] %s\n".freeze
3
- DEFAULT_DATETIME_FORMAT = "%F %T.%N".freeze
4
+ LOG_FORMAT = "%s, %s [%s/%s/%s] %s\n"
5
+ DEFAULT_DATETIME_FORMAT = '%F %T.%N'
4
6
 
5
7
  attr_accessor :datetime_format
6
8
 
@@ -9,14 +11,7 @@ class LogFormatter
9
11
  end
10
12
 
11
13
  def call(severity, time, progname, message)
12
- LOG_FORMAT % [
13
- severity[0..0],
14
- format_datetime(time),
15
- "0x#{Async::Task.current?&.object_id&.to_s(16)}",
16
- "0x#{Fiber.current.object_id.to_s(16)}",
17
- progname,
18
- format_message(message)
19
- ]
14
+ format(LOG_FORMAT, severity[0..0], format_datetime(time), "0x#{Async::Task.current?&.object_id&.to_s(16)}", "0x#{Fiber.current.object_id.to_s(16)}", progname, format_message(message))
20
15
  end
21
16
 
22
17
  private
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'libvirt'
6
+ require 'logger'
7
+ require 'active_support/all'
8
+ require 'async'
9
+
10
+ require_relative 'support/libvirt_async'
11
+ require_relative 'support/log_formatter'
12
+
13
+ require 'libvirt/xml'
14
+
15
+ Libvirt.logger = Logger.new(STDOUT, formatter: LogFormatter.new)
16
+ Libvirt.logger.level = ENV['DEBUG'] ? :debug : :info
17
+
18
+ IMPL = LibvirtAsync::Implementations.new
19
+
20
+ Async do
21
+ ASYNC_REACTOR = Async::Task.current.reactor
22
+
23
+ puts "Lib version #{Libvirt.lib_version}"
24
+ puts "Gem version #{Libvirt::VERSION}"
25
+
26
+ IMPL.start
27
+
28
+ conn = Libvirt::Connection.new('qemu+tcp://localhost:16510/system')
29
+ conn.open
30
+
31
+ puts "Connection version #{conn.version.inspect}"
32
+ puts "Connection lib_version #{conn.lib_version.inspect}"
33
+ puts "Connection hostname #{conn.hostname.inspect}"
34
+
35
+ doms = conn.list_all_domains
36
+ puts "Connection domains qty #{doms.size}"
37
+
38
+ doms.each.with_index do |dom, i|
39
+ puts "Domain #{i} xml", dom.xml_desc
40
+ puts "Domain #{i} xml object", Libvirt::Xml::Domain.load(dom.xml_desc).to_h
41
+ end
42
+
43
+ end