Example #1
0
    def _setup_notif_port(self):
        for notif_filter in self._notif_filters_list:
            filter_struct = clusapi_def.NOTIFY_FILTER_AND_TYPE(
                dwObjectType=notif_filter['object_type'],
                FilterFlags=notif_filter['filter_flags'])
            notif_key = notif_filter.get('notif_key', 0)

            self._add_filter(filter_struct, notif_key)
Example #2
0
    def get_cluster_notify_v2(self, notif_port_h, timeout_ms):
        filter_and_type = clusapi_def.NOTIFY_FILTER_AND_TYPE()
        obj_name_buff_sz = ctypes.c_ulong(w_const.MAX_PATH)
        notif_key_p = wintypes.PDWORD()
        buff_sz = ctypes.c_ulong(w_const.MAX_PATH)

        # Event notification buffer. The notification format depends
        # on the event type and filter flags.
        buff = (wintypes.BYTE * buff_sz.value)()
        obj_name_buff = (ctypes.c_wchar * obj_name_buff_sz.value)()

        def get_args(buff, obj_name_buff):
            return (
                clusapi.GetClusterNotifyV2,
                notif_port_h,
                ctypes.byref(notif_key_p),
                ctypes.byref(filter_and_type),
                buff,
                ctypes.byref(buff_sz),
                None,  # object id
                None,  # object id sz
                None,  # parent id
                None,  # parent id sz
                obj_name_buff,
                ctypes.byref(obj_name_buff_sz),
                None,  # object type
                None,  # object type sz
                timeout_ms)

        try:
            self._run_and_check_output(*get_args(buff, obj_name_buff))
        except exceptions.ClusterWin32Exception as ex:
            if ex.error_code == w_const.ERROR_MORE_DATA:
                # This function will specify the buffer sizes it needs using
                # the references we pass.
                buff = (wintypes.BYTE * buff_sz.value)()
                obj_name_buff = (ctypes.c_wchar * obj_name_buff_sz.value)()

                self._run_and_check_output(*get_args(buff, obj_name_buff))
            else:
                raise

        # We'll leverage notification key values instead of their addresses,
        # although this returns us the address we passed in when setting up
        # the notification port.
        notif_key = notif_key_p.contents.value
        event = {
            'cluster_object_name': obj_name_buff.value,
            'object_type': filter_and_type.dwObjectType,
            'filter_flags': filter_and_type.FilterFlags,
            'buff': buff,
            'buff_sz': buff_sz.value,
            'notif_key': notif_key
        }
        return event
