Skip to main content

rust_corosync/
votequorum.rs

1// libvotequorum interface for Rust
2// Copyright (c) 2021 Red Hat, Inc.
3//
4// All rights reserved.
5//
6// Author: Christine Caulfield (ccaulfi@redhat.com)
7//
8
9#![allow(clippy::type_complexity)]
10#![allow(clippy::needless_range_loop)]
11#![allow(clippy::single_match)]
12
13// For the code generated by bindgen
14use crate::sys::votequorum as ffi;
15
16use std::collections::HashMap;
17use std::ffi::CString;
18use std::fmt;
19use std::os::raw::{c_int, c_void};
20use std::slice;
21use std::sync::Mutex;
22
23use crate::string_from_bytes;
24use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags};
25
26/// RingId returned by votequorum_notification_fn
27pub struct RingId {
28    pub nodeid: NodeId,
29    pub seq: u64,
30}
31
32// Used to convert a VOTEQUORUM handle into one of ours
33lazy_static! {
34    static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
35}
36
37/// Current state of a node in the cluster, part of the [NodeInfo] and [Node] structs
38pub enum NodeState {
39    Member,
40    Dead,
41    Leaving,
42    Unknown,
43}
44impl NodeState {
45    pub fn new(state: u32) -> NodeState {
46        match state {
47            1 => NodeState::Member,
48            2 => NodeState::Dead,
49            3 => NodeState::Leaving,
50            _ => NodeState::Unknown,
51        }
52    }
53}
54impl fmt::Debug for NodeState {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56        match self {
57            NodeState::Member => write!(f, "Member"),
58            NodeState::Dead => write!(f, "Dead"),
59            NodeState::Leaving => write!(f, "Leaving"),
60            _ => write!(f, "Unknown"),
61        }
62    }
63}
64
65/// Basic information about a node in the cluster. Contains [NodeId], and [NodeState]
66pub struct Node {
67    nodeid: NodeId,
68    state: NodeState,
69}
70impl fmt::Debug for Node {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        write!(f, "nodeid: {}, state: {:?}", self.nodeid, self.state)
73    }
74}
75
76bitflags! {
77/// Flags in the [NodeInfo] struct
78    pub struct NodeInfoFlags: u32
79    {
80    const VOTEQUORUM_INFO_TWONODE             = 1;
81    const VOTEQUORUM_INFO_QUORATE             = 2;
82    const VOTEQUORUM_INFO_WAIT_FOR_ALL        = 4;
83    const VOTEQUORUM_INFO_LAST_MAN_STANDING   = 8;
84    const VOTEQUORUM_INFO_AUTO_TIE_BREAKER    = 16;
85    const VOTEQUORUM_INFO_ALLOW_DOWNSCALE     = 32;
86    const VOTEQUORUM_INFO_QDEVICE_REGISTERED  = 64;
87    const VOTEQUORUM_INFO_QDEVICE_ALIVE       = 128;
88    const VOTEQUORUM_INFO_QDEVICE_CAST_VOTE   = 256;
89    const VOTEQUORUM_INFO_QDEVICE_MASTER_WINS = 512;
90    }
91}
92
93/// Detailed information about a node in the cluster, returned from [get_info]
94pub struct NodeInfo {
95    pub node_id: NodeId,
96    pub node_state: NodeState,
97    pub node_votes: u32,
98    pub node_expected_votes: u32,
99    pub highest_expected: u32,
100    pub quorum: u32,
101    pub flags: NodeInfoFlags,
102    pub qdevice_votes: u32,
103    pub qdevice_name: String,
104}
105
106// Turn a C nodeID list into a vec of NodeIds
107fn list_to_vec(list_entries: u32, list: *const u32) -> Vec<NodeId> {
108    let mut r_member_list = Vec::<NodeId>::new();
109    let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) };
110    for i in 0..list_entries as usize {
111        r_member_list.push(NodeId::from(temp_members[i]));
112    }
113    r_member_list
114}
115
116// Called from votequorum callback function - munge params back to Rust from C
117extern "C" fn rust_expectedvotes_notification_fn(
118    handle: ffi::votequorum_handle_t,
119    context: u64,
120    expected_votes: u32,
121) {
122    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
123        if let Some(cb) = h.callbacks.expectedvotes_notification_fn {
124            (cb)(h, context, expected_votes);
125        }
126    }
127}
128
129// Called from votequorum callback function - munge params back to Rust from C
130extern "C" fn rust_quorum_notification_fn(
131    handle: ffi::votequorum_handle_t,
132    context: u64,
133    quorate: u32,
134    node_list_entries: u32,
135    node_list: *mut ffi::votequorum_node_t,
136) {
137    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
138        let r_quorate = match quorate {
139            0 => false,
140            1 => true,
141            _ => false,
142        };
143        let mut r_node_list = Vec::<Node>::new();
144        let temp_members: &[ffi::votequorum_node_t] =
145            unsafe { slice::from_raw_parts(node_list, node_list_entries as usize) };
146        for i in 0..node_list_entries as usize {
147            r_node_list.push(Node {
148                nodeid: NodeId::from(temp_members[i].nodeid),
149                state: NodeState::new(temp_members[i].state),
150            });
151        }
152        if let Some(cb) = h.callbacks.quorum_notification_fn {
153            (cb)(h, context, r_quorate, r_node_list);
154        }
155    }
156}
157
158// Called from votequorum callback function - munge params back to Rust from C
159extern "C" fn rust_nodelist_notification_fn(
160    handle: ffi::votequorum_handle_t,
161    context: u64,
162    ring_id: ffi::votequorum_ring_id_t,
163    node_list_entries: u32,
164    node_list: *mut u32,
165) {
166    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
167        let r_ring_id = RingId {
168            nodeid: NodeId::from(ring_id.nodeid),
169            seq: ring_id.seq,
170        };
171
172        let r_node_list = list_to_vec(node_list_entries, node_list);
173
174        if let Some(cb) = h.callbacks.nodelist_notification_fn {
175            (cb)(h, context, r_ring_id, r_node_list);
176        }
177    }
178}
179
180/// Callbacks that can be called from votequorum, pass these in to [initialize]
181#[derive(Copy, Clone)]
182pub struct Callbacks {
183    pub quorum_notification_fn:
184        Option<fn(hande: &Handle, context: u64, quorate: bool, node_list: Vec<Node>)>,
185    pub nodelist_notification_fn:
186        Option<fn(hande: &Handle, context: u64, ring_id: RingId, node_list: Vec<NodeId>)>,
187    pub expectedvotes_notification_fn:
188        Option<fn(handle: &Handle, context: u64, expected_votes: u32)>,
189}
190
191/// A handle into the votequorum library. Returned from [initialize] and needed for all other calls
192pub struct Handle {
193    votequorum_handle: u64,
194    callbacks: Callbacks,
195    clone: bool,
196}
197
198impl Clone for Handle {
199    fn clone(&self) -> Handle {
200        Handle {
201            votequorum_handle: self.votequorum_handle,
202            callbacks: self.callbacks,
203            clone: true,
204        }
205    }
206}
207
208impl Drop for Handle {
209    fn drop(self: &mut Handle) {
210        if !self.clone {
211            let _e = finalize(self);
212        }
213    }
214}
215
216// Clones count as equivalent
217impl PartialEq for Handle {
218    fn eq(&self, other: &Handle) -> bool {
219        self.votequorum_handle == other.votequorum_handle
220    }
221}
222
223/// Initialize a connection to the votequorum library. You must call this before doing anything
224/// else and use the passed back [Handle].
225/// Remember to free the handle using [finalize] when finished.
226pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
227    let mut handle: ffi::votequorum_handle_t = 0;
228
229    let mut c_callbacks = ffi::votequorum_callbacks_t {
230        votequorum_quorum_notify_fn: Some(rust_quorum_notification_fn),
231        votequorum_nodelist_notify_fn: Some(rust_nodelist_notification_fn),
232        votequorum_expectedvotes_notify_fn: Some(rust_expectedvotes_notification_fn),
233    };
234
235    unsafe {
236        let res = ffi::votequorum_initialize(&mut handle, &mut c_callbacks);
237        if res == ffi::CS_OK {
238            let rhandle = Handle {
239                votequorum_handle: handle,
240                callbacks: *callbacks,
241                clone: false,
242            };
243            HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone());
244            Ok(rhandle)
245        } else {
246            Err(CsError::from_c(res))
247        }
248    }
249}
250
251/// Finish with a connection to corosync
252pub fn finalize(handle: &Handle) -> Result<()> {
253    let res = unsafe { ffi::votequorum_finalize(handle.votequorum_handle) };
254    if res == ffi::CS_OK {
255        HANDLE_HASH
256            .lock()
257            .unwrap()
258            .remove(&handle.votequorum_handle);
259        Ok(())
260    } else {
261        Err(CsError::from_c(res))
262    }
263}
264
265// Not sure if an FD is the right thing to return here, but it will do for now.
266/// Return a file descriptor to use for poll/select on the VOTEQUORUM handle
267pub fn fd_get(handle: &Handle) -> Result<i32> {
268    let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
269    let res = unsafe { ffi::votequorum_fd_get(handle.votequorum_handle, c_fd) };
270    if res == ffi::CS_OK {
271        Ok(unsafe { *c_fd })
272    } else {
273        Err(CsError::from_c(res))
274    }
275}
276
277const VOTEQUORUM_QDEVICE_MAX_NAME_LEN: usize = 255;
278
279/// Returns detailed information about a node in a [NodeInfo] structure
280pub fn get_info(handle: &Handle, nodeid: NodeId) -> Result<NodeInfo> {
281    let mut c_info = ffi::votequorum_info {
282        node_id: 0,
283        node_state: 0,
284        node_votes: 0,
285        node_expected_votes: 0,
286        highest_expected: 0,
287        total_votes: 0,
288        quorum: 0,
289        flags: 0,
290        qdevice_votes: 0,
291        qdevice_name: [0; 255usize],
292    };
293    let res = unsafe {
294        ffi::votequorum_getinfo(handle.votequorum_handle, u32::from(nodeid), &mut c_info)
295    };
296
297    if res == ffi::CS_OK {
298        let info = NodeInfo {
299            node_id: NodeId::from(c_info.node_id),
300            node_state: NodeState::new(c_info.node_state),
301            node_votes: c_info.node_votes,
302            node_expected_votes: c_info.node_expected_votes,
303            highest_expected: c_info.highest_expected,
304            quorum: c_info.quorum,
305            flags: NodeInfoFlags::from_bits(c_info.flags).unwrap_or(NodeInfoFlags::empty()),
306            qdevice_votes: c_info.qdevice_votes,
307            qdevice_name: string_from_bytes(
308                c_info.qdevice_name.as_ptr(),
309                VOTEQUORUM_QDEVICE_MAX_NAME_LEN,
310            )
311            .unwrap_or_default(),
312        };
313        Ok(info)
314    } else {
315        Err(CsError::from_c(res))
316    }
317}
318
319/// Call any/all active votequorum callbacks for this [Handle]. see [DispatchFlags] for details
320pub fn dispatch(handle: &Handle, flags: DispatchFlags) -> Result<()> {
321    let res = unsafe { ffi::votequorum_dispatch(handle.votequorum_handle, flags as u32) };
322    if res == ffi::CS_OK {
323        Ok(())
324    } else {
325        Err(CsError::from_c(res))
326    }
327}
328
329/// Track node and votequorum changes
330pub fn trackstart(handle: &Handle, context: u64, flags: TrackFlags) -> Result<()> {
331    let res =
332        unsafe { ffi::votequorum_trackstart(handle.votequorum_handle, context, flags as u32) };
333    if res == ffi::CS_OK {
334        Ok(())
335    } else {
336        Err(CsError::from_c(res))
337    }
338}
339
340/// Stop tracking node and votequorum changes
341pub fn trackstop(handle: &Handle) -> Result<()> {
342    let res = unsafe { ffi::votequorum_trackstop(handle.votequorum_handle) };
343    if res == ffi::CS_OK {
344        Ok(())
345    } else {
346        Err(CsError::from_c(res))
347    }
348}
349
350/// Get the current 'context' value for this handle.
351/// The context value is an arbitrary value that is always passed
352/// back to callbacks to help identify the source
353pub fn context_get(handle: &Handle) -> Result<u64> {
354    let (res, context) = unsafe {
355        let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void;
356        let r = ffi::votequorum_context_get(handle.votequorum_handle, &mut c_context);
357        let context: u64 = c_context as u64;
358        (r, context)
359    };
360    if res == ffi::CS_OK {
361        Ok(context)
362    } else {
363        Err(CsError::from_c(res))
364    }
365}
366
367/// Set the current 'context' value for this handle.
368/// The context value is an arbitrary value that is always passed
369/// back to callbacks to help identify the source.
370/// Normally this is set in [trackstart], but this allows it to be changed
371pub fn context_set(handle: &Handle, context: u64) -> Result<()> {
372    let res = unsafe {
373        let c_context = context as *mut c_void;
374        ffi::votequorum_context_set(handle.votequorum_handle, c_context)
375    };
376    if res == ffi::CS_OK {
377        Ok(())
378    } else {
379        Err(CsError::from_c(res))
380    }
381}
382
383/// Set the current expected_votes for the cluster, this value must
384/// be valid and not result in an inquorate cluster.
385pub fn set_expected(handle: &Handle, expected_votes: u32) -> Result<()> {
386    let res = unsafe { ffi::votequorum_setexpected(handle.votequorum_handle, expected_votes) };
387    if res == ffi::CS_OK {
388        Ok(())
389    } else {
390        Err(CsError::from_c(res))
391    }
392}
393
394/// Set the current votes for a node
395pub fn set_votes(handle: &Handle, nodeid: NodeId, votes: u32) -> Result<()> {
396    let res =
397        unsafe { ffi::votequorum_setvotes(handle.votequorum_handle, u32::from(nodeid), votes) };
398    if res == ffi::CS_OK {
399        Ok(())
400    } else {
401        Err(CsError::from_c(res))
402    }
403}
404
405/// Register a quorum device
406pub fn qdevice_register(handle: &Handle, name: &str) -> Result<()> {
407    let c_string = {
408        match CString::new(name) {
409            Ok(cs) => cs,
410            Err(_) => return Err(CsError::CsErrInvalidParam),
411        }
412    };
413
414    let res =
415        unsafe { ffi::votequorum_qdevice_register(handle.votequorum_handle, c_string.as_ptr()) };
416    if res == ffi::CS_OK {
417        Ok(())
418    } else {
419        Err(CsError::from_c(res))
420    }
421}
422
423/// Unregister a quorum device
424pub fn qdevice_unregister(handle: &Handle, name: &str) -> Result<()> {
425    let c_string = {
426        match CString::new(name) {
427            Ok(cs) => cs,
428            Err(_) => return Err(CsError::CsErrInvalidParam),
429        }
430    };
431
432    let res =
433        unsafe { ffi::votequorum_qdevice_unregister(handle.votequorum_handle, c_string.as_ptr()) };
434    if res == ffi::CS_OK {
435        Ok(())
436    } else {
437        Err(CsError::from_c(res))
438    }
439}
440
441/// Update the name of a quorum device
442pub fn qdevice_update(handle: &Handle, oldname: &str, newname: &str) -> Result<()> {
443    let on_string = {
444        match CString::new(oldname) {
445            Ok(cs) => cs,
446            Err(_) => return Err(CsError::CsErrInvalidParam),
447        }
448    };
449    let nn_string = {
450        match CString::new(newname) {
451            Ok(cs) => cs,
452            Err(_) => return Err(CsError::CsErrInvalidParam),
453        }
454    };
455
456    let res = unsafe {
457        ffi::votequorum_qdevice_update(
458            handle.votequorum_handle,
459            on_string.as_ptr(),
460            nn_string.as_ptr(),
461        )
462    };
463    if res == ffi::CS_OK {
464        Ok(())
465    } else {
466        Err(CsError::from_c(res))
467    }
468}
469
470/// Poll a quorum device
471/// This must be done more often than the qdevice timeout (default 10s) while the device is active
472/// and the [RingId] must match the current value returned from the callbacks for it to be accepted.
473pub fn qdevice_poll(handle: &Handle, name: &str, cast_vote: bool, ring_id: &RingId) -> Result<()> {
474    let c_string = {
475        match CString::new(name) {
476            Ok(cs) => cs,
477            Err(_) => return Err(CsError::CsErrInvalidParam),
478        }
479    };
480
481    let c_cast_vote: u32 = u32::from(cast_vote);
482    let c_ring_id = ffi::votequorum_ring_id_t {
483        nodeid: u32::from(ring_id.nodeid),
484        seq: ring_id.seq,
485    };
486
487    let res = unsafe {
488        ffi::votequorum_qdevice_poll(
489            handle.votequorum_handle,
490            c_string.as_ptr(),
491            c_cast_vote,
492            c_ring_id,
493        )
494    };
495    if res == ffi::CS_OK {
496        Ok(())
497    } else {
498        Err(CsError::from_c(res))
499    }
500}
501
502/// Allow qdevice to tell votequorum if master_wins can be enabled or not
503pub fn qdevice_master_wins(handle: &Handle, name: &str, master_wins: bool) -> Result<()> {
504    let c_string = {
505        match CString::new(name) {
506            Ok(cs) => cs,
507            Err(_) => return Err(CsError::CsErrInvalidParam),
508        }
509    };
510
511    let c_master_wins: u32 = u32::from(master_wins);
512
513    let res = unsafe {
514        ffi::votequorum_qdevice_master_wins(
515            handle.votequorum_handle,
516            c_string.as_ptr(),
517            c_master_wins,
518        )
519    };
520    if res == ffi::CS_OK {
521        Ok(())
522    } else {
523        Err(CsError::from_c(res))
524    }
525}