libvirt_ffi 0.2.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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