Class: Corosync::CMAP

Inherits:
Object
  • Object
show all
Defined in:
lib/corosync/cmap.rb

Overview

CMAP is used to access the corosync configuration database for the local node. You can list keys, get/set/delete values, and watch for changes.

Many of the methods take or return a ‘type’. The type is a symbol for one of CMAP’s supported types. The symbols are:

  • :int8

  • :uint8

  • :int16

  • :uint16

  • :int32

  • :uint32

  • :int64

  • :uint64

  • :float

  • :double

  • :string


Examples:

require 'corosync/cmap'
cmap = Corosync::CMAP.new(true)
cmap.set('mykey.foo', :int32, -1234)
puts "mykey.foo is #{cmap.get('mykey.foo')}"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connect = true) ⇒ CMAP

Starts a new instance of CMAP. You can have as many instances as you like, but no real reason for more than one.

Parameters:

  • connect (Boolean) (defaults to: true)

    Whether to automatically call #connect



38
39
40
41
42
43
44
# File 'lib/corosync/cmap.rb', line 38

def initialize(connect = true)
	@handle = nil

	@track_handle_callbacks = {}

	self.connect if connect
end

Instance Attribute Details

#fdIO (readonly)

The IO object containing the file descriptor events and messages come across. You can use this to check for activity prior to calling #dispatch, but do not read anything from it.

Returns:

  • (IO)


32
33
34
# File 'lib/corosync/cmap.rb', line 32

def fd
  @fd
end

Instance Method Details

#connectvoid

This method returns an undefined value.

Connect to the CMAP service.



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/corosync/cmap.rb', line 49

def connect
	handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_handle_t))

	Corosync.cs_send(:cmap_initialize, handle_ptr)

	@handle = handle_ptr.read_uint64

	fd_ptr = FFI::MemoryPointer.new(:int)
	Corosync.cs_send(:cmap_fd_get, @handle, fd_ptr)
	@fd = IO.new(fd_ptr.read_int)
end

#dec(name) ⇒ void

This method returns an undefined value.

Decrement the specified key.

Parameters:

  • name (String)

    The name of the key



241
242
243
# File 'lib/corosync/cmap.rb', line 241

def dec(name)
	Corosync.cs_send(:cmap_dec, @handle, name)
end

#delete(name) ⇒ void

This method returns an undefined value.

Delete the specified key.

Parameters:

  • name (String)

    The name of the key



232
233
234
# File 'lib/corosync/cmap.rb', line 232

def delete(name)
	Corosync.cs_send(:cmap_delete, @handle, name)
end

#dispatch(timeout = -1)) ⇒ Boolean

Checks for a single pending event and triggers the appropriate callback if found.

Parameters:

  • timeout (Integer) (defaults to: -1))

    How long to wait for an event.

    • -1: Indefinite. Wait forever

    • 0: Non-blocking. If there isn’t a pending event, return immediately

    • >0: Wait the specified number of seconds.

Returns:

  • (Boolean)

    Returns True if an event was triggered. Otherwise False.



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/corosync/cmap.rb', line 366

def dispatch(timeout = -1)
	if !timeout != 0 then
		timeout = nil if timeout == -1
		select([@fd], [], [], timeout)
	end

	begin
		Corosync.cs_send!(:cmap_dispatch, @handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
	rescue Corosync::TryAgainError => e
		raise e if e.depth > 1 # this exception is from a nested corosync function, not our quorum_dispatch we just called
		return false
	end

	return true
end

#finalizevoid

This method returns an undefined value.

Disconnect from the CMAP service.



64
65
66
67
68
69
70
71
# File 'lib/corosync/cmap.rb', line 64

def finalize
	return if @handle.nil?

	Corosync.cs_send(:cmap_finalize, @handle)

	@handle = nil
	@fd = nil
end

#get(name) ⇒ Array<type, value>

Retrieve a key by the specified name. Will raise NotExistError if the key does not exist.

Returns:

  • (Array<type, value>)

    The type and value of the key



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/corosync/cmap.rb', line 77

def get(name)
	#TODO? make it just return nil if the key doesn't exist
	size_ptr = FFI::MemoryPointer.new(:size_t)
	type_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_value_types_t))

	size = 256

	begin
		size_ptr.write_type(:size_t, size)
		value_ptr = FFI::MemoryPointer.new(size)

		Corosync.cs_send(:cmap_get, @handle, name, value_ptr, size_ptr, type_ptr)
	rescue Corosync::InvalidParamError => e
		# err_invalid_param is supposed to indicate our buffer was too small
		value_ptr.free
		size << 1
		retry if size < 1024 ** 2 # 1 MB

		raise e
	end

	type = type_ptr.read_type(Corosync.find_type(:cmap_value_types_t))
	type = Corosync.enum_type(:cmap_value_types_t)[type]
	if type == :binary then
		raise RuntimeError, "Binary, not sure how to handle this. Corosync docs don't clearly indicate what it is"
	end

	[type, value_ptr.send("read_#{type}".downcase.to_sym)]
