From 93367be03d5bc869c303b863db5767450634b760 Mon Sep 17 00:00:00 2001 From: Piotr Debski Date: Tue, 11 Jan 2022 15:16:38 +0100 Subject: [PATCH 1/2] JSON API Signed-off-by: Piotr Debski --- configure | 12 ++ json/Makefile | 24 ++++ json/api/compose_data.go | 173 +++++++++++++++++++++++++ json/api/error.go | 65 ++++++++++ json/api/json_api.go | 228 +++++++++++++++++++++++++++++++++ json/api/request.go | 206 ++++++++++++++++++++++++++++++ json/go.mod | 8 ++ json/ioctl/ioctl.go | 96 ++++++++++++++ json/ioctl/json_structs.go | 150 ++++++++++++++++++++++ json/ioctl/ocf_types.go | 25 ++++ json/ioctl/structs_conv.go | 255 +++++++++++++++++++++++++++++++++++++ json/main.go | 16 +++ json/request.json | 6 + 13 files changed, 1264 insertions(+) create mode 100644 json/Makefile create mode 100644 json/api/compose_data.go create mode 100644 json/api/error.go create mode 100644 json/api/json_api.go create mode 100644 json/api/request.go create mode 100644 json/go.mod create mode 100644 json/ioctl/ioctl.go create mode 100644 json/ioctl/json_structs.go create mode 100644 json/ioctl/ocf_types.go create mode 100644 json/ioctl/structs_conv.go create mode 100644 json/main.go create mode 100644 json/request.json diff --git a/configure b/configure index cb7e7762b..9e5723d48 100755 --- a/configure +++ b/configure @@ -139,6 +139,18 @@ generate_header() { done } +while (( $# )); do + case "$1" in + --with-json-api) + if ! grep json Makefile >/dev/null; then + sed -i '/^DIRS/ s/$/ json/' Makefile + fi + ;; + esac + shift +done + + if [ -z "$1" ]; then generate_config else diff --git a/json/Makefile b/json/Makefile new file mode 100644 index 000000000..591562262 --- /dev/null +++ b/json/Makefile @@ -0,0 +1,24 @@ +# +# Copyright(c) 2012-2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# + +include ../tools/helpers.mk + +BINARY_PATH = /sbin + +all: + @mkdir -p bin + @go build -o bin/opencas-json-api main.go +install: + @echo " Installing JSON API " + @install -m 755 bin/opencas-json-api $(DESTDIR)/sbin/opencas-json-api + +uninstall: + @echo " Uninstalling JSON API " + @$(call remove-file,$(DESTDIR)/sbin/opencas-json-api) + +clean: + @rm -f bin/opencas-json-api + +.PHONY: all clean install uninstall diff --git a/json/api/compose_data.go b/json/api/compose_data.go new file mode 100644 index 000000000..724586f0a --- /dev/null +++ b/json/api/compose_data.go @@ -0,0 +1,173 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package api + +import ( + "os" + + "github.com/Open-CAS/open-cas-linux/json/ioctl" +) + +/** packages of JSON marshalable structs with packed CAS info for easy use in RESTful API */ + +type Cache_with_cores struct { + // default value of ptr is nil + Cache_info *ioctl.Kcas_cache_info `json:"Cache"` + Cores_info []*ioctl.Kcas_core_info `json:"Cores"` +} + +type Cache_list_pkg []Cache_with_cores + +/** Retriving caches and their cores info from CAS */ + +func List_caches() (Cache_list_pkg, error) { + var cache_list_pkg Cache_list_pkg + + /** retive list of caches id from ioclt */ + cache_id_list, ioctl_err := get_cache_id_list() + + /** early log and return in case of cache_list_id retrieve error + which is needed in further caches iteration for ioctl calls */ + if check_err(ioctl_err) { + log_console(ioctl_err.Error()) + return cache_list_pkg, ioctl_err + } + + /** create array of caches with list of their cores */ + cache_id_list_len := len(cache_id_list) + cache_list_pkg = make([]Cache_with_cores, cache_id_list_len) + + for cache_idx, cache_id := range cache_id_list { + /** retrieve cache info */ + cache_info, ioctl_err := get_cache_info(cache_id) + + /** in case of get_cache_info error, log error and skip adding that cache and it's cores to package */ + if check_err(ioctl_err) { + log_console(ioctl_err.Error()) + return cache_list_pkg, ioctl_err + } + + /** create array of cores for each cache */ + cores_id_list_len := len(cache_info.Attached_cores) + cache_list_pkg[cache_idx].Cores_info = make([]*ioctl.Kcas_core_info, cores_id_list_len) + + /** fill array of caches & their cores with retrieved cache info */ + cache_list_pkg[cache_idx].Cache_info = &cache_info + + for core_idx, core_id := range cache_info.Attached_cores { + /** retrieve core info for table of cores assigned to cache */ + core_info, ioctl_err := get_core_info(cache_id, core_id) + + /** in case of get_core_info error, log error and skip adding that core to package */ + if check_err(ioctl_err) { + log_console(ioctl_err.Error()) + return cache_list_pkg, ioctl_err + } + + /** fill array of cores with retrieved core info */ + cache_list_pkg[cache_idx].Cores_info[core_idx] = &core_info + } + } + /** return array of caches with their cores list and no error */ + return cache_list_pkg, nil +} + +/** retriving CAS structures with ioctl syscalls and converting them into Go Marshalable structs */ + +/** retrieves C cache info struct with ioctl syscall and converts it into Golang cache info struct +also provides errno type error into standard golang error conversion */ +func get_cache_info(cache_id uint16) (ioctl.Kcas_cache_info, error) { + /** retrieve file descriptor of exported object cas_ctrl + and close with defer after completing all tasks */ + cas_ctrl, fd_err := os.Open(ioctl.Cas_ctrl_path) + if check_err(fd_err) { + log_console("Cannot open device exclusively") + return ioctl.Kcas_cache_info{}, fd_err + } + defer cas_ctrl.Close() + + C_cache_info, ioctl_err := ioctl.Ioctl_cache_info(cas_ctrl.Fd(), cache_id) + cache_info := ioctl.Conv_cache_info(&C_cache_info) + ioctl_err = errno_to_error(ioctl_err) + return cache_info, ioctl_err +} + +/** retrieves C core info struct with ioctl syscall and converts it into Golang core info struct +also provides errno type error into standard golang error conversion */ +func get_core_info(cache_id, core_id uint16) (ioctl.Kcas_core_info, error) { + /** retrieve file descriptor of exported object cas_ctrl + and close with defer after completing all tasks */ + cas_ctrl, fd_err := os.Open(ioctl.Cas_ctrl_path) + if check_err(fd_err) { + log_console("Cannot open device exclusively") + return ioctl.Kcas_core_info{}, fd_err + } + defer cas_ctrl.Close() + + C_core_info, ioctl_err := ioctl.Ioctl_core_info(cas_ctrl.Fd(), cache_id, core_id) + core_info := ioctl.Conv_core_info(&C_core_info) + ioctl_err = errno_to_error(ioctl_err) + return core_info, ioctl_err +} + +/** retrieves C io class struct with ioctl syscall and converts it into Golang io class struct +also provides errno type error into standard golang error conversion */ +func get_io_class(cache_id, io_class_id uint16) (ioctl.Kcas_io_class, error) { + /** retrieves file descriptor of exported object cas_ctrl + and close with defer after completing all tasks */ + cas_ctrl, fd_err := os.Open(ioctl.Cas_ctrl_path) + if check_err(fd_err) { + log_console("Cannot open device exclusively") + return ioctl.Kcas_io_class{}, fd_err + } + defer cas_ctrl.Close() + + C_io_class, ioctl_err := ioctl.Ioctl_io_class(cas_ctrl.Fd(), cache_id, io_class_id) + io_class := ioctl.Conv_io_class(&C_io_class) + ioctl_err = errno_to_error(ioctl_err) + return io_class, ioctl_err +} + +/** retrieves C statistics struct with ioctl syscall and converts it into Golang statistics struct +also provides errno type error into standard golang error conversion */ +func get_stats(cache_id, core_id, io_class_id uint16) (ioctl.Kcas_get_stats, error) { + /** retrieve file descriptor of exported object cas_ctrl + and close with defer after completing all tasks */ + cas_ctrl, fd_err := os.Open(ioctl.Cas_ctrl_path) + if check_err(fd_err) { + log_console("Cannot open device exclusively") + return ioctl.Kcas_get_stats{}, fd_err + } + defer cas_ctrl.Close() + + C_stats, ioctl_err := ioctl.Ioctl_get_kcas_stats(cas_ctrl.Fd(), cache_id, core_id, io_class_id) + stats := ioctl.Conv_stats(&C_stats) + ioctl_err = errno_to_error(ioctl_err) + return stats, ioctl_err +} + +/** retrieves C list of cache id's with ioctl syscall and converts it into Golang slice of cache id's +also provides errno type error into standard golang error conversion */ +func get_cache_id_list() ([]uint16, error) { + /** retrieves file descriptor of exported object cas_ctrl + and close with defer after completing all tasks */ + cas_ctrl, fd_err := os.Open(ioctl.Cas_ctrl_path) + if check_err(fd_err) { + log_console("Cannot open device exclusively") + return []uint16{}, fd_err + } + defer cas_ctrl.Close() + + C_cache_list, ioctl_err := ioctl.Ioctl_list_cache(cas_ctrl.Fd()) + cache_list := ioctl.Conv_cache_id_list(&C_cache_list) + ioctl_err = errno_to_error(ioctl_err) + return cache_list, ioctl_err +} + +/** validate file descriptor */ +func valid_fd(fd uintptr) bool { + return fd > 0 +} diff --git a/json/api/error.go b/json/api/error.go new file mode 100644 index 000000000..57c0f1600 --- /dev/null +++ b/json/api/error.go @@ -0,0 +1,65 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package api + +import "C" +import ( + "encoding/json" + "fmt" +) + +/** error checking and logging system */ +var err_sys Error_sys + +type Error_sys struct { + Log_error_list map[string]string `json:"error log"` + stack_error_msg []string +} + +/** Error_sys constructor */ +func NewError_sys(err_sys Error_sys) *Error_sys { + err_sys.Log_error_list = make(map[string]string) + return &err_sys +} + +/** marshal error log to json and transfer to stdout */ +func (err_sys *Error_sys) write_errors_to_console() { + Log_error_list_b, marshall_err := json.MarshalIndent(err_sys.Log_error_list, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON error response") + return + } + fmt.Println(string(Log_error_list_b)) +} + +/** check whether error occured and log it's occurance */ +func check_err(err error) bool { + if err != nil { + return true + } + return false +} + +/** logs errors to console */ +func log_console(msg string) { + if err_sys.Log_error_list == nil { + err_sys.Log_error_list = make(map[string]string) + err_sys.stack_error_msg = make([]string, 1) + err_sys.Log_error_list["error"] = msg + } + err_sys.stack_error_msg = append(err_sys.stack_error_msg, msg) +} + +/** error of errno type even if everything went write is not nil and have message "errno 0" so we check that */ +func errno_to_error(err error) error { + if err == nil { + return nil + } + if err.Error() == "errno 0" { + return nil + } + return err +} diff --git a/json/api/json_api.go b/json/api/json_api.go new file mode 100644 index 000000000..178614d78 --- /dev/null +++ b/json/api/json_api.go @@ -0,0 +1,228 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package api + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/Jeffail/gabs" + "github.com/Open-CAS/open-cas-linux/json/ioctl" +) + +func Json_api() { + + var buffer interface{} + decode_header_err := json.NewDecoder(os.Stdin).Decode(&buffer) + if check_err(decode_header_err) { + log_console("Invalid input parameter") + err_sys.write_errors_to_console() + return + } + strB, _ := json.Marshal(buffer) + + jsonParsed, err := gabs.ParseJSON(strB) + if check_err(err) { + log_console("Invalid input parameter") + err_sys.write_errors_to_console() + return + } + + command, ok := jsonParsed.Path("command").Data().(string) + if !ok { + log_console("Invalid input parameter") + err_sys.write_errors_to_console() + return + } + + /** Comand interpretation section and retrieving json response */ + + if command == "opencas.cache.stats.get" { + // it appears as float64 is default format to read json in this package + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + params := CreateParamsStatsCache(uint16(cache_id)) + json_get_stats_cache(*params) + } + + if command == "opencas.cache.core.stats.get" { + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + core_id, _ := jsonParsed.Search("params", "core id").Data().(float64) + params := CreateParamsStatsCore(uint16(cache_id), uint16(core_id)) + json_get_stats_core(*params) + } + + if command == "opencas.cache.ioclass.stats.get" { + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + io_class, _ := jsonParsed.Search("params", "io class").Data().(float64) + params := CreateParamsStatsIoClass(uint16(cache_id), uint16(io_class)) + json_get_stats_io_class(*params) + } + + if command == "opencas.cache.core.ioclass.stats.get" { + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + core_id, _ := jsonParsed.Search("params", "core id").Data().(float64) + io_class, _ := jsonParsed.Search("params", "io class").Data().(float64) + params := CreateParamsStats(uint16(cache_id), uint16(core_id), uint16(io_class)) + json_get_stats(*params) + } + + if command == "opencas.cache_list.get" { + json_list_caches() + } + + if command == "opencas.cache.info.get" { + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + params := CreateParamsCacheInfo(uint16(cache_id)) + json_get_cache_info(*params) + } + + if command == "opencas.cache.core.info.get" { + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + core_id, _ := jsonParsed.Search("params", "core id").Data().(float64) + params := CreateParamsCoreInfo(uint16(cache_id), uint16(core_id)) + json_get_core_info(*params) + } + + if command == "opencas.cache.ioclass.info.get" { + cache_id, _ := jsonParsed.Search("params", "cache id").Data().(float64) + io_class, _ := jsonParsed.Search("params", "io class").Data().(float64) + params := CreateParamsIoclassInfo(uint16(cache_id), uint16(io_class)) + json_get_io_class_info(*params) + } + + /** if some errors occured - error log is not empty write errors list */ + if len(err_sys.Log_error_list) != 0 { + err_sys.write_errors_to_console() + } + +} + +/** json get stats with different levels */ + +func json_get_stats(request Params_stats) { + stats_pkg, get_stats_err := get_stats(request.Cache_id, request.Core_id, request.Io_class) + if check_err(get_stats_err) { + log_console(get_stats_err.Error()) + return + } + + stats_pkg_b, marshall_err := json.MarshalIndent(stats_pkg, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(stats_pkg_b)) +} + +func json_get_stats_cache(request Params_stats_cache) { + stats_pkg, get_stats_err := get_stats(request.Cache_id, ioctl.Invalid_core_id, ioctl.Invalid_io_class) + if check_err(get_stats_err) { + log_console(get_stats_err.Error()) + return + } + + stats_pkg_b, marshall_err := json.MarshalIndent(stats_pkg, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(stats_pkg_b)) +} + +func json_get_stats_core(request Params_stats_core) { + stats_pkg, get_stats_err := get_stats(request.Cache_id, request.Core_id, ioctl.Invalid_io_class) + if check_err(get_stats_err) { + log_console(get_stats_err.Error()) + return + } + + stats_pkg_b, marshall_err := json.MarshalIndent(stats_pkg, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(stats_pkg_b)) +} + +func json_get_stats_io_class(request Params_stats_io_class) { + stats_pkg, get_stats_err := get_stats(request.Cache_id, ioctl.Invalid_core_id, request.Io_class) + if check_err(get_stats_err) { + log_console(get_stats_err.Error()) + return + } + + stats_pkg_b, marshall_err := json.MarshalIndent(stats_pkg, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(stats_pkg_b)) +} + +/** json get cache_list */ + +func json_list_caches() { + cache_list_pkg, list_caches_err := List_caches() + if check_err(list_caches_err) { + log_console(list_caches_err.Error()) + return + } + + cache_list_pkg_b, marshall_err := json.MarshalIndent(cache_list_pkg, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(cache_list_pkg_b)) +} + +/** json get CAS with different levels */ + +func json_get_cache_info(request Params_cache_info) { + cache_info, cache_info_err := get_cache_info(request.Cache_id) + if check_err(cache_info_err) { + log_console(cache_info_err.Error()) + return + } + + cache_info_b, marshall_err := json.MarshalIndent(cache_info, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(cache_info_b)) +} + +func json_get_core_info(request Params_core_info) { + core_info, core_info_err := get_core_info(request.Cache_id, request.Core_id) + if check_err(core_info_err) { + log_console(core_info_err.Error()) + return + } + + core_info_b, marshall_err := json.MarshalIndent(core_info, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(core_info_b)) +} + +func json_get_io_class_info(request Params_io_class_info) { + io_class_info, io_class_info_err := get_io_class(request.Cache_id, request.Io_class) + if check_err(io_class_info_err) { + log_console(io_class_info_err.Error()) + return + } + + io_class_info_b, marshall_err := json.MarshalIndent(io_class_info, "", " ") + if check_err(marshall_err) { + log_console("failed marshaling JSON response") + return + } + fmt.Println(string(io_class_info_b)) +} diff --git a/json/api/request.go b/json/api/request.go new file mode 100644 index 000000000..e2146fca9 --- /dev/null +++ b/json/api/request.go @@ -0,0 +1,206 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package api + +import ( + "encoding/json" + "errors" + "os" +) + +/** Request structures */ + +type Request struct { + Command string `json:"command"` + Rparams Params `json:"params"` +} + +/** parameters to varius request types*/ + +type Params interface { + Read() +} + +/** most specyfic stats for concrete core with specyfied io class*/ +type Params_stats struct { + Cache_id uint16 `json:"cache id"` // obligatory + Core_id uint16 `json:"core id"` // obligatory + Io_class uint16 `json:"io class"` // obligatory +} + +/** cache level stats */ +type Params_stats_cache struct { + Cache_id uint16 `json:"cache id"` // obligatory +} + +/** core level stats */ +type Params_stats_core struct { + Cache_id uint16 `json:"cache id"` // obligatory + Core_id uint16 `json:"core id"` // obligatory + +} + +/** io class level stats */ +type Params_stats_io_class struct { + Cache_id uint16 `json:"cache id"` // obligatory + Io_class uint16 `json:"io class"` // obligatory +} + +type Params_list_caches struct { +} + +type Params_cache_info struct { + Cache_id uint16 `json:"cache id"` // obligatory +} + +type Params_core_info struct { + Cache_id uint16 `json:"cache id"` // obligatory + Core_id uint16 `json:"core id"` // obligatory +} + +type Params_io_class_info struct { + Cache_id uint16 `json:"cache id"` // obligatory + Io_class uint16 `json:"io class"` // obligatory +} + +/** Request parameters constructors */ + +func CreateParamsStats(cache_id, core_id, io_class_id uint16) *Params_stats { + return &Params_stats{ + Cache_id: cache_id, + Core_id: core_id, + Io_class: io_class_id, + } +} + +func CreateParamsStatsCache(cache_id uint16) *Params_stats_cache { + return &Params_stats_cache{ + Cache_id: cache_id, + } +} + +func CreateParamsStatsCore(cache_id, core_id uint16) *Params_stats_core { + return &Params_stats_core{ + Cache_id: cache_id, + Core_id: core_id, + } +} + +func CreateParamsStatsIoClass(cache_id, io_class_id uint16) *Params_stats_io_class { + return &Params_stats_io_class{ + Cache_id: cache_id, + Io_class: io_class_id, + } +} +func CreateParamsCacheList() *Params_list_caches { + return &Params_list_caches{} +} + +func CreateParamsCacheInfo(cache_id uint16) *Params_cache_info { + return &Params_cache_info{ + Cache_id: cache_id, + } +} + +func CreateParamsCoreInfo(cache_id, core_id uint16) *Params_core_info { + return &Params_core_info{ + Cache_id: cache_id, + Core_id: core_id, + } +} +func CreateParamsIoclassInfo(cache_id, io_class_id uint16) *Params_io_class_info { + return &Params_io_class_info{ + Cache_id: cache_id, + Io_class: io_class_id, + } +} + +/** factory design pattern for creating customized in header command parameters */ +func NewRequest(command string) (Params, error) { + if command == "opencas.cache.stats.get" { + return new(Params_stats_cache), nil + } + if command == "opencas.cache.core.stats.get" { + return new(Params_core_info), nil + } + if command == "opencas.cache.ioclass.stats.get" { + return new(Params_io_class_info), nil + } + if command == "opencas.cache.core.ioclass.stats.get" { + return new(Params_stats), nil + } + if command == "opencas.cache_list.get" { + return new(Params_list_caches), nil + } + if command == "opencas.cache.info.get" { + return new(Params_cache_info), nil + } + if command == "opencas.core.info.get" { + return new(Params_core_info), nil + } + if command == "opencas.ioclass.info.get" { + return new(Params_io_class_info), nil + } + return nil, errors.New("Invalid input parameter") +} + +/** read JSON from stdin functions */ + +func (request *Params_stats) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_stats_cache) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_stats_core) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_stats_io_class) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_list_caches) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_cache_info) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_core_info) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} + +func (request *Params_io_class_info) Read() { + decode_request_err := json.NewDecoder(os.Stdin).Decode(request) + if decode_request_err != nil { + log_console("Invalid input parameter") + } +} diff --git a/json/go.mod b/json/go.mod new file mode 100644 index 000000000..51ab08f57 --- /dev/null +++ b/json/go.mod @@ -0,0 +1,8 @@ +module github.com/Open-CAS/open-cas-linux/json + +go 1.13 + +require ( + github.com/Jeffail/gabs v1.4.0 + github.com/gin-gonic/gin v1.7.4 +) diff --git a/json/ioctl/ioctl.go b/json/ioctl/ioctl.go new file mode 100644 index 000000000..ae1a97310 --- /dev/null +++ b/json/ioctl/ioctl.go @@ -0,0 +1,96 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package ioctl + +// #cgo CFLAGS: -I./../../modules/include/ -I./../../casadm +// #include +// #include <./../../casadm/extended_err_msg.c> +// #include +import "C" +import ( + "errors" + "syscall" + "unsafe" +) + +/** CAS control device path **/ + +var Cas_ctrl_path string = "/dev/cas_ctrl" + +/** Use syscall ioctl to get get_kcas_stats structs **/ +// returned values of syscall r1, r2 skipped (register1 value, register2 value, Errno error) + +func Ioctl_get_kcas_stats(fd uintptr, cache_id, core_id, part_id uint16) (C.struct_kcas_get_stats, error) { + C_kstats := C.struct_kcas_get_stats{cache_id: C.ushort(cache_id), + core_id: C.ushort(core_id), part_id: C.ushort(part_id)} + _, _, ioctl_err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.KCAS_IOCTL_GET_STATS, + uintptr(unsafe.Pointer(&C_kstats))) + if C_kstats.ext_err_code == 0 { + ext_err := ext_err_code_to_string(int(C_kstats.ext_err_code)) + return C_kstats, ext_err + } + return C_kstats, ioctl_err +} + +/** Use syscall ioctl to get cache info struct **/ + +func Ioctl_cache_info(fd uintptr, cache_id uint16) (C.struct_kcas_cache_info, error) { + C_kcache_info := C.struct_kcas_cache_info{cache_id: C.ushort(cache_id)} + _, _, ioctl_err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.KCAS_IOCTL_CACHE_INFO, + uintptr(unsafe.Pointer(&C_kcache_info))) + if C_kcache_info.ext_err_code == 0 { + ext_err := ext_err_code_to_string(int(C_kcache_info.ext_err_code)) + return C_kcache_info, ext_err + } + return C_kcache_info, ioctl_err +} + +/** Use syscall ioctl to get core info struct **/ + +func Ioctl_core_info(fd uintptr, cache_id, core_id uint16) (C.struct_kcas_core_info, error) { + C_kcore_info := C.struct_kcas_core_info{cache_id: C.ushort(cache_id), core_id: C.ushort(core_id)} + _, _, ioctl_err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.KCAS_IOCTL_CORE_INFO, + uintptr(unsafe.Pointer(&C_kcore_info))) + if C_kcore_info.ext_err_code == 0 { + ext_err := ext_err_code_to_string(int(C_kcore_info.ext_err_code)) + return C_kcore_info, ext_err + } + return C_kcore_info, ioctl_err +} + +/** Use syscall ioctl to get io class struct **/ + +func Ioctl_io_class(fd uintptr, cache_id, class_id uint16) (C.struct_kcas_io_class, error) { + C_kio_class := C.struct_kcas_io_class{cache_id: C.ushort(cache_id), class_id: C.uint(class_id)} + _, _, ioctl_err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.KCAS_IOCTL_PARTITION_INFO, + uintptr(unsafe.Pointer(&C_kio_class))) + if C_kio_class.ext_err_code == 0 { + ext_err := ext_err_code_to_string(int(C_kio_class.ext_err_code)) + return C_kio_class, ext_err + } + return C_kio_class, ioctl_err +} + +/** Use syscall ioctl to get list of cache ids **/ + +func Ioctl_list_cache(fd uintptr) (C.struct_kcas_cache_list, error) { + C_kcache_list := C.struct_kcas_cache_list{} + _, _, ioctl_err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.KCAS_IOCTL_LIST_CACHE, + uintptr(unsafe.Pointer(&C_kcache_list))) + if C_kcache_list.ext_err_code == 0 { + ext_err := ext_err_code_to_string(int(C_kcache_list.ext_err_code)) + return C_kcache_list, ext_err + } + return C_kcache_list, ioctl_err +} + +/** maping CAS ext_err_code to Go string error messages */ +func ext_err_code_to_string(ext_err_code int) error { + if ext_err_code == 0 { + return nil + } + return errors.New(C.GoString(C.cas_strerr(C.int(ext_err_code)))) +} diff --git a/json/ioctl/json_structs.go b/json/ioctl/json_structs.go new file mode 100644 index 000000000..b5c0696d9 --- /dev/null +++ b/json/ioctl/json_structs.go @@ -0,0 +1,150 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package ioctl + +/** IOCTL_KCAS_GET_STATS structs */ + +type Kcas_get_stats struct { + Cache_id uint16 `json:"Cache id"` + Core_id uint16 `json:"Core id"` + IO_class uint16 `json:"IO class"` + Usage Ocf_stats_usage `json:"Usage"` + Req Ocf_stats_requests `json:"Requests"` + Blocks Ocf_stats_blocks `json:"Blocks"` + Errors Ocf_stats_errors `json:"Errors"` + ext_err_code int +} + +type Ocf_stats_usage struct { + Occupancy Ocf_stat `json:"Occupancy"` + Free Ocf_stat `json:"Free"` + Clean Ocf_stat `json:"Clean"` + Dirty Ocf_stat `json:"Dirty"` +} + +type Ocf_stats_requests struct { + Rd_hits Ocf_stat `json:"Read hits"` + Rd_partial_misses Ocf_stat `json:"Read partial misses"` + Rd_full_misses Ocf_stat `json:"Read full misses"` + Rd_total Ocf_stat `json:"Read total"` + Wr_hits Ocf_stat `json:"Write hits"` + Wr_partial_misses Ocf_stat `json:"Write partial misses"` + Wr_full_misses Ocf_stat `json:"Write full misses"` + Wr_total Ocf_stat `json:"Write total"` + Rd_pt Ocf_stat `json:"Pass-Trough reads"` + Wr_pt Ocf_stat `json:"Pass-Trough writes"` + Serviced Ocf_stat `json:"Serviced requests"` + Total Ocf_stat `json:"Total requests"` +} + +type Ocf_stats_blocks struct { + Core_volume_rd Ocf_stat `json:"Reads from core(s)"` + Core_volume_wr Ocf_stat `json:"Writes from core(s)"` + Core_volume_total Ocf_stat `json:"Total from/to core(s)"` + Cache_volume_rd Ocf_stat `json:"Reads from cache(s)"` + Cache_volume_wr Ocf_stat `json:"Writes from cache(s)"` + Cache_volume_total Ocf_stat `json:"Total from/to cache(s)"` + Volume_rd Ocf_stat `json:"Reads from exported object(s)"` + Volume_wr Ocf_stat `json:"Writes from exported object(s)"` + Volume_total Ocf_stat `json:"Total from/to exported object(s)"` +} + +type Ocf_stats_errors struct { + Core_volume_rd Ocf_stat `json:"Core read errors"` + Core_volume_wr Ocf_stat `json:"Core write errors"` + Core_volume_total Ocf_stat `json:"Core total errors"` + Cache_volume_rd Ocf_stat `json:"Cache read errors"` + Cache_volume_wr Ocf_stat `json:"Cache write errors"` + Cache_volume_total Ocf_stat `json:"Cache total errors"` + Total Ocf_stat `json:"Total errors"` +} + +type Ocf_stat struct { + Value uint64 `json:"Page"` + Fraction uint64 `json:"Fraction"` +} + +/** IOCTL_CACHE_INFO structs **/ + +type Kcas_cache_info struct { + Cache_id uint16 `json:"Cache id"` + Cache_path_name string `json:"Cache device"` + Attached_cores []uint16 `json:"Core(s) id(s)"` + Info Ocf_cache_info `json:"Cache details"` + ext_err_code int +} + +type Ocf_cache_info struct { + Attached bool `json:"Attached"` + volume_type uint8 `json:"Volume type"` // mby enum -> convert to str? + State string `json:"Status"` + Size uint32 `json:"Size [cache lines]"` + Inactive_cores Core_stats `json:"Inactive cores"` + Occupancy uint32 `json:"Occupancy [cache lines]"` + Dirty uint32 `json:"Dirty [cache lines]"` + Dirty_for uint64 `json:"Dirty for [s]"` + Dirty_initial uint32 `json:"Initially dirty [cache lines]"` + Cache_mode string `json:"Cache mode"` + Fallback_pt Fallback_pt_stats `json:"Pass-Trough fallback statistics"` + Cleaning_policy string `json:"Cleaning policy"` + Promotion_policy string `json:"Promotion policy"` + Cache_line_size uint64 `json:"Cache line size [KiB]"` + Flushed uint32 `json:"Flushed blocks"` + Core_count uint32 `json:"Core count"` + Metadata_footprint uint64 `json:"Metadata footprint [B]"` + Metadata_end_offset uint64 `json:"Metadata end offset [4 KiB blocks]"` +} + +type Core_stats struct { + Occupancy Ocf_stat `json:"Occupancy"` + Clean Ocf_stat `json:"Clean"` + Dirty Ocf_stat `json:"Dirty"` +} + +type Fallback_pt_stats struct { + Error_counter int `json:"IO errors count"` + Status bool `json:"Status"` +} + +/** IOCTL_CORE_INFO structs **/ + +type Kcas_core_info struct { + cache_id uint16 `json:"Cache id"` + Core_id uint16 `json:"Core id"` + Core_path_name string `json:"Core path"` + Info Ocf_core_info `json:"Core details"` + State string `json:"State"` + ext_err_code int +} + +type Ocf_core_info struct { + Core_size uint64 `json:"Core size [line size]"` + Core_size_bytes uint64 `json:"Core size [B]"` + Flushed uint32 `json:"Flushed blocks"` + Dirty uint32 `json:"Dirty blocks"` + Dirty_for uint64 `json:"Dirty for [s]"` + Seq_cutoff_threshold uint32 `json:"Sequential cutoff threshold [B]"` + Seq_cutoff_policy string `json:"Sequential cutoff policy [B]"` +} + +/** IOCTL_KCAS_IO_CLASS structs **/ + +type Kcas_io_class struct { + cache_id uint16 `json:"Cache id"` + Class_id uint32 `json:"Class id"` + Info Ocf_io_class_info `json:"IO class details"` + ext_err_code int +} + +type Ocf_io_class_info struct { + Name string `json:"Name"` + Cache_mode string `json:"Cache mode"` + Priority int16 `json:"Priority"` + Curr_size uint32 `json:"Current size [cache line]"` + Min_size int32 `json:"Min size [%]"` + Max_size int32 `json:"Max size [%]"` + Cleaning_policy string `json:"Cleaning policy"` +} diff --git a/json/ioctl/ocf_types.go b/json/ioctl/ocf_types.go new file mode 100644 index 000000000..80e6a8ed1 --- /dev/null +++ b/json/ioctl/ocf_types.go @@ -0,0 +1,25 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package ioctl + +// #cgo CFLAGS: -I./../../modules/include/ +// #include +import "C" + +const ( + invalid_cache_id = C.OCF_CACHE_ID_INVALID + Invalid_core_id = C.OCF_CORE_ID_INVALID + Invalid_io_class = C.OCF_IO_CLASS_INVALID +) + +/** ocf enum -> string mapping **/ + +var ocf_cache_state_str = []string{"running", "stopping", "initializing", "incomplete", "passive", "max"} +var ocf_core_state_str = []string{"active", "inactive", "max"} +var ocf_cache_mode_str = []string{"wt", "wb", "wa", "pt", "wi", "wo", "max"} +var ocf_cleaning_policy_str = []string{"nop", "alru", "acp", "max"} +var ocf_seq_cutoff_policy_str = []string{"always", "full", "never", "max"} +var ocf_promotion_str = []string{"always", "nhit", "max"} diff --git a/json/ioctl/structs_conv.go b/json/ioctl/structs_conv.go new file mode 100644 index 000000000..37d7f67f9 --- /dev/null +++ b/json/ioctl/structs_conv.go @@ -0,0 +1,255 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-3-Clause + */ + +package ioctl + +// #cgo CFLAGS: -I./../../modules/include/ +// #include +import "C" +import ( + "unsafe" +) + +/** kcas_get_stats struct conversion C to Go **/ + +func Conv_stats(C_kstats *C.struct_kcas_get_stats) Kcas_get_stats { + res := Kcas_get_stats{ + Cache_id: uint16(C_kstats.cache_id), + Core_id: uint16(C_kstats.core_id), + IO_class: uint16(C_kstats.part_id), + Usage: Ocf_stats_usage{ + Occupancy: Ocf_stat{Value: uint64(C_kstats.usage.occupancy.value), + Fraction: uint64(C_kstats.usage.occupancy.fraction)}, + Free: Ocf_stat{Value: uint64(C_kstats.usage.free.value), + Fraction: uint64(C_kstats.usage.free.fraction)}, + Clean: Ocf_stat{Value: uint64(C_kstats.usage.clean.value), + Fraction: uint64(C_kstats.usage.clean.fraction)}, + Dirty: Ocf_stat{Value: uint64(C_kstats.usage.dirty.value), + Fraction: uint64(C_kstats.usage.dirty.fraction)}, + }, + Req: Ocf_stats_requests{ + Rd_hits: Ocf_stat{Value: uint64(C_kstats.req.rd_hits.value), + Fraction: uint64(C_kstats.req.rd_hits.fraction)}, + Rd_partial_misses: Ocf_stat{Value: uint64(C_kstats.req.rd_partial_misses.value), + Fraction: uint64(C_kstats.req.rd_partial_misses.fraction)}, + Rd_full_misses: Ocf_stat{Value: uint64(C_kstats.req.rd_full_misses.value), + Fraction: uint64(C_kstats.req.rd_full_misses.fraction)}, + Rd_total: Ocf_stat{Value: uint64(C_kstats.req.rd_total.value), + Fraction: uint64(C_kstats.req.rd_total.fraction)}, + Wr_hits: Ocf_stat{Value: uint64(C_kstats.req.wr_hits.value), + Fraction: uint64(C_kstats.req.wr_hits.fraction)}, + Wr_partial_misses: Ocf_stat{Value: uint64(C_kstats.req.wr_partial_misses.value), + Fraction: uint64(C_kstats.req.wr_partial_misses.fraction)}, + Wr_full_misses: Ocf_stat{Value: uint64(C_kstats.req.wr_full_misses.value), + Fraction: uint64(C_kstats.req.wr_full_misses.fraction)}, + Wr_total: Ocf_stat{Value: uint64(C_kstats.req.wr_total.value), + Fraction: uint64(C_kstats.req.wr_total.fraction)}, + Rd_pt: Ocf_stat{Value: uint64(C_kstats.req.rd_pt.value), + Fraction: uint64(C_kstats.req.rd_pt.fraction)}, + Wr_pt: Ocf_stat{Value: uint64(C_kstats.req.wr_pt.value), + Fraction: uint64(C_kstats.req.wr_pt.fraction)}, + Serviced: Ocf_stat{Value: uint64(C_kstats.req.serviced.value), + Fraction: uint64(C_kstats.req.serviced.fraction)}, + Total: Ocf_stat{Value: uint64(C_kstats.req.total.value), + Fraction: uint64(C_kstats.req.total.fraction)}, + }, + Blocks: Ocf_stats_blocks{ + Core_volume_rd: Ocf_stat{Value: uint64(C_kstats.blocks.core_volume_rd.value), + Fraction: uint64(C_kstats.blocks.core_volume_rd.fraction)}, + Core_volume_wr: Ocf_stat{Value: uint64(C_kstats.blocks.core_volume_wr.value), + Fraction: uint64(C_kstats.blocks.core_volume_wr.fraction)}, + Core_volume_total: Ocf_stat{Value: uint64(C_kstats.blocks.core_volume_total.value), + Fraction: uint64(C_kstats.blocks.core_volume_total.fraction)}, + Cache_volume_rd: Ocf_stat{Value: uint64(C_kstats.blocks.cache_volume_rd.value), + Fraction: uint64(C_kstats.blocks.cache_volume_rd.fraction)}, + Cache_volume_wr: Ocf_stat{Value: uint64(C_kstats.blocks.cache_volume_wr.value), + Fraction: uint64(C_kstats.blocks.cache_volume_wr.fraction)}, + Cache_volume_total: Ocf_stat{Value: uint64(C_kstats.blocks.cache_volume_total.value), + Fraction: uint64(C_kstats.blocks.cache_volume_total.fraction)}, + Volume_rd: Ocf_stat{Value: uint64(C_kstats.blocks.volume_rd.value), + Fraction: uint64(C_kstats.blocks.volume_rd.fraction)}, + Volume_wr: Ocf_stat{Value: uint64(C_kstats.blocks.volume_wr.value), + Fraction: uint64(C_kstats.blocks.volume_wr.fraction)}, + Volume_total: Ocf_stat{Value: uint64(C_kstats.blocks.volume_total.value), + Fraction: uint64(C_kstats.blocks.volume_total.fraction)}, + }, + Errors: Ocf_stats_errors{ + Core_volume_rd: Ocf_stat{Value: uint64(C_kstats.errors.core_volume_rd.value), + Fraction: uint64(C_kstats.errors.core_volume_rd.fraction)}, + Core_volume_wr: Ocf_stat{Value: uint64(C_kstats.errors.core_volume_wr.value), + Fraction: uint64(C_kstats.errors.core_volume_wr.fraction)}, + Core_volume_total: Ocf_stat{Value: uint64(C_kstats.errors.core_volume_total.value), + Fraction: uint64(C_kstats.errors.core_volume_total.fraction)}, + Cache_volume_rd: Ocf_stat{Value: uint64(C_kstats.errors.cache_volume_rd.value), + Fraction: uint64(C_kstats.errors.cache_volume_rd.fraction)}, + Cache_volume_wr: Ocf_stat{Value: uint64(C_kstats.errors.cache_volume_wr.value), + Fraction: uint64(C_kstats.errors.cache_volume_wr.fraction)}, + Cache_volume_total: Ocf_stat{Value: uint64(C_kstats.errors.cache_volume_total.value), + Fraction: uint64(C_kstats.errors.cache_volume_total.fraction)}, + Total: Ocf_stat{Value: uint64(C_kstats.errors.total.value), + Fraction: uint64(C_kstats.errors.total.fraction)}, + }, + ext_err_code: int(C_kstats.ext_err_code), + } + return res +} + +/** cache_info struct conversion C to Go **/ + +func Conv_cache_info(C_kcache_info *C.struct_kcas_cache_info) Kcas_cache_info { + + /** fields with no standard initialization rquired */ + attached_cores := get_attached_cores(C_kcache_info) + state := valid_enum_idx(int(C_kcache_info.info.state), ocf_cache_state_str) + cache_mode := valid_enum_idx(int(C_kcache_info.info.cache_mode), ocf_cache_mode_str) + cleaning_policy := valid_enum_idx(int(C_kcache_info.info.cleaning_policy), ocf_cleaning_policy_str) + promotion_policy := valid_enum_idx(int(C_kcache_info.info.promotion_policy), ocf_promotion_str) + + res := Kcas_cache_info{ + Cache_id: uint16(C_kcache_info.cache_id), + Cache_path_name: C.GoString(&C_kcache_info.cache_path_name[0]), + Attached_cores: attached_cores, + Info: Ocf_cache_info{ + Attached: bool(C_kcache_info.info.attached), + volume_type: uint8(C_kcache_info.info.volume_type), + State: state, + Size: uint32(C_kcache_info.info.size), + Inactive_cores: Core_stats{ + Occupancy: Ocf_stat{Value: uint64(C_kcache_info.info.inactive.occupancy.value), + Fraction: uint64(C_kcache_info.info.inactive.occupancy.fraction)}, + Clean: Ocf_stat{Value: uint64(C_kcache_info.info.inactive.clean.value), + Fraction: uint64(C_kcache_info.info.inactive.clean.fraction)}, + Dirty: Ocf_stat{Value: uint64(C_kcache_info.info.inactive.dirty.value), + Fraction: uint64(C_kcache_info.info.inactive.dirty.fraction)}, + }, + Occupancy: uint32(C_kcache_info.info.occupancy), + Dirty: uint32(C_kcache_info.info.dirty), + Dirty_for: uint64(C_kcache_info.info.dirty_for), + Dirty_initial: uint32(C_kcache_info.info.dirty_initial), + Cache_mode: cache_mode, + Fallback_pt: Fallback_pt_stats{ + Error_counter: int(C_kcache_info.info.fallback_pt.error_counter), + Status: bool(C_kcache_info.info.fallback_pt.status), + }, + Cleaning_policy: cleaning_policy, + Promotion_policy: promotion_policy, + Cache_line_size: uint64(C_kcache_info.info.cache_line_size), + Flushed: uint32(C_kcache_info.info.flushed), + Core_count: uint32(C_kcache_info.info.core_count), + Metadata_footprint: uint64(C_kcache_info.info.metadata_footprint), + Metadata_end_offset: uint64(C_kcache_info.info.metadata_end_offset), + }, + ext_err_code: int(C_kcache_info.ext_err_code), + } + return res +} + +/** core_info struct conversion C to Go **/ + +func Conv_core_info(C_kcore_info *C.struct_kcas_core_info) Kcas_core_info { + + /** fields with no standard initialization rquired */ + seq_cutoff_policy := valid_enum_idx(int(C_kcore_info.info.seq_cutoff_policy), ocf_seq_cutoff_policy_str) + state := valid_enum_idx(int(C_kcore_info.state), ocf_core_state_str) + + res := Kcas_core_info{ + Core_path_name: C.GoString(&C_kcore_info.core_path_name[0]), + cache_id: uint16(C_kcore_info.cache_id), + Core_id: uint16(C_kcore_info.core_id), + Info: Ocf_core_info{ + Core_size: uint64(C_kcore_info.info.core_size), + Core_size_bytes: uint64(C_kcore_info.info.core_size_bytes), + Flushed: uint32(C_kcore_info.info.flushed), + Dirty: uint32(C_kcore_info.info.dirty), + Dirty_for: uint64(C_kcore_info.info.dirty_for), + Seq_cutoff_threshold: uint32(C_kcore_info.info.seq_cutoff_threshold), + Seq_cutoff_policy: seq_cutoff_policy, + }, + State: state, + ext_err_code: int(C_kcore_info.ext_err_code), + } + return res +} + +/** io_class struct conversion C to Go **/ + +func Conv_io_class(C_kio_class *C.struct_kcas_io_class) Kcas_io_class { + + /** fields with no standard initialization rquired */ + cache_mode := valid_enum_idx(int(C_kio_class.info.cache_mode), ocf_cache_mode_str) + cleaning_policy := valid_enum_idx(int(C_kio_class.info.cleaning_policy_type), ocf_cleaning_policy_str) + + res := Kcas_io_class{ + cache_id: uint16(C_kio_class.cache_id), + Class_id: uint32(C_kio_class.class_id), + Info: Ocf_io_class_info{ + Name: C.GoString(&C_kio_class.info.name[0]), + Cache_mode: cache_mode, + Priority: int16(C_kio_class.info.priority), + Curr_size: uint32(C_kio_class.info.curr_size), + Min_size: int32(C_kio_class.info.min_size), + Max_size: int32(C_kio_class.info.max_size), + Cleaning_policy: cleaning_policy, + }, + ext_err_code: int(C_kio_class.ext_err_code), + } + return res +} + +// converts C type uint16[CACHE_LIST_ID_LIMIT] to Go slice []uint16 +// picks only valid caches and appends to result slice +func Conv_cache_id_list(C_kcache_list *C.struct_kcas_cache_list) []uint16 { + var valid_cache_ids []uint16 + cache_id_list := (*[C.CACHE_LIST_ID_LIMIT]uint16)(unsafe.Pointer(&C_kcache_list.cache_id_tab[0]))[:C.CACHE_LIST_ID_LIMIT:C.CACHE_LIST_ID_LIMIT] + + // Go in range for syntax - for index, value := range collection {} + for _, cache_id := range cache_id_list { + if valid_cache_id(cache_id) { + valid_cache_ids = append(valid_cache_ids, cache_id) + } else { + break + } + } + return valid_cache_ids +} + +/** helper functions for initializations of no standard types **/ + +// converts C type uint16[OCF_CORE_MAX] to Go slice []uint16 +// picks only valid cores and appends to result slice +func get_attached_cores(C_kcache_info *C.struct_kcas_cache_info) []uint16 { + var valid_core_ids []uint16 + + /** conversion C cores_id array to Golang slice */ + core_ids := (*[C.OCF_CORE_MAX]uint16)(unsafe.Pointer(&C_kcache_info.core_id[0]))[:C.OCF_CORE_MAX:C.OCF_CORE_MAX] + for core_idx := 0; core_idx < int(C_kcache_info.info.core_count); core_idx++ { + if valid_core_id(core_ids[core_idx]) { + valid_core_ids = append(valid_core_ids, core_ids[core_idx]) + } + } + return valid_core_ids +} + +/** checks for valid core id */ + +func valid_core_id(core_id uint16) bool { + return core_id < C.OCF_CORE_MAX +} + +/** checks for valid cache id */ + +func valid_cache_id(cache_id uint16) bool { + return C.OCF_CACHE_ID_MIN <= cache_id && cache_id < C.CACHE_LIST_ID_LIMIT +} + +/** checks if enum value as idx is out of range and returns valid value or default value */ + +func valid_enum_idx(idx int, enum_str_slice []string) string { + if 0 <= idx && idx < len(enum_str_slice) { + return enum_str_slice[idx] + } else { + return "undefined" + } +} diff --git a/json/main.go b/json/main.go new file mode 100644 index 000000000..6a964ac18 --- /dev/null +++ b/json/main.go @@ -0,0 +1,16 @@ +/* +* Copyright(c) 2012-2021 Intel Corporation +* SPDX-License-Identifier: BSD-Clause + */ + +/* +* Prototype of JSON RESTful API + */ + +package main + +import "github.com/Open-CAS/open-cas-linux/json/api" + +func main() { + api.Json_api() +} diff --git a/json/request.json b/json/request.json new file mode 100644 index 000000000..1fc2bf1d7 --- /dev/null +++ b/json/request.json @@ -0,0 +1,6 @@ +{ + "command": "opencas.cache.info.get", + "params": { + "cache id": 1 + } +} From 17ba5e3af2f19258f4306c6a9e98cb3e406b254e Mon Sep 17 00:00:00 2001 From: Piotr Debski Date: Tue, 11 Jan 2022 15:17:43 +0100 Subject: [PATCH 2/2] JSON API tests Signed-off-by: Piotr Debski --- test/functional/api/cas/json_api.py | 69 ++++++++ .../tests/json_api/test_json_api.py | 151 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 test/functional/api/cas/json_api.py create mode 100644 test/functional/tests/json_api/test_json_api.py diff --git a/test/functional/api/cas/json_api.py b/test/functional/api/cas/json_api.py new file mode 100644 index 000000000..e00892162 --- /dev/null +++ b/test/functional/api/cas/json_api.py @@ -0,0 +1,69 @@ +# +# Copyright(c) 2019-2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause-Clear +# + +from core.test_run import TestRun +import json + + +class Response_structure: + response_keys = {"opencas.cache.stats.get": ["Cache id", "Cache id", "IO class", "Usage", + "Requests", "Blocks", "Errors"], + "opencas.cache.core.stats.get": ["Cache id", "Cache id", "IO class", "Usage", + "Requests", "Blocks", "Errors"], + "opencas.cache.ioclass.stats.get": ["Cache id", "Cache id", "IO class", + "Usage", "Requests", "Blocks", "Errors"], + "opencas.cache.core.ioclass.stats.get": ["Cache id", "Cache id", "IO class", + "Usage", "Requests", "Blocks", + "Errors"], + "opencas.cache_list.get": [], + "opencas.cache.info.get": ["Cache id", "Cache device", "Core(s) id(s)", + "Cache details"], + "opencas.cache.core.info.get": ["Core id", "Core path", "Core details", + "State"], + "opencas.cache.ioclass.info.get": ["Class id", "IO class details"]} + + +class Json_api: + response_structure = Response_structure() + + @classmethod + def send_request(cls, request: dict): + request = json.dumps(request) + TestRun.LOGGER.info(f"Request: '{request}'") + exec_json_api_path = "./usr/sbin/opencas-json-api" + command = f"cd / && echo '{request}' | {exec_json_api_path}" + response = TestRun.executor.run(command) + if response.exit_code != 0: + raise Exception(f"Failed Request: '{request}'") + return response.stdout + + @classmethod + def verify_response_structure(cls, command: str, response: str): + response = json.loads(response) + for key in cls.response_structure.response_keys[command]: + if key not in response.keys(): + raise Exception(f"Response structure is not valid: missing {key} field") + TestRun.LOGGER.info(f"Request: {command} PASSED") + return True + + @classmethod + def verify_response_content(cls, response: str): + raise NotImplementedError() + + @classmethod + def verify_invalid_request_response(cls, command: str, response: str): + response = json.loads(response) + if "error" in response.keys(): + TestRun.LOGGER.info(f"Invalid Request: {command} DETECTED") + return + raise Exception(f"Invalid Request: {command} NOT DETECTED") + + @classmethod + def check_json_api_installed(cls): + command = "ls /sbin/opencas-json-api" + output = TestRun.executor.run(command) + if output.exit_code != 0: + raise Exception("JSON API not installed - try rebuild after ./configure " + "--with-json-api") diff --git a/test/functional/tests/json_api/test_json_api.py b/test/functional/tests/json_api/test_json_api.py new file mode 100644 index 000000000..1d9c1a715 --- /dev/null +++ b/test/functional/tests/json_api/test_json_api.py @@ -0,0 +1,151 @@ +# +# Copyright(c) 2019-2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause-Clear +# + +import pytest + +from api.cas import casadm +from api.cas.json_api import Json_api +from core.test_run import TestRun +from storage_devices.disk import DiskTypeSet, DiskType, DiskTypeLowerThan +from test_utils.size import Size, Unit + + +@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand])) +@pytest.mark.require_disk("core", DiskTypeLowerThan("cache")) +def test_json_api_requests(): + with TestRun.step("Check JSON API installed"): + Json_api.check_json_api_installed() + + with TestRun.step("Prepare CAS device."): + cache_disk = TestRun.disks['cache'] + cache_disk.create_partitions([Size(20, Unit.GibiByte)]) + cache_dev = cache_disk.partitions[0] + + core_disk = TestRun.disks['core'] + core_disk.create_partitions([Size(20, Unit.GibiByte)]) + core_dev = core_disk.partitions[0] + + cache = casadm.start_cache(cache_dev, force=True) + core = cache.add_core(core_dev) + TestRun.LOGGER.info(TestRun.executor.run("casadm -L").stdout) + + with TestRun.step("Parametrize valid requests"): + io_class = 0 + cache_id = cache.cache_id + core_id = core.core_id + + with TestRun.step("Init valid all requests"): + request_opencas_cache_stats_get = {"command": "opencas.cache.stats.get", + "params": {"cache id": cache_id}} + request_opencas_cache_core_stats_get = {"command": "opencas.cache.core.stats.get", + "params": {"cache id": cache_id, + "core id": core_id}} + request_opencas_cache_ioclass_stats_get = {"command": + "opencas.cache.ioclass.stats.get", + "params": {"cache id": cache_id, + "io class": io_class}} + request_opencas_cache_core_ioclass_stats_get = {"command": + "opencas.cache.core.ioclass.stats.get", + "params": {"cache id": cache_id, + "core id": core_id, + "io class": io_class}} + request_opencas_cache_list_get = {"command": "opencas.cache_list.get", + "params": {}} + request_opencas_cache_info_get = {"command": "opencas.cache.info.get", + "params": {"cache id": cache_id, }} + request_opencas_core_info_get = {"command": "opencas.cache.core.info.get", + "params": {"cache id": cache_id, "core id": core_id}} + request_opencas_ioclass_info_get = {"command": "opencas.cache.ioclass.info.get", + "params": {"cache id": cache_id, "io class": io_class}} + + with TestRun.group("JSON API requests"): + with TestRun.step("GET CACHE REQUEST"): + response = Json_api.send_request(request_opencas_cache_stats_get) + Json_api.verify_response_structure(request_opencas_cache_stats_get["command"], response) + with TestRun.step("GET CACHE CORE REQUEST"): + response = Json_api.send_request(request_opencas_cache_core_stats_get) + Json_api.verify_response_structure( + request_opencas_cache_core_stats_get["command"], response) + with TestRun.step("GET CACHE IO CLASS REQUEST"): + response = Json_api.send_request(request_opencas_cache_ioclass_stats_get) + Json_api.verify_response_structure( + request_opencas_cache_ioclass_stats_get["command"], response) + with TestRun.step("GET CACHE CORE IO CLAS REQUEST"): + response = Json_api.send_request(request_opencas_cache_core_ioclass_stats_get) + Json_api.verify_response_structure( + request_opencas_cache_core_ioclass_stats_get["command"], response) + with TestRun.step("GET CACHE LIST"): + response = Json_api.send_request(request_opencas_cache_list_get) + Json_api.verify_response_structure(request_opencas_cache_list_get["command"], response) + with TestRun.step("GET CACHE INFO"): + response = Json_api.send_request(request_opencas_cache_info_get) + Json_api.verify_response_structure(request_opencas_cache_info_get["command"], response) + with TestRun.step("GET CORE INFO"): + response = Json_api.send_request(request_opencas_core_info_get) + Json_api.verify_response_structure(request_opencas_core_info_get["command"], response) + with TestRun.step("GET IO CLASS INFO"): + response = Json_api.send_request(request_opencas_ioclass_info_get) + Json_api.verify_response_structure( + request_opencas_ioclass_info_get["command"], response) + + with TestRun.group("JSON API invalid requests"): + with TestRun.step("Corner values"): + invalid_cache_id = cache_id + 1 + invalid_core_id = core_id + 1 + different_io_class = io_class + 1 + + with TestRun.step("Init all invalid requests"): + request_opencas_cache_stats_get = {"command": "opencas.cache.stats.get", + "params": {"cache id": invalid_cache_id}} + request_opencas_cache_core_stats_get = {"command": "opencas.cache.core.stats.get", + "params": {"cache id": cache_id, + "core id": invalid_core_id}} + request_opencas_cache_ioclass_stats_get = {"command": "opencas.cache.ioclass.stats.get", + "params": {"cache id": invalid_cache_id, + "io class": different_io_class}} + request_opencas_cache_core_ioclass_stats_get = {"command": + "opencas.cache.core.ioclass.stats.get", + "params": {"cache id": invalid_cache_id, + "core id": invalid_core_id, + "io class": different_io_class}} + request_opencas_cache_info_get = {"command": "opencas.cache.info.get", + "params": {"cache id": invalid_cache_id, }} + request_opencas_core_info_get = {"command": "opencas.cache.core.info.get", + "params": {"cache id": cache_id, + "core id": invalid_core_id}} + request_opencas_ioclass_info_get = {"command": "opencas.cache.ioclass.info.get", + "params": {"cache id": invalid_cache_id, + "io class": different_io_class}} + + with TestRun.group("JSON API invalid requests"): + with TestRun.step("GET CACHE REQUEST"): + response = Json_api.send_request(request_opencas_cache_stats_get) + Json_api.verify_invalid_request_response(request_opencas_cache_stats_get["command"], + response) + with TestRun.step("GET CACHE CORE REQUEST"): + response = Json_api.send_request(request_opencas_cache_core_stats_get) + Json_api.verify_invalid_request_response( + request_opencas_cache_core_stats_get["command"], response) + with TestRun.step("GET CACHE IO CLASS REQUEST"): + response = Json_api.send_request(request_opencas_cache_ioclass_stats_get) + Json_api.verify_invalid_request_response( + request_opencas_cache_ioclass_stats_get["command"], response) + with TestRun.step("GET CACHE CORE IO CLAS REQUEST"): + response = Json_api.send_request(request_opencas_cache_core_ioclass_stats_get) + Json_api.verify_invalid_request_response( + request_opencas_cache_core_ioclass_stats_get["command"], response) + + with TestRun.step("GET CACHE INFO"): + response = Json_api.send_request(request_opencas_cache_info_get) + Json_api.verify_invalid_request_response(request_opencas_cache_info_get["command"], + response) + with TestRun.step("GET CORE INFO"): + response = Json_api.send_request(request_opencas_core_info_get) + Json_api.verify_invalid_request_response(request_opencas_core_info_get["command"], + response) + with TestRun.step("GET IO CLASS INFO"): + response = Json_api.send_request(request_opencas_ioclass_info_get) + Json_api.verify_invalid_request_response( + request_opencas_ioclass_info_get["command"], response)