libvirt_ffi 0.2.1 → 0.5.1

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 -2
  4. data/Rakefile +6 -1
  5. data/bin/console +1 -0
  6. data/exe/libvirt +1 -0
  7. data/lib/libvirt.rb +14 -13
  8. data/lib/libvirt/base_info.rb +34 -0
  9. data/lib/libvirt/connection.rb +156 -47
  10. data/lib/libvirt/domain.rb +136 -8
  11. data/lib/libvirt/domain_callback_storage.rb +69 -0
  12. data/lib/libvirt/errors.rb +65 -0
  13. data/lib/libvirt/event.rb +60 -38
  14. data/lib/libvirt/ffi.rb +17 -0
  15. data/lib/libvirt/ffi/common.rb +8 -1
  16. data/lib/libvirt/ffi/domain.rb +796 -69
  17. data/lib/libvirt/ffi/error.rb +243 -0
  18. data/lib/libvirt/ffi/event.rb +30 -36
  19. data/lib/libvirt/ffi/helpers.rb +17 -0
  20. data/lib/libvirt/ffi/host.rb +122 -0
  21. data/lib/libvirt/ffi/storage.rb +149 -0
  22. data/lib/libvirt/ffi/stream.rb +74 -0
  23. data/lib/libvirt/node_info.rb +2 -41
  24. data/lib/libvirt/storage_pool.rb +70 -0
  25. data/lib/libvirt/storage_pool_info.rb +7 -0
  26. data/lib/libvirt/storage_volume.rb +51 -0
  27. data/lib/libvirt/storage_volume_info.rb +7 -0
  28. data/lib/libvirt/stream.rb +124 -0
  29. data/lib/libvirt/util.rb +75 -8
  30. data/lib/libvirt/version.rb +1 -1
  31. data/lib/libvirt/xml.rb +23 -0
  32. data/lib/libvirt/xml/disk.rb +59 -0
  33. data/lib/libvirt/xml/domain.rb +76 -0
  34. data/lib/libvirt/xml/generic.rb +252 -0
  35. data/lib/libvirt/xml/graphics.rb +14 -0
  36. data/lib/libvirt/xml/max_vcpu.rb +12 -0
  37. data/lib/libvirt/xml/memory.rb +14 -0
  38. data/lib/libvirt/xml/storage_pool.rb +24 -0
  39. data/lib/libvirt/xml/storage_volume.rb +32 -0
  40. data/lib/libvirt/xml/vcpu.rb +12 -0
  41. data/lib/libvirt_ffi.rb +2 -0
  42. data/libvirt.gemspec +5 -1
  43. data/test_usage/support/libvirt_async.rb +33 -31
  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 +134 -33
  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 +197 -0
  50. data/test_usage/test_storage.rb +52 -0
  51. metadata +46 -6
  52. data/lib/libvirt/error.rb +0 -6
  53. data/lib/libvirt/ffi/connection.rb +0 -84
  54. data/lib/libvirt/ffi/libvirt.rb +0 -17
  55. data/lib/libvirt/ffi/node_info.rb +0 -37
@@ -2,23 +2,36 @@
2
2
 
3
3
  module Libvirt
4
4
  module Util
5
+ UNIT_TO_BYTES = {
6
+ b: 1,
7
+ bytes: 1,
8
+ KB: 1_000,
9
+ KiB: 1_024,
10
+ k: 1_024,
11
+ MB: 1_000_000,
12
+ M: 1_048_576,
13
+ MiB: 1_048_576,
14
+ GB: 1_000_000_000,
15
+ G: 1_073_741_824,
16
+ GiB: 1_073_741_824,
17
+ TB: 1_000_000_000_000,
18
+ T: 1_099_511_627_776,
19
+ TiB: 1_099_511_627_776
20
+ }.freeze
21
+
5
22
  class << self
23
+ attr_writer :logger
6
24
 
7
- def logger=(logger)
8
- @logger = logger
9
- end
10
-
11
- def logger
12
- @logger
13
- end
25
+ attr_reader :logger
14
26
 
15
27
  def log(severity, prog = nil, &block)
16
28
  return if @logger.nil?
29
+
17
30
  @logger.public_send(severity, prog, &block)
18
31
  end
19
32
 
20
33
  def library_path