end

#get_value(name) ⇒ Number, String

Retrieve a key’s value. This is just a conveinence wrapper around #get to only get the value if you don’t want the type.

Parameters:

  • name (String)

    The name of the key to look up

Returns:

  • (Number, String)

    The value of the key



113
114
115
116
# File 'lib/corosync/cmap.rb', line 113

def get_value(name)
	type, value = get(name)
	value
end

#inc(name) ⇒ void

This method returns an undefined value.

Increment the specified key.

Parameters:

  • name (String)

    The name of the key



250
251
252
# File 'lib/corosync/cmap.rb', line 250

def inc(name)
	Corosync.cs_send(:cmap_inc, @handle, name)
end

#keys(prefix = nil) ⇒ Array<String>

Get a list of keys.

Parameters:

  • prefix (String) (defaults to: nil)

    Filter the list of keys to those starting with the specified prefix

Returns:

  • (Array<String>)

    List of matching key names



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/corosync/cmap.rb', line 259

def keys(prefix = nil)
	cmap_iteration_handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_iter_handle_t))
	Corosync.cs_send(:cmap_iter_init, @handle, prefix, cmap_iteration_handle_ptr)
	cmap_iteration_handle = cmap_iteration_handle_ptr.read_type(Corosync.find_type(:cmap_iter_handle_t))

	keys = []

	key_name_ptr = FFI::MemoryPointer.new(Corosync::CMAP_KEYNAME_MAXLEN)
	begin
		begin
			loop do
				Corosync.cs_send(:cmap_iter_next, @handle, cmap_iteration_handle, key_name_ptr, nil, nil)

				# we really don't need to get info on the value. it doesn't help us any
				#value_size = value_len_ptr.read_type(:size_t)
				#value_type = value_type_ptr.read_type(Corosync.find_type(:cmap_value_types_t))
				#value_type = Corosync.enum_type(:cmap_value_types_t)[value_type]
				#keys[key_name_ptr.read_string] = Corosync::CMAP::ValueInfo.new(value_size, value_type)

				keys << key_name_ptr.read_string
			end
		rescue Corosync::NoSectionsError
			# signals end of iteration
		end
	ensure
		Corosync.cs_send(:cmap_iter_finalize, @handle, cmap_iteration_handle)
	end

	keys
end

#set(name, type, value) ⇒ Number, String

Set a key to the specified type & value. This will create the key if it doesn’t exist, and will otherwise modify it, including changing the type if it doesn’t match.

Parameters:

  • name (String)

    The name of the key

  • type (Symbol)

    One of CMAP’s supported types

  • value (Number, String)

    The value to set

Returns:

  • (Number, String)

    The value as stored in the CMAP service. This will normally be the value passed in, but if you store a non-string as a string, the return will be the result of to_s



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/corosync/cmap.rb', line 126

def set(name, type, value)
	# get byte size
	if type == :string then
		size = value.bytesize
	elsif SIZEMAP.keys.include?(type) then
		size = SIZEMAP[type].bytes
	elsif type == :float then
		size = 4
	elsif type == :double then
		size = 8
	elsif type == :binary then
		size = value.bytesize
	end

	value_ptr = FFI::MemoryPointer.new(size)
	value_ptr.write_type(type, value)
	Corosync.cs_send(:cmap_set, @handle, name, value_ptr, size, type)

	value
end

#set_value(name, value) ⇒ Number, String

Set a key to the specified value. A convenience wrapper around #set to automatically determine the type. If the value is numeric, we will use the same type as the existing value (if it already exists). Otherwise we pick the smallest type that will hold the value.

Parameters:

  • name (String)

    The name of the key

  • value (Number, String)

    The value to set

Returns:

  • (Number, String)

    The value as stored in the CMAP service. This will normally be the value passed in, but if you store a non-string as a string, the return will be the result of to_s



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/corosync/cmap.rb', line 168

