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
192#[derive(Copy, Clone)]
193pub struct Handle {
194    votequorum_handle: u64,
195    callbacks: Callbacks,
196}
197
198/// Initialize a connection to the votequorum library. You must call this before doing anything
199/// else and use the passed back [Handle].
200/// Remember to free the handle using [finalize] when finished.
201pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
202    let mut handle: ffi::votequorum_handle_t = 0;
203
204    let mut c_callbacks = ffi::votequorum_callbacks_t {
205        votequorum_quorum_notify_fn: Some(rust_quorum_notification_fn),
206        votequorum_nodelist_notify_fn: Some(rust_nodelist_notification_fn),
207        votequorum_expectedvotes_notify_fn: Some(rust_expectedvotes_notification_fn),
208    };
209
210    unsafe {
211        let res = ffi::votequorum_initialize(&mut handle, &mut c_callbacks);
212        if res == ffi::CS_OK {
213            let rhandle = Handle {
214                votequorum_handle: handle,
215                callbacks: *callbacks,
216            };
217            HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
218            Ok(rhandle)
219        } else {
220            Err(CsError::from_c(res))
221        }
222    }
223}
224
225/// Finish with a connection to corosync
226pub fn finalize(handle: Handle) -> Result<()> {
227    let res = unsafe { ffi::votequorum_finalize(handle.votequorum_handle) };
228    if res == ffi::CS_OK {
229        HANDLE_HASH
230            .lock()
231            .unwrap()
232            .remove(&handle.votequorum_handle);
233        Ok(())
234    } else {
235        Err(CsError::from_c(res))
236    }
237}
238
239// Not sure if an FD is the right thing to return here, but it will do for now.
240/// Return a file descriptor to use for poll/select on the VOTEQUORUM handle
241pub fn fd_get(handle: Handle) -> Result<i32> {
242    let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
243    let res = unsafe { ffi::votequorum_fd_get(handle.votequorum_handle, c_fd) };
244    if res == ffi::CS_OK {
245        Ok(c_fd as i32)
246    } else {
247        Err(CsError::from_c(res))
248    }
249}
250
251const VOTEQUORUM_QDEVICE_MAX_NAME_LEN: usize = 255;
252
253/// Returns detailed information about a node in a [NodeInfo] structure
254pub fn get_info(handle: Handle, nodeid: NodeId) -> Result<NodeInfo> {
255    let mut c_info = ffi::votequorum_info {
256        node_id: 0,
257        node_state: 0,
258        node_votes: 0,
259        node_expected_votes: 0,
260        highest_expected: 0,
261        total_votes: 0,
262        quorum: 0,
263        flags: 0,
264        qdevice_votes: 0,
265        qdevice_name: [0; 255usize],
266    };
267    let res = unsafe {
268        ffi::votequorum_getinfo(handle.votequorum_handle, u32::from(nodeid), &mut c_info)
269    };
270
271    if res == ffi::CS_OK {
272        let info = NodeInfo {
273            node_id: NodeId::from(c_info.node_id),
274            node_state: NodeState::new(c_info.node_state),
275            node_votes: c_info.node_votes,
276            node_expected_votes: c_info.node_expected_votes,
277            highest_expected: c_info.highest_expected,
278            quorum: c_info.quorum,
279            flags: NodeInfoFlags { bits: c_info.flags },
280            qdevice_votes: c_info.qdevice_votes,
281            qdevice_name: match string_from_bytes(
282                c_info.qdevice_name.as_ptr(),
283                VOTEQUORUM_QDEVICE_MAX_NAME_LEN,
284            ) {
285                Ok(s) => s,
286                Err(_) => String::new(),
287            },
288        };
289        Ok(info)
290    } else {
291        Err(CsError::from_c(res))
292    }
293}
294
295/// Call any/all active votequorum callbacks for this [Handle]. see [DispatchFlags] for details
296pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
297    let res = unsafe { ffi::votequorum_dispatch(handle.votequorum_handle, flags as u32) };
298    if res == ffi::CS_OK {
299        Ok(())
300    } else {
301        Err(CsError::from_c(res))
302    }
303}
304
305/// Track node and votequorum changes
306pub fn trackstart(handle: Handle, context: u64, flags: TrackFlags) -> Result<()> {
307    let res =
308        unsafe { ffi::votequorum_trackstart(handle.votequorum_handle, context, flags as u32) };
309    if res == ffi::CS_OK {
310        Ok(())
311    } else {
312        Err(CsError::from_c(res))
313    }
314}
315
316/// Stop tracking node and votequorum changes
317pub fn trackstop(handle: Handle) -> Result<()> {
318    let res = unsafe { ffi::votequorum_trackstop(handle.votequorum_handle) };
319    if res == ffi::CS_OK {
320        Ok(())
321    } else {
322        Err(CsError::from_c(res))
323    }
324}
325
326/// Get the current 'context' value for this handle.
327/// The context value is an arbitrary value that is always passed
328/// back to callbacks to help identify the source
329pub fn context_get(handle: Handle) -> Result<u64> {
330    let (res, context) = unsafe {
331        let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void;
332        let r = ffi::votequorum_context_get(handle.votequorum_handle, &mut c_context);
333        let context: u64 = c_context as u64;
334        (r, context)
335    };
336    if res == ffi::CS_OK {
337        Ok(context)
338    } else {
339        Err(CsError::from_c(res))
340    }
341}
342
343/// Set the current 'context' value for this handle.
344/// The context value is an arbitrary value that is always passed
345/// back to callbacks to help identify the source.
346/// Normally this is set in [trackstart], but this allows it to be changed
347pub fn context_set(handle: Handle, context: u64) -> Result<()> {
348    let res = unsafe {
349        let c_context = context as *mut c_void;
350        ffi::votequorum_context_set(handle.votequorum_handle, c_context)
351    };
352    if res == ffi::CS_OK {
353        Ok(())
354    } else {
355        Err(CsError::from_c(res))
356    }
357}
358
359/// Set the current expected_votes for the cluster, this value must
360/// be valid and not result in an inquorate cluster.
361pub fn set_expected(handle: Handle, expected_votes: u32) -> Result<()> {
362    let res = unsafe { ffi::votequorum_setexpected(handle.votequorum_handle, expected_votes) };
363    if res == ffi::CS_OK {
364        Ok(())
365    } else {
366        Err(CsError::from_c(res))
367    }
368}
369
370/// Set the current votes for a node
371pub fn set_votes(handle: Handle, nodeid: NodeId, votes: u32) -> Result<()> {
372    let res =
373        unsafe { ffi::votequorum_setvotes(handle.votequorum_handle, u32::from(nodeid), votes) };
374    if res == ffi::CS_OK {
375        Ok(())
376    } else {
377        Err(CsError::from_c(res))
378    }
379}
380
381/// Register a quorum device
382pub fn qdevice_register(handle: Handle, name: &str) -> Result<()> {
383    let c_string = {
384        match CString::new(name) {
385            Ok(cs) => cs,
386            Err(_) => return Err(CsError::CsErrInvalidParam),
387        }
388    };
389
390    let res =
391        unsafe { ffi::votequorum_qdevice_register(handle.votequorum_handle, c_string.as_ptr()) };
392    if res == ffi::CS_OK {
393        Ok(())
394    } else {
395        Err(CsError::from_c(res))
396    }
397}
398
399/// Unregister a quorum device
400pub fn qdevice_unregister(handle: Handle, name: &str) -> Result<()> {
401    let c_string = {
402        match CString::new(name) {
403            Ok(cs) => cs,
404            Err(_) => return Err(CsError::CsErrInvalidParam),
405        }
406    };
407
408    let res =
409        unsafe { ffi::votequorum_qdevice_unregister(handle.votequorum_handle, c_string.as_ptr()) };
410    if res == ffi::CS_OK {
411        Ok(())
412    } else {
413        Err(CsError::from_c(res))
414    }
415}
416
417/// Update the name of a quorum device
418pub fn qdevice_update(handle: Handle, oldname: &str, newname: &str) -> Result<()> {
419    let on_string = {
420        match CString::new(oldname) {
421            Ok(cs) => cs,
422            Err(_) => return Err(CsError::CsErrInvalidParam),
423        }
424    };
425    let nn_string = {
426        match CString::new(newname) {
427            Ok(cs) => cs,
428            Err(_) => return Err(CsError::CsErrInvalidParam),
429        }
430    };
431
432    let res = unsafe {
433        ffi::votequorum_qdevice_update(
434            handle.votequorum_handle,
435            on_string.as_ptr(),
436            nn_string.as_ptr(),
437        )
438    };
439    if res == ffi::CS_OK {
440        Ok(())
441    } else {
442        Err(CsError::from_c(res))
443    }
444}
445
446/// Poll a quorum device
447/// This must be done more often than the qdevice timeout (default 10s) while the device is active
448/// and the [RingId] must match the current value returned from the callbacks for it to be accepted.
449pub fn qdevice_poll(handle: Handle, name: &str, cast_vote: bool, ring_id: &RingId) -> Result<()> {
450    let c_string = {
451        match CString::new(name) {
452            Ok(cs) => cs,
453            Err(_) => return Err(CsError::CsErrInvalidParam),
454        }
455    };
456
457    let c_cast_vote: u32 = u32::from(cast_vote);
458    let c_ring_id = ffi::votequorum_ring_id_t {
459        nodeid: u32::from(ring_id.nodeid),
460        seq: ring_id.seq,
461    };
462
463    let res = unsafe {
464        ffi::votequorum_qdevice_poll(
465            handle.votequorum_handle,
466            c_string.as_ptr(),
467            c_cast_vote,
468            c_ring_id,
469        )
470    };
471    if res == ffi::CS_OK {
472        Ok(())
473    } else {
474        Err(CsError::from_c(res))
475    }
476}
477
478/// Allow qdevice to tell votequorum if master_wins can be enabled or not
479pub fn qdevice_master_wins(handle: Handle, name: &str, master_wins: bool) -> Result<()> {
480    let c_string = {
481        match CString::new(name) {
482            Ok(cs) => cs,
483            Err(_) => return Err(CsError::CsErrInvalidParam),
484        }
485    };
486
487    let c_master_wins: u32 = u32::from(master_wins);
488
489    let res = unsafe {
490        ffi::votequorum_qdevice_master_wins(
491            handle.votequorum_handle,
492            c_string.as_ptr(),
493            c_master_wins,
494        )
495    };
496    if res == ffi::CS_OK {
497        Ok(())
498    } else {
499        Err(CsError::from_c(res))
500    }
501}