Example #3
0
class ClusApiUtilsTestCase(test_base.OsWinBaseTestCase):
    _LIVE_MIGRATION_TYPE = 4

    def setUp(self):
        super(ClusApiUtilsTestCase, self).setUp()

        self._clusapi = mock.patch.object(_clusapi_utils,
                                          'clusapi',
                                          create=True).start()

        self._clusapi_utils = _clusapi_utils.ClusApiUtils()

        self._run_patcher = mock.patch.object(self._clusapi_utils,
                                              '_run_and_check_output')
        self._mock_run = self._run_patcher.start()

    def _mock_ctypes(self):
        self._ctypes = mock.Mock()
        # This is used in order to easily make assertions on the variables
        # passed by reference.
        self._ctypes.byref = lambda x: (x, "byref")
        self._ctypes.c_wchar_p = lambda x: (x, 'c_wchar_p')
        self._ctypes.sizeof = lambda x: (x, 'sizeof')
        self._ctypes.c_ulong = lambda x: (x, 'c_ulong')

        mock.patch.object(_clusapi_utils, 'ctypes', self._ctypes).start()

    def test_run_and_check_output(self):
        self._clusapi_utils._win32utils = mock.Mock()
        self._clusapi_utils._run_and_check_output = (
            self._run_patcher.temp_original)

        mock_win32utils_run_and_check_output = (
            self._clusapi_utils._win32utils.run_and_check_output)

        ret_val = self._clusapi_utils._run_and_check_output(
            mock.sentinel.func,
            mock.sentinel.arg,
            fake_kwarg=mock.sentinel.kwarg)

        mock_win32utils_run_and_check_output.assert_called_once_with(
            mock.sentinel.func,
            mock.sentinel.arg,
            fake_kwarg=mock.sentinel.kwarg,
            failure_exc=exceptions.ClusterWin32Exception)
        self.assertEqual(mock_win32utils_run_and_check_output.return_value,
                         ret_val)

    def test_dword_align(self):
        self.assertEqual(8, self._clusapi_utils._dword_align(5))
        self.assertEqual(4, self._clusapi_utils._dword_align(4))

    def test_get_clusprop_value_struct(self):
        val_type = ctypes.c_ubyte * 3
        expected_padding_sz = 1

        clusprop_val_struct = self._clusapi_utils._get_clusprop_value_struct(
            val_type)

        expected_fields = [('syntax', wintypes.DWORD),
                           ('length', wintypes.DWORD), ('value', val_type),
                           ('_padding', ctypes.c_ubyte * expected_padding_sz)]
        self.assertEqual(expected_fields, clusprop_val_struct._fields_)

    def test_get_property_list_entry(self):
        fake_prop_name = 'fake prop name'
        fake_prop_syntax = 1
        fake_prop_val = (ctypes.c_wchar * 10)()
        fake_prop_val.value = 'fake prop'

        entry = self._clusapi_utils.get_property_list_entry(
            name=fake_prop_name, syntax=fake_prop_syntax, value=fake_prop_val)

        self.assertEqual(w_const.CLUSPROP_SYNTAX_NAME, entry.name.syntax)
        self.assertEqual(fake_prop_name, entry.name.value)
        self.assertEqual(
            ctypes.sizeof(ctypes.c_wchar) * (len(fake_prop_name) + 1),
            entry.name.length)

        self.assertEqual(fake_prop_syntax, entry.value.syntax)
        self.assertEqual(bytearray(fake_prop_val),
                         bytearray(entry.value.value))
        self.assertEqual(ctypes.sizeof(fake_prop_val), entry.value.length)

        self.assertEqual(w_const.CLUSPROP_SYNTAX_ENDMARK, entry._endmark)

    def test_get_property_list(self):
        entry_0 = self._clusapi_utils.get_property_list_entry(
            name='fake prop name', syntax=1, value=ctypes.c_uint(2))
        entry_1 = self._clusapi_utils.get_property_list_entry(
            name='fake prop name', syntax=2, value=ctypes.c_ubyte(5))

        prop_list = self._clusapi_utils.get_property_list([entry_0, entry_1])

        self.assertEqual(2, prop_list.count)
        self.assertEqual(
            bytearray(entry_0) + bytearray(entry_1), prop_list.entries_buff)

    @ddt.data('fake cluster name', None)
    def test_open_cluster(self, cluster_name):
        self._mock_ctypes()

        handle = self._clusapi_utils.open_cluster(cluster_name)

        expected_handle_arg = (self._ctypes.c_wchar_p(cluster_name)
                               if cluster_name else None)
        self._mock_run.assert_called_once_with(
            self._clusapi.OpenCluster, expected_handle_arg,
            **self._clusapi_utils._open_handle_check_flags)

        self.assertEqual(self._mock_run.return_value, handle)

    def test_open_cluster_group(self):
        self._mock_ctypes()

        handle = self._clusapi_utils.open_cluster_group(
            mock.sentinel.cluster_handle, mock.sentinel.group_name)

        self._mock_run.assert_called_once_with(
            self._clusapi.OpenClusterGroup, mock.sentinel.cluster_handle,
            self._ctypes.c_wchar_p(mock.sentinel.group_name),
            **self._clusapi_utils._open_handle_check_flags)

        self.assertEqual(self._mock_run.return_value, handle)

    def test_open_cluster_node(self):
        self._mock_ctypes()

        handle = self._clusapi_utils.open_cluster_node(
            mock.sentinel.cluster_handle, mock.sentinel.node_name)

        self._mock_run.assert_called_once_with(
            self._clusapi.OpenClusterNode, mock.sentinel.cluster_handle,
            self._ctypes.c_wchar_p(mock.sentinel.node_name),
            **self._clusapi_utils._open_handle_check_flags)

        self.assertEqual(self._mock_run.return_value, handle)

    def test_close_cluster(self):
        self._clusapi_utils.close_cluster(mock.sentinel.handle)
        self._clusapi.CloseCluster.assert_called_once_with(
            mock.sentinel.handle)

    def test_close_cluster_group(self):
        self._clusapi_utils.close_cluster_group(mock.sentinel.handle)
        self._clusapi.CloseClusterGroup.assert_called_once_with(
            mock.sentinel.handle)

    def test_close_cluster_node(self):
        self._clusapi_utils.close_cluster_node(mock.sentinel.handle)
        self._clusapi.CloseClusterNode.assert_called_once_with(
            mock.sentinel.handle)

    @ddt.data(0, w_const.ERROR_IO_PENDING)
    def test_cancel_cluster_group_operation(self, cancel_ret_val):
        self._mock_run.return_value = cancel_ret_val

        expected_ret_val = cancel_ret_val != w_const.ERROR_IO_PENDING
        ret_val = self._clusapi_utils.cancel_cluster_group_operation(
            mock.sentinel.group_handle)

        self.assertEqual(expected_ret_val, ret_val)

        self._mock_run.assert_called_once_with(
            self._clusapi.CancelClusterGroupOperation,
            mock.sentinel.group_handle,
            0,
            ignored_error_codes=[w_const.ERROR_IO_PENDING])

    @ddt.data(mock.sentinel.prop_list, None)
    def test_move_cluster_group(self, prop_list):
        self._mock_ctypes()

        expected_prop_list_arg = (self._ctypes.byref(prop_list)
                                  if prop_list else None)
        expected_prop_list_sz = (self._ctypes.sizeof(prop_list)
                                 if prop_list else 0)

        self._clusapi_utils.move_cluster_group(mock.sentinel.group_handle,
                                               mock.sentinel.dest_node_handle,
                                               mock.sentinel.move_flags,
                                               prop_list)

        self._mock_run.assert_called_once_with(
            self._clusapi.MoveClusterGroupEx,
            mock.sentinel.group_handle,
            mock.sentinel.dest_node_handle,
            mock.sentinel.move_flags,
            expected_prop_list_arg,
            expected_prop_list_sz,
            ignored_error_codes=[w_const.ERROR_IO_PENDING])

    def test_get_cluster_group_state(self):
        owner_node = 'fake owner node'

        def fake_get_state(inst, group_handle, node_name_buff, node_name_len,
                           error_ret_vals, error_on_nonzero_ret_val,
                           ret_val_is_err_code):
            self.assertEqual(mock.sentinel.group_handle, group_handle)

            # Those arguments would not normally get to the ClusApi
            # function, instead being used by the helper invoking
            # it and catching errors. For convenience, we validate
            # those arguments at this point.
            self.assertEqual([constants.CLUSTER_GROUP_STATE_UNKNOWN],
                             error_ret_vals)
            self.assertFalse(error_on_nonzero_ret_val)
            self.assertFalse(ret_val_is_err_code)

            node_name_len_arg = ctypes.cast(node_name_len,
                                            wintypes.PDWORD).contents
            self.assertEqual(w_const.MAX_PATH, node_name_len_arg.value)

            node_name_arg = ctypes.cast(
                node_name_buff,
                ctypes.POINTER(ctypes.c_wchar * w_const.MAX_PATH)).contents
            node_name_arg.value = owner_node
            return mock.sentinel.group_state

        self._mock_run.side_effect = fake_get_state

        state_info = self._clusapi_utils.get_cluster_group_state(
            mock.sentinel.group_handle)
        expected_state_info = dict(state=mock.sentinel.group_state,
                                   owner_node=owner_node)
        self.assertEqual(expected_state_info, state_info)

    @ddt.data(
        {
            'notif_filters': (clusapi_def.NOTIFY_FILTER_AND_TYPE * 2)(),
            'exp_notif_filters_len': 2
        }, {
            'notif_filters': clusapi_def.NOTIFY_FILTER_AND_TYPE(),
            'notif_port_h': mock.sentinel.notif_port_h,
            'notif_key': mock.sentinel.notif_key
        })
    @ddt.unpack
    def test_create_cluster_notify_port(self,
                                        notif_filters,
                                        exp_notif_filters_len=1,
                                        notif_port_h=None,
                                        notif_key=None):
        self._mock_ctypes()
        self._ctypes.Array = ctypes.Array

        self._clusapi_utils.create_cluster_notify_port_v2(
            mock.sentinel.cluster_handle, notif_filters, notif_port_h,
            notif_key)

        exp_notif_key_p = self._ctypes.byref(notif_key) if notif_key else None
        exp_notif_port_h = notif_port_h or w_const.INVALID_HANDLE_VALUE

        self._mock_run.assert_called_once_with(
            self._clusapi.CreateClusterNotifyPortV2, exp_notif_port_h,
            mock.sentinel.cluster_handle, self._ctypes.byref(notif_filters),
            self._ctypes.c_ulong(exp_notif_filters_len), exp_notif_key_p,
            **self._clusapi_utils._open_handle_check_flags)

    def test_close_cluster_notify_port(self):
        self._clusapi_utils.close_cluster_notify_port(mock.sentinel.handle)
        self._clusapi.CloseClusterNotifyPort.assert_called_once_with(
            mock.sentinel.handle)

    def test_get_cluster_notify_v2(self):
        fake_notif_key = 1
        fake_notif_port_h = 2
        fake_notif_type = 3
        fake_filter_flags = 4
        fake_clus_obj_name = 'fake-changed-clus-object'
        fake_event_buff = 'fake-event-buff'

        notif_key = ctypes.c_ulong(fake_notif_key)
        requested_buff_sz = 1024

        def fake_get_cluster_notify(func, notif_port_h, pp_notif_key,
                                    p_filter_and_type, p_buff, p_buff_sz,
                                    p_obj_id_buff, p_obj_id_buff_sz,
                                    p_parent_id_buff, p_parent_id_buff_sz,
                                    p_obj_name_buff, p_obj_name_buff_sz,
                                    p_obj_type, p_obj_type_sz, timeout_ms):
            self.assertEqual(self._clusapi.GetClusterNotifyV2, func)
            self.assertEqual(fake_notif_port_h, notif_port_h)

            obj_name_buff_sz = ctypes.cast(p_obj_name_buff_sz,
                                           wintypes.PDWORD).contents
            buff_sz = ctypes.cast(p_buff_sz, wintypes.PDWORD).contents

            # We'll just request the tested method to pass us
            # a buffer this large.
            if (buff_sz.value < requested_buff_sz
                    or obj_name_buff_sz.value < requested_buff_sz):
                buff_sz.value = requested_buff_sz
                obj_name_buff_sz.value = requested_buff_sz
                raise exceptions.ClusterWin32Exception(
                    error_code=w_const.ERROR_MORE_DATA,
                    func_name='GetClusterNotify',
                    error_message='error more data')

            pp_notif_key = ctypes.cast(pp_notif_key, ctypes.c_void_p)
            p_notif_key = ctypes.c_void_p.from_address(pp_notif_key.value)
            p_notif_key.value = ctypes.addressof(notif_key)

            filter_and_type = ctypes.cast(
                p_filter_and_type,
                ctypes.POINTER(clusapi_def.NOTIFY_FILTER_AND_TYPE)).contents
            filter_and_type.dwObjectType = fake_notif_type
            filter_and_type.FilterFlags = fake_filter_flags

            obj_name_buff = ctypes.cast(
                p_obj_name_buff,
                ctypes.POINTER(
                    ctypes.c_wchar *
                    (requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
            obj_name_buff = obj_name_buff.contents
            ctypes.memset(obj_name_buff, 0, obj_name_buff_sz.value)
            obj_name_buff.value = fake_clus_obj_name

            buff = ctypes.cast(
                p_buff,
                ctypes.POINTER(
                    ctypes.c_wchar *
                    (requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
            buff = buff.contents
            ctypes.memset(buff, 0, buff_sz.value)
            buff.value = fake_event_buff

            self.assertEqual(mock.sentinel.timeout_ms, timeout_ms)

        self._mock_run.side_effect = fake_get_cluster_notify

        event = self._clusapi_utils.get_cluster_notify_v2(
            fake_notif_port_h, mock.sentinel.timeout_ms)
        w_event_buff = ctypes.cast(
            event['buff'],
            ctypes.POINTER(
                ctypes.c_wchar *
                (requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
        w_event_buff = w_event_buff.contents[:]
        event['buff'] = w_event_buff.split('\x00')[0]

        expected_event = dict(cluster_object_name=fake_clus_obj_name,
                              object_type=fake_notif_type,
                              filter_flags=fake_filter_flags,
                              buff=fake_event_buff,
                              buff_sz=requested_buff_sz,
                              notif_key=fake_notif_key)
        self.assertEqual(expected_event, event)

    def _get_fake_prop_list(self):
        syntax = w_const.CLUSPROP_SYNTAX_LIST_VALUE_DWORD
        migr_type = wintypes.DWORD(self._LIVE_MIGRATION_TYPE)

        prop_entries = [
            self._clusapi_utils.get_property_list_entry(
                w_const.CLUS_RESTYPE_NAME_VM, syntax, migr_type),
            self._clusapi_utils.get_property_list_entry(
                w_const.CLUS_RESTYPE_NAME_VM_CONFIG, syntax, migr_type),
            self._clusapi_utils.get_property_list_entry(
                w_const.CLUSREG_NAME_GRP_STATUS_INFORMATION,
                w_const.CLUSPROP_SYNTAX_LIST_VALUE_ULARGE_INTEGER,
                ctypes.c_ulonglong(
                    w_const.CLUSGRP_STATUS_WAITING_IN_QUEUE_FOR_MOVE))  # noqa
        ]

        prop_list = self._clusapi_utils.get_property_list(prop_entries)
        return prop_list

    def test_get_prop_list_entry_p_not_found(self):
        prop_list = self._get_fake_prop_list()

        self.assertRaises(exceptions.ClusterPropertyListEntryNotFound,
                          self._clusapi_utils.get_prop_list_entry_p,
                          ctypes.byref(prop_list), ctypes.sizeof(prop_list),
                          'InexistentProperty')

    def test_get_prop_list_entry_p_parsing_error(self):
        prop_list = self._get_fake_prop_list()

        prop_entry_name_len_addr = ctypes.addressof(
            prop_list.entries_buff) + ctypes.sizeof(ctypes.c_ulong)
        prop_entry_name_len = ctypes.c_ulong.from_address(
            prop_entry_name_len_addr)
        prop_entry_name_len.value = ctypes.sizeof(prop_list)

        self.assertRaises(exceptions.ClusterPropertyListParsingError,
                          self._clusapi_utils.get_prop_list_entry_p,
                          ctypes.byref(prop_list), ctypes.sizeof(prop_list),
                          w_const.CLUS_RESTYPE_NAME_VM)

    def test_get_prop_list_entry_p(self):
        prop_list = self._get_fake_prop_list()

        prop_entry = self._clusapi_utils.get_prop_list_entry_p(
            ctypes.byref(prop_list), ctypes.sizeof(prop_list),
            w_const.CLUS_RESTYPE_NAME_VM_CONFIG)

        self.assertEqual(w_const.CLUSPROP_SYNTAX_LIST_VALUE_DWORD,
                         prop_entry['syntax'])
        self.assertEqual(ctypes.sizeof(ctypes.c_ulong), prop_entry['length'])

        val = ctypes.c_ulong.from_address(prop_entry['val_p'].value).value
        self.assertEqual(self._LIVE_MIGRATION_TYPE, val)

    def test_cluster_group_control(self):
        fake_out_buff = 'fake-event-buff'

        requested_buff_sz = 1024

        def fake_cluster_group_ctrl(func, group_handle, node_handle,
                                    control_code, in_buff_p, in_buff_sz,
                                    out_buff_p, out_buff_sz,
                                    requested_buff_sz_p):
            self.assertEqual(self._clusapi.ClusterGroupControl, func)
            self.assertEqual(mock.sentinel.group_handle, group_handle)
            self.assertEqual(mock.sentinel.node_handle, node_handle)
            self.assertEqual(mock.sentinel.control_code, control_code)
            self.assertEqual(mock.sentinel.in_buff_p, in_buff_p)
            self.assertEqual(mock.sentinel.in_buff_sz, in_buff_sz)

            req_buff_sz = ctypes.cast(requested_buff_sz_p,
                                      wintypes.PDWORD).contents
            req_buff_sz.value = requested_buff_sz

            # We'll just request the tested method to pass us
            # a buffer this large.
            if (out_buff_sz.value < requested_buff_sz):
                raise exceptions.ClusterWin32Exception(
                    error_code=w_const.ERROR_MORE_DATA,
                    func_name='ClusterGroupControl',
                    error_message='error more data')

            out_buff = ctypes.cast(
                out_buff_p,
                ctypes.POINTER(
                    ctypes.c_wchar *
                    (requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
            out_buff = out_buff.contents
            out_buff.value = fake_out_buff

        self._mock_run.side_effect = fake_cluster_group_ctrl

        out_buff, out_buff_sz = self._clusapi_utils.cluster_group_control(
            mock.sentinel.group_handle, mock.sentinel.control_code,
            mock.sentinel.node_handle, mock.sentinel.in_buff_p,
            mock.sentinel.in_buff_sz)

        self.assertEqual(requested_buff_sz, out_buff_sz)
        wp_out_buff = ctypes.cast(
            out_buff, ctypes.POINTER(ctypes.c_wchar * requested_buff_sz))
        self.assertEqual(fake_out_buff,
                         wp_out_buff.contents[:len(fake_out_buff)])

    def test_get_cluster_group_status_info(self):
        prop_list = self._get_fake_prop_list()

        status_info = self._clusapi_utils.get_cluster_group_status_info(
            ctypes.byref(prop_list), ctypes.sizeof(prop_list))
        self.assertEqual(w_const.CLUSGRP_STATUS_WAITING_IN_QUEUE_FOR_MOVE,
                         status_info)