-
Notifications
You must be signed in to change notification settings - Fork 96
adding API and tests for IOCTL requests (and bypassing casadm) #920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # | ||
| # Copyright(c) 2021 Intel Corporation | ||
| # SPDX-License-Identifier: BSD-3-Clause-Clear | ||
| # | ||
|
|
||
| from api.cas.ioctl.cas_structs import * | ||
| from api.cas.ioctl.ioctl import IOWR | ||
|
|
||
|
|
||
| class IORequest: | ||
| def __init__(self, | ||
| command_number: RequestCode, | ||
| command_direction=None): | ||
| self.command_number = command_number.value | ||
| self.command_struct = self.get_struct() | ||
| self.command = command_direction(self.command_number, self.command_struct) | ||
|
|
||
| def get_struct(self): | ||
| pass | ||
|
|
||
|
|
||
| class StartCacheRequest(IORequest): | ||
| def __init__(self, | ||
| cache_path_name: str, | ||
| cache_id: int = 1, | ||
| init_cache: InitCache = InitCache.CACHE_INIT_NEW, | ||
| caching_mode: CacheMode = CacheMode.default, | ||
| line_size: CacheLineSize = CacheLineSize.default, | ||
| force: int = 1): | ||
| self.cache_id = ctypes.c_uint16(cache_id).value | ||
| self.init_cache = init_cache.value | ||
| self.cache_path_name = ctypes.create_string_buffer( | ||
| bytes(cache_path_name, encoding='ascii'), MAX_STR_LEN).value | ||
| self.caching_mode = caching_mode.value | ||
| self.line_size = line_size.value | ||
| self.force = ctypes.c_uint8(force).value | ||
| super().__init__(RequestCode.START_CACHE_CODE, IOWR) | ||
|
|
||
| def get_struct(self): | ||
| return StartCacheStructure( | ||
| cache_id=self.cache_id, | ||
| init_cache=self.init_cache, | ||
| cache_path_name=self.cache_path_name, | ||
| caching_mode=self.caching_mode, | ||
| line_size=self.line_size, | ||
| force=self.force | ||
| ) | ||
|
|
||
| def __repr__(self): | ||
| return f'{self.command_struct}' | ||
|
|
||
|
|
||
| class StopCacheRequest(IORequest): | ||
| def __init__(self, | ||
| cache_id: int = 1, | ||
| flush_data: int = 1): | ||
| self.cache_id = ctypes.c_uint16(cache_id).value | ||
| self.flush_data = ctypes.c_uint8(flush_data).value | ||
| super().__init__(RequestCode.STOP_CACHE_CODE, IOWR) | ||
|
|
||
| def get_struct(self): | ||
| return StopCacheStructure( | ||
| cache_id=self.cache_id, | ||
| flush_data=self.flush_data | ||
| ) | ||
|
|
||
| def __repr__(self): | ||
| return f'{self.command_struct}' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| # | ||
| # Copyright(c) 2021 Intel Corporation | ||
| # SPDX-License-Identifier: BSD-3-Clause-Clear | ||
| # | ||
|
|
||
| import ctypes | ||
| from enum import Enum | ||
|
|
||
|
|
||
| class RequestCode(Enum): | ||
| START_CACHE_CODE = ctypes.c_uint(21).value | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this construct do? I just checked that |
||
| STOP_CACHE_CODE = ctypes.c_uint(2).value | ||
| SET_CACHE_STATE_CODE = ctypes.c_uint(3).value | ||
| INSERT_CORE_CODE = ctypes.c_uint(22).value | ||
| REMOVE_CORE_CODE = ctypes.c_uint(23).value | ||
| RESET_STATS_CODE = ctypes.c_uint(6).value | ||
| FLUSH_CACHE_CODE = ctypes.c_uint(9).value | ||
| INTERRUPT_FLUSHING_CODE = ctypes.c_uint(10).value | ||
| FLUSH_CORE_CODE = ctypes.c_uint(11).value | ||
| CACHE_INFO_CODE = ctypes.c_uint(24).value | ||
| CORE_INFO_CODE = ctypes.c_uint(25).value | ||
| PARTITION_INFO_CODE = ctypes.c_uint(14).value | ||
| PARTITION_SET_CODE = ctypes.c_uint(15).value | ||
| GET_CACHE_COUNT_CODE = ctypes.c_uint(16).value | ||
| LIST_CACHE_CODE = ctypes.c_uint(17).value | ||
| UPGRADE_CODE = ctypes.c_uint(19).value | ||
| GET_CORE_POOL_COUNT_CODE = ctypes.c_uint(26).value | ||
| GET_CORE_POOL_PATHS_CODE = ctypes.c_uint(27).value | ||
| CORE_POOL_REMOVE_CODE = ctypes.c_uint(28).value | ||
| CACHE_CHECK_DEVICE_CODE = ctypes.c_uint(29).value | ||
| SET_CORE_PARAM_CODE = ctypes.c_uint(30).value | ||
| GET_CORE_PARAM_CODE = ctypes.c_uint(31).value | ||
| SET_CACHE_PARAM_CODE = ctypes.c_uint(32).value | ||
| GET_CACHE_PARAM_CODE = ctypes.c_uint(33).value | ||
| GET_STATS_CODE = ctypes.c_uint(34).value | ||
| PURGE_CACHE_CODE = ctypes.c_uint(35).value | ||
| PURGE_CORE_CODE = ctypes.c_uint(36).value | ||
|
|
||
|
|
||
| KiB = ctypes.c_ulonglong(1024).value | ||
| MAX_STR_LEN = 4096 | ||
| MAX_ELEVATOR_NAME = 16 | ||
|
|
||
|
|
||
| class InitCache(Enum): | ||
| CACHE_INIT_NEW = ctypes.c_uint8(0).value | ||
| CACHE_INIT_LOAD = ctypes.c_uint8(1).value | ||
|
|
||
|
|
||
| class CacheMode(Enum): | ||
| ocf_cache_mode_wt = ctypes.c_int(0).value | ||
| ocf_cache_mode_wb = ctypes.c_int(1).value | ||
| ocf_cache_mode_wa = ctypes.c_int(2).value | ||
| ocf_cache_mode_pt = ctypes.c_int(3).value | ||
| ocf_cache_mode_wi = ctypes.c_int(4).value | ||
| ocf_cache_mode_wo = ctypes.c_int(5).value | ||
| default = ocf_cache_mode_wt | ||
|
|
||
|
|
||
| class CacheLineSize(Enum): | ||
| ocf_cache_line_size_4 = ctypes.c_ulonglong(4).value * KiB | ||
| ocf_cache_line_size_8 = ctypes.c_ulonglong(8).value * KiB | ||
| ocf_cache_line_size16 = ctypes.c_ulonglong(16).value * KiB | ||
| ocf_cache_line_size_32 = ctypes.c_ulonglong(32).value * KiB | ||
| ocf_cache_line_size_64 = ctypes.c_ulonglong(64).value * KiB | ||
| default = ocf_cache_line_size_4 | ||
|
Comment on lines
+60
to
+66
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we use class |
||
|
|
||
|
|
||
| class StartCacheStructure(ctypes.Structure): | ||
| _fields_ = [ | ||
| ('cache_id', ctypes.c_uint16), | ||
| ('init_cache', ctypes.c_uint8), | ||
| ('cache_path_name', ctypes.c_char * MAX_STR_LEN), | ||
| ('caching_mode', ctypes.c_int), | ||
| ('flush_data', ctypes.c_uint8), | ||
| ('line_size', ctypes.c_ulonglong), | ||
| ('force', ctypes.c_uint8), | ||
| ('min_free_ram', ctypes.c_uint64), | ||
| ('metadata_mode_optimal', ctypes.c_uint8), | ||
| ('cache_elevator', ctypes.c_char * MAX_ELEVATOR_NAME), | ||
| ('ext_err_code', ctypes.c_int) | ||
| ] | ||
|
|
||
| def __repr__(self): | ||
| return (f'cache_id: {self.cache_id}\n' | ||
| f'init_cache: {self.init_cache}\n' | ||
| f'cache_path_name: {self.cache_path_name}\n' | ||
| f'caching_mode: {self.caching_mode}\n' | ||
| f'flush_data: {self.flush_data}\n' | ||
| f'line_size: {self.line_size}\n' | ||
| f'force: {self.force}\n' | ||
| f'min_free_ram: {self.min_free_ram}\n' | ||
| f'metadata_mode_optimal: {self.metadata_mode_optimal}\n' | ||
| f'cache_elevator: {self.cache_elevator}\n' | ||
| f'ext_err_code: {self.ext_err_code}\n') | ||
|
|
||
|
|
||
| class StopCacheStructure(ctypes.Structure): | ||
| _fields_ = [ | ||
| ('cache_id', ctypes.c_uint16), | ||
| ('flush_data', ctypes.c_uint8), | ||
| ('ext_err_code', ctypes.c_int) | ||
| ] | ||
|
|
||
| def __repr__(self): | ||
| return (f'cache_id: {self.cache_id}\n' | ||
| f'flush_data: {self.flush_data}\n' | ||
| f'ext_err_code: {self.ext_err_code}\n') | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # | ||
| # Copyright(c) 2021 Intel Corporation | ||
| # SPDX-License-Identifier: BSD-3-Clause-Clear | ||
| # | ||
|
|
||
| import ctypes | ||
| import marshal | ||
| import os | ||
| from time import sleep | ||
|
|
||
| from core.test_run import TestRun | ||
| from test_tools.fs_utils import chmod_numerical, remove, check_if_directory_exists, \ | ||
| create_directory | ||
|
|
||
| IOC_NRBITS = 8 | ||
| IOC_TYPEBITS = 8 | ||
| IOC_SIZEBITS = 14 | ||
| IOC_DIRBITS = 2 | ||
|
|
||
| IOC_NRMASK = (1 << IOC_NRBITS) - 1 # 255 | ||
| IOC_TYPEMASK = (1 << IOC_TYPEBITS) - 1 # 255 | ||
| IOC_SIZEMASK = (1 << IOC_SIZEBITS) - 1 # 16 383 | ||
| IOC_DIRMASK = (1 << IOC_DIRBITS) - 1 # 3 | ||
|
|
||
| IOC_NRSHIFT = 0 # 0 | ||
| IOC_TYPESHIFT = IOC_NRSHIFT + IOC_NRBITS # 8 | ||
| IOC_SIZESHIFT = IOC_TYPESHIFT + IOC_TYPEBITS # 16 | ||
| IOC_DIRSHIFT = IOC_SIZESHIFT + IOC_SIZEBITS # 30 | ||
|
|
||
| IOC_NONE = 0 | ||
| IOC_WRITE = 1 | ||
| IOC_READ = 2 | ||
|
|
||
| KCAS_IOCTL_MAGIC = 0xBA # 186 | ||
|
|
||
| IOC_IN = IOC_WRITE << IOC_DIRSHIFT # 1 073 741 824 | ||
| IOC_OUT = IOC_READ << IOC_DIRSHIFT # 2 147 483 648 | ||
| IOC_INOUT = (IOC_WRITE | IOC_READ) << IOC_DIRSHIFT # 3 221 225 472 | ||
| IOCSIZE_MASK = IOC_SIZEMASK << IOC_SIZESHIFT # 1 073 676 288 | ||
| IOCSIZE_SHIFT = IOC_SIZESHIFT # 16 | ||
|
|
||
|
|
||
| def IOC(dir, type, nr, size): | ||
| if dir > IOC_DIRMASK: | ||
| raise OverflowError(f"IO direction value {dir} exceeds {IOC_DIRMASK}") | ||
| dir <<= IOC_DIRSHIFT | ||
|
|
||
| if type > IOC_TYPEMASK: | ||
| raise OverflowError(f"IO type value {type} exceeds {IOC_TYPEMASK}") | ||
| type <<= IOC_TYPESHIFT | ||
|
|
||
| if nr > IOC_NRMASK: | ||
| raise OverflowError(f"IO command value {nr} exceeds {IOC_NRMASK}") | ||
| nr <<= IOC_NRSHIFT | ||
|
|
||
| if size > IOC_SIZEMASK: | ||
| raise OverflowError(f"IO size value {size} exceeds {IOC_SIZEMASK}") | ||
| size <<= IOC_SIZESHIFT | ||
|
|
||
| return dir | type | nr | size | ||
|
Comment on lines
+15
to
+60
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are libs for that https://pypi.org/project/ioctl-opt/ |
||
|
|
||
|
|
||
| def IOC_TYPECHECK(item): | ||
| return ctypes.sizeof(item) | ||
|
|
||
|
|
||
| def IO(nr): | ||
| return IOC(IOC_NONE, KCAS_IOCTL_MAGIC, nr, 0) | ||
|
|
||
|
|
||
| def IOR(nr, size): | ||
| return IOC(IOC_READ, KCAS_IOCTL_MAGIC, nr, IOC_TYPECHECK(size)) | ||
|
|
||
|
|
||
| def IOW(nr, size): | ||
| return IOC(IOC_WRITE, KCAS_IOCTL_MAGIC, nr, IOC_TYPECHECK(size)) | ||
|
|
||
|
|
||
| def IOWR(nr, size): | ||
| return IOC(IOC_READ | IOC_WRITE, KCAS_IOCTL_MAGIC, nr, IOC_TYPECHECK(size)) | ||
|
|
||
|
|
||
| def IOR_BAD(nr, size): | ||
| return IOC(IOC_READ, KCAS_IOCTL_MAGIC, nr, ctypes.sizeof(size)) | ||
|
|
||
|
|
||
| def IOW_BAD(nr, size): | ||
| return IOC(IOC_WRITE, KCAS_IOCTL_MAGIC, nr, ctypes.sizeof(size)) | ||
|
|
||
|
|
||
| def IOWR_BAD(nr, size): | ||
| return IOC(IOC_READ | IOC_WRITE, KCAS_IOCTL_MAGIC, nr, ctypes.sizeof(size)) | ||
|
|
||
|
|
||
| def IOC_DIR(nr): | ||
| return (nr >> IOC_DIRSHIFT) & IOC_DIRMASK | ||
|
|
||
|
|
||
| def IOC_TYPE(nr): | ||
| return (nr >> IOC_TYPESHIFT) & IOC_TYPEMASK | ||
|
|
||
|
|
||
| def IOC_NR(nr): | ||
| return (nr >> IOC_NRSHIFT) & IOC_NRMASK | ||
|
|
||
|
|
||
| def IOC_SIZE(nr): | ||
| return (nr >> IOC_SIZESHIFT) & IOC_SIZEMASK | ||
|
|
||
|
|
||
| temp_dir = '/tmp/cas' | ||
| struct_path = os.path.join(temp_dir, 'dump_file') | ||
| script_source = os.path.join(f'{os.path.dirname(__file__)}', 'send_ioctl_script.py') | ||
| script_dest = os.path.join(temp_dir, 'send_ioctl_script.py') | ||
|
Comment on lines
+111
to
+114
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. struct_path and script_source and local (so os.path is ok), but script_dest is remote (so Linux - posixpath required) |
||
|
|
||
|
|
||
| def send_script_with_dumped_args(): | ||
| if not check_if_directory_exists(temp_dir): | ||
| create_directory(temp_dir, True) | ||
|
|
||
| TestRun.executor.rsync_to(script_source, script_dest) | ||
| chmod_numerical(script_dest, 550) | ||
|
|
||
| TestRun.executor.rsync_to(struct_path, struct_path) | ||
| chmod_numerical(struct_path, 440) | ||
|
Comment on lines
+117
to
+125
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We finally have DUT-side scripting! ❤️
Comment on lines
+121
to
+125
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rsync to be replaced |
||
|
|
||
|
|
||
| def cas_ioctl(cas_ioctl_request, interrupt: bool = False): | ||
| if not os.path.exists(temp_dir): | ||
| os.mkdir(temp_dir) | ||
|
|
||
| with open(struct_path, 'wb') as dump_file: | ||
| marshal.dump(cas_ioctl_request.command_struct, dump_file) | ||
|
|
||
| send_script_with_dumped_args() | ||
| if interrupt: | ||
| pid = TestRun.executor.run_in_background( | ||
| f"{script_dest} -c {cas_ioctl_request.command} -s {struct_path}" | ||
| ) | ||
| sleep(2) | ||
| TestRun.executor.kill_process(pid) | ||
|
Comment on lines
+140
to
+141
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should at least take timeout as an argument. Otherwise it's unusable with commands like cache flush, which typically takes much longer than 2 seconds. |
||
| else: | ||
| TestRun.executor.run(f"{script_dest} -c {cas_ioctl_request.command} -s {struct_path}") | ||
| if check_if_directory_exists(temp_dir): | ||
| remove(temp_dir, True, True, True) | ||
| if os.path.exists(struct_path): | ||
| os.remove(struct_path) | ||
|
Comment on lines
+144
to
+147
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Virtually all ioctls handled by CAS return information from kernel via ioctl structure, which means we want to have a way to receive this information back from DUT once ioctl is finished. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| #!/usr/bin/env python3 | ||
| # | ||
| # Copyright(c) 2021 Intel Corporation | ||
| # SPDX-License-Identifier: BSD-3-Clause-Clear | ||
| # | ||
|
|
||
| import argparse | ||
| import marshal | ||
| import os | ||
| import sys | ||
| from fcntl import ioctl | ||
|
|
||
|
|
||
| def main(): | ||
| CAS_DEVICE = '/dev/cas_ctrl' | ||
|
|
||
| parser = argparse.ArgumentParser(description=f'Send ioctl request to {CAS_DEVICE}') | ||
|
|
||
| parser.add_argument('-c', '--command', action='store', dest='command', type=str, | ||
| required=True, help=f"Specific request code to send to {CAS_DEVICE}") | ||
| parser.add_argument('-s', '--struct', action='store', dest='struct', type=str, | ||
| required=True, help="Structure of CAS request") | ||
| args = parser.parse_args() | ||
|
|
||
| with open(args.struct, 'rb') as struct_file: | ||
| struct = bytearray(marshal.load(struct_file)) | ||
|
|
||
| fd = os.open(CAS_DEVICE, os.O_RDWR) | ||
| try: | ||
| ioctl(fd, int(args.command), struct) | ||
| except OSError as err: | ||
| print(f"IOCTL request returned error: {err}", sys.stdout) | ||
| finally: | ||
| os.close(fd) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| main() |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. empty |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # | ||
| # Copyright(c) 2021 Intel Corporation | ||
| # SPDX-License-Identifier: BSD-3-Clause-Clear | ||
| # | ||
|
|
||
| from api.cas.cli_messages import __check_string_msg | ||
| from core.test_run import TestRun | ||
|
|
||
|
|
||
| interrupt_stop = [ | ||
| r"Waiting for cache stop interrupted\. Stop will finish asynchronously\." | ||
| ] | ||
|
|
||
| interrupt_start = [ | ||
| r"Cache added successfully, but waiting interrupted\. Rollback" | ||
| ] | ||
|
|
||
| load_and_force = [ | ||
| r"cache\d+: Using \'force\' flag is forbidden for load operation\." | ||
| ] | ||
|
Comment on lines
+10
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think those messages should be in this file. Maybe more appropriate place would be in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or keep separate |
||
|
|
||
|
|
||
| def clear_dmesg(): | ||
| TestRun.executor.run_expect_success('dmesg -C') | ||
|
|
||
|
|
||
| def check_dmesg(searched_phrase: str): | ||
| dmesg_out = TestRun.executor.run_expect_success("dmesg").stdout | ||
| __check_string_msg(dmesg_out, searched_phrase) | ||
|
Comment on lines
+23
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those as well, I think clearing/checking dmesg is more generic operation, not limited only to ioctl tests. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so empty