def set_value(name, value)
	type = catch :type do
		# strings are strings
		throw :type, :string if value.is_a?(String)

		# try and get existing type
		begin
			type_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_value_types_t))
			size_ptr = FFI::MemoryPointer.new(:size_t)
			Corosync.cs_send(:cmap_get, @handle, name, nil, size_ptr, type_ptr)
			type = type_ptr.read_type(Corosync.find_type(:cmap_value_types_t))
			type = Corosync.enum_type(:cmap_value_types_t)[type]
			if SIZEMAP.keys.include(type) then
				size = size_ptr.read_type(:size_t)
				if size <= SIZEMAP[type].bytes then
					# it fits within the existing type
					throw :type, type
				end
				# it doesnt fit, we need to re-type it
			end
		rescue Corosync::NotExistError
		end

		# find the type that will fit
		if value.is_a?(Float) then
			type = :double
		elsif value.is_a?(Numeric) then
			if value.abs <= 2 ** 7 and value < 0 then
				type = :int8
			elsif value <= 2 ** 8 and value >= 0 then
				type = :uint8
			elsif value.abs <= 2 ** 15 and value < 0 then
				type = :int16
			elsif value <= 2 ** 16 and value >= 0 then
				type = :uint16
			elsif value.abs <= 2 ** 31 and value < 0 then
				type = :int32
			elsif value <= 2 ** 32 and value >= 0 then
				type = :uint32
			elsif value.abs <= 2 ** 63 and value < 0 then
				type = :int64
			elsif value < 2 ** 64 and value >= 0 then
				type = :uint64
			else
				raise ArgumentError, "Corosync cannot handle numbers larger than 64-bit"
			end

			throw :type, type
		end

		# Unknown type, force it into a string
		throw :type, :string
	end

	value = value.to_s if type == :string and !value.is_a?(String)

	set(name, type, value)
end

#track_add(name, actions, prefix = false, &block) {|action, key, value_new_type, value_new_data, value_old_type, value_old_data| ... } ⇒ Object

Watch keys for changes. Calls a callback when the watched key(s) are changed.

Parameters:

  • name (String)

    The specified key (or prefix)

  • actions (Array<Symbol>)

    The operations to watch for

    • :add - The key is added

    • :delete - The key is deleted

    • :modify - The value/type is changed

  • prefix (Boolean) (defaults to: false)

    Whether to use the name as a prefix and watch all keys under it

  • block (Proc)

    The callback to call when an event is triggered.

Yield Parameters:

  • action (Symbol)

    The action that triggered the callback (:add, :delete, :modify)

  • key (String)

    The name of the key which changed

  • value_new_type (Symbol)

    The type of the new value. nil if just deleted

  • value_new_data (Number, String)

    The new value. nil if just deleted

  • value_old_type (Symbol)

    The type of the old value. nil if just created

  • value_old_data (Number, String)

    The old value. nil if just created

Returns:

  • (Object)

    The handle used to identify the track session. Pass to #track_delete to stop tracking.



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/corosync/cmap.rb', line 309

def track_add(name, actions, prefix = false, &block)
	cs_track_type = 0
	cs_track_type |= Corosync::CMAP_TRACK_ADD if actions.include?(:add)
	cs_track_type |= Corosync::CMAP_TRACK_DELETE if actions.include?(:delete)
	cs_track_type |= Corosync::CMAP_TRACK_MODIFY if actions.include?(:modify)

	cs_track_type |= Corosync::CMAP_TRACK_PREFIX if prefix

	track_handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_track_handle_t))

	@track_notify_method ||= self.method(:track_notify) # we have to keep it from being garbage collected
	Corosync.cs_send(:cmap_track_add, @handle, name, cs_track_type, @track_notify_method, nil, track_handle_ptr)

	track_handle = track_handle_ptr.read_type(Corosync.find_type(:cmap_track_handle_t))
	@track_handle_callbacks[track_handle] = block

	track_handle
end

#track_delete(track_handle) ⇒ void

This method returns an undefined value.

Stop watching for changes.

Parameters:

  • track_handle (Number)

    The handle returned by #track_add



332
333
334
335
# File 'lib/corosync/cmap.rb', line 332

def track_delete(track_handle)
	@track_handle_callbacks.delete(track_handle)
	Corosync.cs_send(:cmap_track_delete, @handle, track_handle)
end