21
- %w(libvirt libvirt.so.0)
34
+ %w[libvirt libvirt.so.0]
22
35
  end
23
36
 
24
37
  # @param [Integer] version_number ulong
@@ -29,6 +42,60 @@ module Libvirt
29
42
  "#{major}.#{minor}.#{release}"
30
43
  end
31
44
 
45
+ # @param enum [FFI::Enum]
46
+ # @param value [Symbol, Integer]
47
+ # @return [Array] event_id, event_id_sym
48
+ # @raise ArgumentError
49
+ def parse_enum(enum, value)
50
+ if value.is_a?(Symbol)
51
+ raise ArgumentError, 'invalid enum value' unless enum.symbols.include?(value)
52
+
53
+ return [enum.find(value), value]
54
+ end
55
+
56
+ raise ArgumentError, 'invalid enum value' unless enum.symbol_map.values.include?(value)
57
+
58
+ [value, enum.symbol_map[value]]
59
+ end
60
+
61
+ # Bitwise OR integer flags calculation for C language.
62
+ # @param flags [Integer,Symbol,Array<Symbol>,Hash{Symbol=>Boolean},nil]
63
+ # @param enum [FFI::Enum]
64
+ # @param default [Integer] optional (default 0x0)
65
+ # @return [Integer] bitwise OR of keys
66
+ # @example Usage:
67
+ # parse_flags(nil, enum)
68
+ # parse_flags({MANAGED_SAVE: true, SNAPSHOTS_METADATA: true, NVRAM: false}, enum)
69
+ # parse_flags({managed_save: true, snapshots_metadata: true, keep_nvram: nil}, enum)
70
+ # parse_flags(3, enum)
71
+ # parse_flags([:MANAGED_SAVE, :SNAPSHOTS_METADATA], enum)
72
+ # parse_flags([:managed_save, :snapshots_metadata], enum)
73
+ #
74
+ def parse_flags(flags, enum, default: 0x0)
75
+ flags = default if flags.nil?
76
+ flags = enum[flags] if flags.is_a?(Symbol)
77
+ return flags if flags.is_a?(Integer)
78
+
79
+ result = 0x0
80
+ flags = flags.select { |_, v| v }.keys if flags.is_a?(Hash)
81
+
82
+ raise ArgumentError, 'flags must be an Integer or a Hash or an Array' unless flags.is_a?(Array)
83
+
84
+ flags.each do |key|
85
+ result |= enum[key.to_s.upcase.to_sym]
86
+ end
87
+
88
+ result
89
+ end
90
+
91
+ # @param value [Integer,String]
92
+ # @param unit [String,Symbol] default 'bytes'
93
+ # @return [Integer] memory in bytes
94
+ def parse_memory(value, unit)
95
+ unit ||= 'bytes'
96
+ multiplier = UNIT_TO_BYTES.fetch(unit.to_sym)
97
+ Integer(value) * multiplier
98
+ end
32
99
  end
33
100
  end
34
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Libvirt
4
- VERSION = '0.2.1'
4
+ VERSION = '0.5.1'
5
5
  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
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class Disk < Generic
6
+ # https://libvirt.org/formatdomain.html#elementsDisks
7
+
8
+ attribute :type, type: :attr
9
+ attribute :device, type: :attr
10
+ attribute :model, type: :attr
11
+ attribute :raw_io, type: :attr, name: :rawio, cast: :bool
12
+ attribute :sg_io, type: :attr, name: :sgio
13
+ attribute :snapshot, type: :attr
14
+ attribute :source_file, name: :file, path: './source', type: :attr
15
+ attribute :source_dev, name: :dev, path: './source', type: :attr
16
+ attribute :source_dir, name: :dir, path: './source', type: :attr
17
+ attribute :source_protocol, name: :protocol, path: './source', type: :attr
18
+ attribute :source_pool, name: :pool, path: './source', type: :attr
19
+ attribute :source_volume, name: :volume, path: './source', type: :attr
20
+ attribute :source_mode, name: :mode, path: './source', type: :attr
21
+ attribute :source_name, name: :name, path: './source', type: :attr
22
+ attribute :source_type, name: :type, path: './source', type: :attr
23
+ attribute :source_managed, name: :managed, path: './source', type: :attr, cast: :bool
24
+ attribute :source_namespace, name: :namespace, path: './source', type: :attr
25
+ attribute :source_index, name: :index, path: './source', type: :attr
26
+ attribute :source_host_name, name: :name, path: './source/host', type: :attr
27
+ attribute :source_host_port, name: :port, path: './source/host', type: :attr
28
+ attribute :source_host_transport, name: :transport, path: './source/host', type: :attr
29
+ attribute :source_host_socket, name: :socket, path: './source/host', type: :attr
30
+ attribute :source_snapshot_name, name: :name, path: './source/snapshot', type: :attr
31
+ attribute :source_config_file, name: :file, path: './source/config', type: :attr
32
+ # TODO: source/auth
33
+ # TODO: source/encryption
34
+ # TODO: source/reservations
35
+ # TODO: source/initiator
36
+ # TODO: source/address
37
+ # TODO: source/slices
38
+ # TODO: backingStore
39
+ # TODO: mirror
40
+ # TODO: target
41
+ # TODO: iotune
42
+ # TODO: driver
43
+ # TODO: backenddomain
44
+ # TODO: boot
45
+ # TODO: encryption
46
+ # TODO: readonly
47
+ # TODO: shareable
48
+ # TODO: transient
49
+ # TODO: serial
50
+ # TODO: wwn
51
+ # TODO: vendor
52
+ # TODO: product
53
+ # TODO: address
54
+ # TODO: auth
55
+ # TODO: geometry
56
+ # TODO: blockio
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libvirt
4
+ module Xml
5
+ class Domain < Generic
6
+ # https://libvirt.org/formatdomain.html
7
+
8
+ root_path './domain'
9
+
10
+ attribute :name
11
+ attribute :uuid
12
+ attribute :gen_id, path: './genid'
13
+ attribute :title
14
+ attribute :description
15
+ attribute :metadata, type: :raw
16
+ attribute :vcpu, type: :struct, class: MaxVcpu
17
+ attribute :vcpus, type: :struct, array: true, class: Vcpu, cast: ->(objects) { objects.sort_by(&:id) }
18
+ attribute :memory, type: :struct, class: Memory
19
+ attribute :current_memory, type: :struct, class: Memory
20
+ attribute :max_memory, type: :struct, class: Memory
21
+ attribute :resource_partitions, path: './resource/partition', array: true
22
+ attribute :on_power_off
23
+ attribute :on_reboot
24
+ attribute :on_crash
25
+ attribute :on_lock_failure
26
+ attribute :device_graphics, type: :struct, path: './devices/graphics', class: Graphics, array: true
27
+ attribute :device_disks, type: :struct, path: './devices/disk', class: Disk, array: true
28
+ # https://libvirt.org/formatdomain.html#elementsDevices
29
+ # todo devices/emulator
30
+ # todo devices/interface
31
+ # todo devices/filesystem
32
+ # todo devices/controller
33
+ # todo devices/lease
34
+ # todo devices/hostdev
35
+ # todo devices/redirdev
36
+ # todo devices/smartcard
37
+ # todo devices/input
38
+ # todo devices/hub
39
+ # todo devices/video
40
+ # todo devices/parallel
41
+ # todo devices/serial
42
+ # todo devices/console
43
+ # todo devices/channel
44
+ # todo devices/sound
45
+ # todo devices/watchdog
46
+ # todo devices/memballoon
47
+ # todo devices/rng
48
+ # todo devices/tpm
49
+ # todo devices/nvram
50
+ # todo devices/panic
51
+ # todo devices/shmem
52
+ # todo devices/memory
53
+ # todo devices/iommu
54
+ # todo devices/vsock
55
+ # todo os
56
+ # todo bootloader
57
+ # todo bootloader_args
58
+ # todo sysinfo
59
+ # todo iothreads
60
+ # todo iothreadids
61
+ # todo cputune
62
+ # todo memoryBacking
63
+ # todo memtune
64
+ # todo numatune
65
+ # todo blkiotune
66
+ # todo cpu
67
+ # todo pm
68
+ # todo features
69
+ # todo clock
70
+ # todo perf
71
+ # todo seclabel
72
+ # todo keywrap
73
+ # todo launchSecurity
74
+ end
75
+ end
76
+ end
@@ -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