Initial commit

Signed-off-by: Robert Baldyga <robert.baldyga@intel.com>
This commit is contained in:
Robert Baldyga
2019-03-29 08:39:34 +01:00
commit 94e8ca09e0
140 changed files with 37144 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
ACTION!="add", GOTO="cas_loader_end"
SUBSYSTEM!="block", GOTO="cas_loader_end"
RUN+="/lib/opencas/open-cas-loader /dev/$name"
# Work around systemd<->udev interaction, make sure filesystems with labels on
# cas are mounted properly
KERNEL!="cas*", GOTO="cas_loader_end"
IMPORT{builtin}="blkid"
ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", RUN+="/lib/opencas/open-cas-mount-utility $env{ID_FS_LABEL_ENC}"
LABEL="cas_loader_end"

View File

@@ -0,0 +1,38 @@
ACTION=="remove", GOTO="cas_end"
SUBSYSTEM=="block", KERNEL=="cas*", OPTIONS+="watch"
SUBSYSTEM!="block", GOTO="cas_end"
KERNEL!="cas*", GOTO="cas_end"
# ignore partitions that span the entire disk
TEST=="whole_disk", GOTO="cas_end"
# for partitions import parent information
ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*"
# by-path
ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id"
ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"
ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n"
# probe filesystem metadata of disks
KERNEL=="cas*", IMPORT{builtin}="blkid"
# by-label/by-uuid links (filesystem metadata)
ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}", OPTIONS+="link_priority=999"
ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}", OPTIONS+="link_priority=999"
# by-id (World Wide Name)
ENV{DEVTYPE}=="disk", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}"
ENV{DEVTYPE}=="partition", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}-part%n"
# by-partlabel/by-partuuid links (partition metadata)
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}", OPTIONS+="link_priority=999"
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}", OPTIONS+="link_priority=999"
# add symlink to GPT root disk
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_GPT_AUTO_ROOT}=="1", SYMLINK+="gpt-auto-root", OPTIONS+="link_priority=999"
LABEL="cas_end"

71
utils/Makefile Normal file
View File

@@ -0,0 +1,71 @@
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
CASCTL_DIR = /lib/opencas
UDEVRULES_DIR = /lib/udev/rules.d
UDEV:=$(shell which udevadm)
SYSTEMCTL := $(shell which systemctl)
ifeq (, $(shell which systemctl))
define cas_install
install -m 755 open-cas-shutdown /etc/init.d/open-cas-shutdown
/sbin/chkconfig open-cas-shutdown on; service open-cas-shutdown start
endef
else
ifneq "$(wildcard /usr/lib/systemd/system)" ""
SYSTEMD_DIR=/usr/lib/systemd/system
else
SYSTEMD_DIR=/lib/systemd/system
endif
define cas_install
install -m 644 open-cas-shutdown.service $(SYSTEMD_DIR)/open-cas-shutdown.service
install -m 755 -d $(SYSTEMD_DIR)/../system-shutdown
install -m 755 open-cas.shutdown $(SYSTEMD_DIR)/../system-shutdown/open-cas.shutdown
$(SYSTEMCTL) daemon-reload
$(SYSTEMCTL) -q enable open-cas-shutdown
endef
endif
# Just a placeholder when running make from parent dir without install/uninstall arg
default: ;
install:
@echo "Installing Open-CAS utils"
@install -m 755 -d $(CASCTL_DIR)
@install -m 644 opencas.py $(CASCTL_DIR)/opencas.py
@install -m 755 casctl $(CASCTL_DIR)/casctl
@install -m 755 open-cas-loader $(CASCTL_DIR)/open-cas-loader
@install -m 755 open-cas-mount-utility $(CASCTL_DIR)/open-cas-mount-utility
@ln -fs $(CASCTL_DIR)/casctl /sbin/casctl
@install -m 644 60-persistent-storage-cas-load.rules $(UDEVRULES_DIR)/60-persistent-storage-cas-load.rules
@install -m 644 60-persistent-storage-cas.rules $(UDEVRULES_DIR)/60-persistent-storage-cas.rules
@install -m 755 -d /usr/share/doc/opencas
@$(UDEV) control --reload-rules
@install -m 644 casctl.8 /usr/share/man/man8/casctl.8
$(cas_install)
uninstall:
@rm $(CASCTL_DIR)/opencas.py
@rm $(CASCTL_DIR)/casctl
@rm $(CASCTL_DIR)/open-cas-loader
@rm $(CASCTL_DIR)/open-cas-mount-utility
@rm -rf $(CASCTL_DIR)
@rm /sbin/casctl
@rm /usr/share/man/man8/casctl.8
@rm /lib/udev/rules.d/60-persistent-storage-cas-load.rules
@rm /lib/udev/rules.d/60-persistent-storage-cas.rules
.PHONY: install uninstall clean distclean

565
utils/casadm.8 Normal file
View File

@@ -0,0 +1,565 @@
.TH casadm 8 __CAS_DATE__ v__CAS_VERSION__
.SH NAME
casadm \- create, and manage Open CAS instances
.SH SYNOPSIS
\fBcasadm\fR <command> [options...]
.SH COPYRIGHT
Copyright(c) 2012-2019 by the Intel Corporation.
.SH DESCRIPTION
Open Cache Acceleration Software (CAS) accelerates Linux applications by caching
active (hot) data to a local flash device inside servers. Open CAS implements
caching at the server level, utilizing local high-performance flash media as
the cache drive media inside the application server as close as possible to
the CPU, thus reducing storage latency as much as possible.
.PP
Open Cache Acceleration Software installs into the GNU/Linux operating system itself,
as a kernel module. The nature of the integration provides a cache solution that is
transparent to users and applications, and your existing storage infrastructure. No
storage migration effort or application changes are required.
.PP
\fBCache device\fR is a faster drive (e.g. SSD-type) used for speeding-up core device.
.br
\fBCore device\fR is a slower drive (e.g. HDD-type) that will be accelerated by Open CAS.
.SH MODES
Open CAS caching software has several modes of operation:
.TP
.B Write-Through (wt)
Write-Through is a basic caching mode where writes are done synchronously to
the cache device and to the core device. Write-Through cache, which is also known
as Read Cache, mainly improves performance of read IO operations.
.TP
.B Write-Back (wb)
In Write-Back mode writes are initially written to the cache device only. Cached
write operations that are not synchronized with core device are marked as dirty.
The procedure of writing dirty data from cache device to core device is known as
cleaning. Cleaning may be required if cache is full and eviction (replacement)
policy needs to remove stale data to make space for incoming blocks. Open CAS
provides mechanism which automatically cleans dirty data in background. This is
cleaning (flushing) thread. User can also invoke manual cleaning procedure (see
-E, --flush-cache and -F --flush-core options). Write-Back cache, also known as
Write Cache, improves performance of both read and write IO operations.
.TP
.B Write-Around (wa)
In Write-Around mode write operations are not cached. This means that write to
block that does not exist in cache is written directly to the core device,
bypassing the cache. If write operation is issued to the block which is already
in cache (because of previous read operation) then write is send to the core device
and cache block is updated in the cache device. Write-Around cache improves performance
of workloads where write operation is done rarely and no further read accesses
to that data are performed, so there is no point in caching it.
.TP
.B Pass-Through (pt)
In Pass-Through mode all read and write operations are not cached and sent directly
to the core device. Pass-Through mode may be used in case if user doesn't want to
cache any workload, for example in case if there are some maintenance operations
causing cache pollution.
.SH COMMANDS
.TP
.B -S, --start-cache
Start cache instance.
.TP
.B -T, --stop-cache
Stop cache instance.
.TP
.B -X, --set-param
Set runtime parameter for cache/core instance.
.TP
.B -G, --set-param
Get runtime parameter for cache/core instance.
.TP
.B -Q, --set-cache-mode
Switch caching mode of cache instance.
.TP
.B -A, --add-core
Add core device to cache instance.
.TP
.B -R, --remove-core
Remove core device from cache instance.
.TP
.B " "--remove-detached
Remove core device from core pool.
.TP
.B -L, --list-caches
List all cache instances and core devices.
.TP
.B -P, --stats
Print statistics of cache instance.
.TP
.B -Z, --reset-counters
Reset statistics of given cache/core instance.
.TP
.B -F, --flush-cache
Flush all dirty data from the caching device to core devices.
.TP
.B -E, --flush-core
Flush dirty data of a given core from the caching device to this core device.
.TP
.B -C, --io-class {--load-config|--list}
Manage IO classes.
.br
1. \fB-C, --load-config\fR - load default configuration of IO classes.
\fBNOTE:\fR See /etc/opencas for example configuration file.
2. \fB-L, --list\fR - print current IO class configuration. Allowed output formats: table or CSV.
.TP
.B -N, --nvme
Manage NVMe device.
.br
1. \fB-F, --format\fR <MODE> {normal|atomic} - format NVMe device to one of supported modes.
\fBNOTE:\fR After formatting NVMe device platform reboot is required.
.br
Defines cache metadata mode.
In normal mode NVMe namespace uses LBA of size 512 bytes. Cache data and metadata
are written to disk in separate requests in the same way that it happens with
standard SSD device.
Atomic mode exploits extended NVMe metadata features. In this mode namespace
uses 520 bytes LBA allowing to write cache data and metadata in a single
request (atomically).
.TP
.B -H, --help
Print help.
.TP
.B -V, --version
Print Open CAS product version.
.SH OPTIONS
List of available options depends on current context of invocation. For each
command there is a different list of available options:
.BR
.SH Options that are valid with --start-cache (-S) are:
.TP
.B -d, --cache-device <DEVICE>
Path to caching device to be used e.g. SSD device (/dev/sdb).
.TP
.B -i, --cache-id <ID>
Unique identifier of cache (if not provided the first available will be used) <1-16384>.
.TP
.B -l, --load
If metadata exists on a device and this parameter is used, cache will be started based on information from metadata.
If this parameter is not used, cache will be started with full initialization of new metadata.
This option should be used if dirty data were not flushed on exit (if the cache was stopped with the -n, --no-data-flush option).
\fBCAUTION:\fR
.br
\fB*\fR If the data between the cache device and core device is not in sync (e.g. changes between cache stop and load operations), starting
cache with load option may cause data mismatch.
.TP
.B -f, --force
Force to start a cache. By default cache will not be started if utility detects file system on cache device.
This parameter ignores this situations, and starts a cache instance.
.TP
.B -c, --cache-mode {wt|wb|wa|pt}
Cache mode to be used for a cache instance.
Available modes are:
.br
1. \fBwt - Write-Through (default)\fR.
.br
2. \fBwb - Write-Back\fR.
.br
3. \fBwa - Write-Around\fR.
.br
4. \fBpt - Pass-Through\fR.
.TP
.B -x, --cache-line-size <NUMBER>
Set cache line size for given cache instance, expressed in KiB. This
can't be reconfigured runtime. Allowed values: {4,8,16,32,64}
(default: 4)
.SH Options that are valid with --stop-cache (-T) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -n, --no-data-flush
Do not flush dirty data on exit (may be \fBDANGEROUS\fR).
If this option was used, the cache should be restarted with the -l, --load option.
.br
\fBNOTE:\fR If dirty data were not flushed, the contents of a core device
MUST NOT be changed before restarting the cache. Otherwise there is
a data mismatch risk.
.SH Options that are valid with --set-param (-X) are:
.TP
.B -n, --name <NAME>
Name of parameters namespace.
Available namespaces are:
.br
\fBseq-cutoff\fR - Sequential cutoff parameters.
\fBcleaning\fR - Cleaning policy parameters.
\fBcleaning-alru\fR - Cleaning policy ALRU parameters.
\fBcleaning-acp\fR - Cleaning policy ACP parameters.
.SH Options that are valid with --set-param (-X) --name (-n) seq-cutoff are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance. If this option
is not specified, parameter is set to all cores within given cache instance.
.TP
.B -t, --seq-threshold <NUMBER>
Amount of sequential data in KiB after which request is handled in pass-through mode.
.TP
.B -p, --seq-policy {always|full|never}
Sequential cutoff policy to be used with a given core instance(s).
.SH Options that are valid with --set-param (-X) --name (-n) cleaning are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -p, --policy {nop|alru|acp}
Cleaning policy type to be used with a given cache instance.
Available policies:
.br
1. \fBnop\fR. No Operation (no periodical cleaning, clean on eviction only).
.br
2. \fBalru\fR. Approximately Least Recently Used (default).
.br
3. \fBacp\fR. Aggressive Cleaning Policy.
.SH Options that are valid with --set-param (-X) --name (-n) cleaning-alru are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -w, --wake-up <NUMBER>
Period of time between awakenings of flushing thread [s] (default: 20 s).
.TP
.B -s, --staleness-time <NUMBER>
Time that has to pass from the last write operation before a dirty cache block can be scheduled to be flushed [s] (default: 120 s).
.TP
.B -b, --flush-max-buffers <NUMBER>
Number of dirty cache blocks to be flushed in one cleaning cycle (default: 100).
.TP
.B -t, --activity-threshold <NUMBER>
Cache idle time before flushing thread can start [ms] (default: 10000 ms).
.SH Options that are valid with --set-param (-X) --name (-n) cleaning-acp are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -w, --wake-up <NUMBER>
Period of time between awakenings of flushing thread [ms] (default: 10 ms).
.TP
.B -b, --flush-max-buffers <NUMBER>
Number of dirty cache blocks to be flushed in one cleaning cycle (default: 128).
.SH Options that are valid with --get-param (-G) are:
.TP
.B -n, --name <NAME>
Name of parameters namespace.
Available namespaces are:
.br
\fBseq-cutoff\fR - Sequential cutoff parameters.
\fBcleaning\fR - Cleaning policy parameters.
\fBcleaning-alru\fR - Cleaning policy ALRU parameters.
\fBcleaning-acp\fR - Cleaning policy ACP parameters.
.SH Options that are valid with --get-param (-G) --name (-n) seq-cutoff are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance.
.TP
.B -o, --output-format {table|csv}
Defines output format for parameter list. It can be either \fBtable\fR (default) or \fBcsv\fR.
.SH Options that are valid with --get-param (-G) --name (-n) cleaning are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -o, --output-format {table|csv}
Defines output format for parameter list. It can be either \fBtable\fR (default) or \fBcsv\fR.
.SH Options that are valid with --get-param (-G) --name (-n) cleaning-alru are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -o, --output-format {table|csv}
Defines output format for parameter list. It can be either \fBtable\fR (default) or \fBcsv\fR.
.SH Options that are valid with --get-param (-G) --name (-n) cleaning-acp are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -o, --output-format {table|csv}
Defines output format for parameter list. It can be either \fBtable\fR (default) or \fBcsv\fR.
.SH Options that are valid with --set-cache-mode (-Q) are:
.TP
.B -c, --cache-mode {wt|wb|wa|pt}
Cache mode to be used with a given cache instance.
Available modes:
.br
1. \fBwt - Write-Through\fR.
.br
2. \fBwb - Write-Back\fR.
.br
3. \fBwa - Write-Around\fR.
.br
4. \fBpt - Pass-Through\fR.
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -f, --flush-cache {yes|no}
Flush all cache dirty data before switching to different mode. Option is required
when switching from Write-Back mode.
.SH Options that are valid with --add-core (-A) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -d, --core-device <DEVICE>
Path to core device e.g. HDD device.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance for new core to be created. This
parameter is optional. If it is not supplied, first available core id within cache instance will
be used for new core.
.SH Options that are valid with --remove-core (-R) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance.
.TP
.B -f, --force
Force remove inactive core.
.SH Options that are valid with --remove-detached are:
.TP
.B -d, --device <DEVICE>
Path to core device to be removed from core pool.
.SH Options that are valid with --list-caches (-L) are:
.TP
.B -o, --output-format {table|csv}
Defines output format for list of all cache instances and core devices. It can be either \fBtable\fR (default) or \fBcsv\fR.
.SH Options that are valid with --stats (-P) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance. If this option is
not given, aggregate statistics for whole cache instance are printed instead.
.TP
.B -d, --io-class-id <ID>
Identifier of IO class <0-33>.
.TP
.B -f, --filter <FILTER-SPEC>
Defines filters to be applied. This is comma separated (no
white-spaces allowed) list from following set of available:
.br
1. \fBconf\fR - provides information on configuration.
.br
2. \fBusage\fR - occupancy, free, clean and dirty statistics are printed.
.br
3. \fBreq\fR - IO request level statistics are printed.
.br
4. \fBblk\fR - block level statistics are printed.
.br
5. \fBerr\fR - error statistics are printed.
.br
6. \fBall\fR - all of the above.
.br
Default for --filter option is \fBall\fR.
.TP
.B -o --output-format {table|csv}
Defines output format for statistics. It can be either \fBtable\fR
(default) or \fBcsv\fR.
.SH Options that are valid with --reset-counters (-Z) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance. If this option
is not specified, statistics are reset for all cores within given cache instance.
.SH Options that are valid with --flush-cache (-F) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.SH Options that are valid with --flush-core (-E) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -j, --core-id <ID>
Identifier of core instance <0-4095> within given cache instance.
.SH Options that are valid with --io-class --load-config (-C -C) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -f, --file <FILE>
Configuration file containing IO class definition.
.SH Options that are valid with --io-class --list (-C -L) are:
.TP
.B -i, --cache-id <ID>
Identifier of cache instance <1-16384>.
.TP
.B -o --output-format {table|csv}
Defines output format for printed IO class configuration. It can be either
\fBtable\fR (default) or \fBcsv\fR.
.SH Options that are valid with --nvme --format (-N -F) are:
.TP
.B -d, --device <DEVICE>
Path to NVMe device to be formatted (e.g. /dev/nvme0).
.TP
.B -f, --force
Force to format NVMe device. By default device will not be formatted if utility
detects on the device file system or presence of dirty data after cache dirty
shutdown. This parameter formats NVMe namespace regardless to this situations.
.SH Command --help (-H) does not accept any options.
.BR
.SH Options that are valid with --version (-V) are:
.TP
.B -o --output-format {table|csv}
Defines output format. It can be either \fBtable\fR (default) or \fBcsv\fR.
.SH ENVIRONMENT VARIABLES
Following environment variables affect behavior of casadm administrative utility:
.TP
.B LANG
If en_US.utf-8, en_US.UTF-8 is configured, tables displayed by -L/--list-caches,
-P/--stats and -C -L/--io-class --list are formatted using Unicode table drawing
characters. Otherwise only '+', '|' and '-' are used.
.TP
.B TERM
If xterm or screen is used, colors are used for formatting tables. Otherwise,
color is not used. Additionally colors are NOT used if standard output of
casadm isn't a TTY (i.e. it's output is displayed via less(1), watch(1) or
redirected to a file)
.TP
.B CASADM_COLORS
If this variable is set, colors are used even if TERM isn't set to xterm/screen
or when output is redirected to another program. It's convenient to do:
CASADM_COLORS=true screen 'casadm -P -i 1'
.TP
.B CASADM_NO_LINE_BREAK
If CASADM_NO_LINE_BREAK is set, casadm won't break lines for tables displayed
by -L/--list-caches, -P/--stats and -C -L/--io-class --list
.SH REPORTING BUGS
Patches and issues may be submitted to the official repository at
\fBhttps://open-cas.github.io\fR
.SH SEE ALSO
.TP
casctl(8), opencas.conf(5)

142
utils/casctl Executable file
View File

@@ -0,0 +1,142 @@
#!/usr/bin/env python2
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
from __future__ import print_function
import argparse
import sys
import re
import opencas
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
# Start - load all the caches and add cores
def start():
try:
config = opencas.cas_config.from_file('/etc/opencas/opencas.conf',
allow_incomplete=True)
except Exception as e:
eprint(e)
eprint('Unable to parse config file.')
exit(1)
for cache in config.caches.values():
try:
opencas.start_cache(cache, True)
except opencas.casadm.CasadmError as e:
eprint('Unable to load cache {0} ({1}). Reason:\n{2}'
.format(cache.cache_id, cache.device, e.result.stderr))
# Initial cache start
def add_core_recursive(core, config):
with_error = False
if core.added:
return with_error
if core.marked:
eprint('Unable to add core {0} to cache {1}. Reason:\nRecursive core configuration!'
.format(core.device, core.cache_id))
exit(3)
core.marked = True
match = re.match('/dev/cas(\d)-(\d).*', core.device)
if match:
cache_id,core_id = match.groups()
with_error = add_core_recursive(config.caches[int(cache_id)].cores[int(core_id)], config)
try:
opencas.add_core(core, False)
core.added = True
except opencas.casadm.CasadmError as e:
eprint('Unable to add core {0} to cache {1}. Reason:\n{2}'
.format(core.device, core.cache_id, e.result.stderr))
with_error = True
return with_error
def init(force):
exit_code = 0
try:
config = opencas.cas_config.from_file('/etc/opencas/opencas.conf')
except Exception as e:
eprint(e)
eprint('Unable to parse config file.')
exit(1)
if not force:
for cache in config.caches.values():
try:
status = opencas.check_cache_device(cache.device)
if status['Is cache'] == 'yes' and status['Cache dirty'] == 'yes':
eprint('Unable to perform initial configuration.\n' \
'One of cache devices contains dirty data.')
exit(1)
except opencas.casadm.CasadmError as e:
eprint('Unable to check status of device {0}. Reason:\n{1}'
.format(cache.device, e.result.stderr))
exit(e.result.exit_code)
for cache in config.caches.values():
try:
opencas.start_cache(cache, False, force)
except opencas.casadm.CasadmError as e:
eprint('Unable to start cache {0} ({1}). Reason:\n{2}'
.format(cache.cache_id, cache.device, e.result.stderr))
exit_code = 2
try:
opencas.configure_cache(cache)
except opencas.casadm.CasadmError as e:
eprint('Unable to configure cache {0} ({1}). Reason:\n{2}'
.format(cache.cache_id, cache.device, e.result.stderr))
exit_code = 2
for core in config.cores:
core.added = False
core.marked = False
for core in config.cores:
with_error = add_core_recursive(core, config)
if with_error:
exit_code = 2
exit(exit_code)
# Stop - detach cores and stop caches
def stop(flush):
try:
opencas.stop(flush)
except Exception as e:
eprint(e)
# Command line arguments parsing
class cas:
def __init__(self):
parser = argparse.ArgumentParser(prog = 'cas')
subparsers = parser.add_subparsers(title = 'actions')
parser_init = subparsers.add_parser('init', help = 'Setup initial configuration')
parser_init.set_defaults(command='init')
parser_init.add_argument ('--force', action='store_true', help = 'Force cache start')
parser_start = subparsers.add_parser('start', help = 'Start cache configuration')
parser_start.set_defaults(command='start')
parser_stop = subparsers.add_parser('stop', help = 'Stop cache configuration')
parser_stop.set_defaults(command='stop')
parser_stop.add_argument ('--flush', action='store_true', help = 'Flush data before stopping')
args = parser.parse_args(sys.argv[1:])
getattr(self, 'command_' + args.command)(args)
def command_init(self, args):
init(args.force)
def command_start(self, args):
start()
def command_stop(self, args):
stop(args.flush)
if __name__ == '__main__':
cas()

63
utils/casctl.8 Normal file
View File

@@ -0,0 +1,63 @@
.TH casctl.8 __CAS_DATE__ v__CAS_VERSION__
.SH NAME
casctl \- whole-configuration-manager for Open CAS.
.SH SYNOPSIS
\fBcasctl\fR <command> [options...]
.SH COPYRIGHT
Copyright(c) 2012-2019 by the Intel Corporation.
.SH COMMANDS
.TP
.B start
Start all cache instances.
.TP
.B stop
Stop all cache instances.
.TP
.B init
Initial configuration of caches and core devices.
.br
.B CAUTION
.br
May be used if there is no metadata on cache device or if metatata exists, then only if it's all clean.
.TP
.B -h, --help
.SH OPTIONS
.TP
.SH Command start does not accept any options.
.TP
.SH Options that are valid with stop are:
.TP
.B --flush
Flush data before stopping.
.TP
.SH Options that are valid with init are:
.TP
.B --force
Force cache start even if cache device contains partitions or metadata from previously running cache instances.
.TP
.SH Command --help (-h) does not accept any options.
.SH REPORTING BUGS
Patches and issues may be submitted to the official repository at
\fBhttps://open-cas.github.io\fR
.SH SEE ALSO
.TP
casadm(8), opencas.conf(5)

24
utils/ext3-config.csv Normal file
View File

@@ -0,0 +1,24 @@
IO class id,IO class name,Eviction priority,Allocation
0,Unclassified,22,1
1,Superblock,0,1
2,GroupDesc,1,1
3,BlockBitmap,2,1
4,InodeBitmap,3,1
5,Inode,4,1
6,IndirectBlk,5,1
7,Directory,6,1
8,Journal,7,1
10,Xatrr,8,1
11,<=4KiB,9,1
12,<=16KiB,10,1
13,<=64KiB,11,1
14,<=256KiB,12,1
15,<=1MiB,13,1
16,<=4MiB,14,1
17,<=16MiB,15,1
18,<=64MiB,16,1
19,<=256MiB,17,1
20,<=1GiB,18,1
21,>1GiB,19,1
22,O_DIRECT,20,1
23,Misc,21,1
1 IO class id IO class name Eviction priority Allocation
2 0 Unclassified 22 1
3 1 Superblock 0 1
4 2 GroupDesc 1 1
5 3 BlockBitmap 2 1
6 4 InodeBitmap 3 1
7 5 Inode 4 1
8 6 IndirectBlk 5 1
9 7 Directory 6 1
10 8 Journal 7 1
11 10 Xatrr 8 1
12 11 <=4KiB 9 1
13 12 <=16KiB 10 1
14 13 <=64KiB 11 1
15 14 <=256KiB 12 1
16 15 <=1MiB 13 1
17 16 <=4MiB 14 1
18 17 <=16MiB 15 1
19 18 <=64MiB 16 1
20 19 <=256MiB 17 1
21 20 <=1GiB 18 1
22 21 >1GiB 19 1
23 22 O_DIRECT 20 1
24 23 Misc 21 1

25
utils/ext4-config.csv Normal file
View File

@@ -0,0 +1,25 @@
IO class id,IO class name,Eviction priority,Allocation
0,Unclassified,23,1
1,Superblock,0,1
2,GroupDesc,1,1
3,BlockBitmap,2,1
4,InodeBitmap,3,1
5,Inode,4,1
6,IndirectBlk,5,1
7,Directory,6,1
8,Journal,7,1
9,Extent,8,1
10,Xatrr,9,1
11,<=4KiB,10,1
12,<=16KiB,11,1
13,<=64KiB,12,1
14,<=256KiB,13,1
15,<=1MiB,14,1
16,<=4MiB,15,1
17,<=16MiB,16,1
18,<=64MiB,17,1
19,<=256MiB,18,1
20,<=1GiB,19,1
21,>1GiB,20,1
22,O_DIRECT,21,1
23,Misc,22,1
1 IO class id IO class name Eviction priority Allocation
2 0 Unclassified 23 1
3 1 Superblock 0 1
4 2 GroupDesc 1 1
5 3 BlockBitmap 2 1
6 4 InodeBitmap 3 1
7 5 Inode 4 1
8 6 IndirectBlk 5 1
9 7 Directory 6 1
10 8 Journal 7 1
11 9 Extent 8 1
12 10 Xatrr 9 1
13 11 <=4KiB 10 1
14 12 <=16KiB 11 1
15 13 <=64KiB 12 1
16 14 <=256KiB 13 1
17 15 <=1MiB 14 1
18 16 <=4MiB 15 1
19 17 <=16MiB 16 1
20 18 <=64MiB 17 1
21 19 <=256MiB 18 1
22 20 <=1GiB 19 1
23 21 >1GiB 20 1
24 22 O_DIRECT 21 1
25 23 Misc 22 1

15
utils/ioclass-config.csv Normal file
View File

@@ -0,0 +1,15 @@
IO class id,IO class name,Eviction priority,Allocation
0,unclassified,22,1
1,metadata&done,0,1
11,file_size:le:4096&done,9,1
12,file_size:le:16384&done,10,1
13,file_size:le:65536&done,11,1
14,file_size:le:262144&done,12,1
15,file_size:le:1048576&done,13,1
16,file_size:le:4194304&done,14,1
17,file_size:le:16777216&done,15,1
18,file_size:le:67108864&done,16,1
19,file_size:le:268435456&done,17,1
20,file_size:le:1073741824&done,18,1
21,file_size:gt:1073741824&done,19,1
22,direct&done,20,1
1 IO class id IO class name Eviction priority Allocation
2 0 unclassified 22 1
3 1 metadata&done 0 1
4 11 file_size:le:4096&done 9 1
5 12 file_size:le:16384&done 10 1
6 13 file_size:le:65536&done 11 1
7 14 file_size:le:262144&done 12 1
8 15 file_size:le:1048576&done 13 1
9 16 file_size:le:4194304&done 14 1
10 17 file_size:le:16777216&done 15 1
11 18 file_size:le:67108864&done 16 1
12 19 file_size:le:268435456&done 17 1
13 20 file_size:le:1073741824&done 18 1
14 21 file_size:gt:1073741824&done 19 1
15 22 direct&done 20 1

56
utils/open-cas-loader Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python2
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
from __future__ import print_function
import subprocess
import time
import opencas
import sys
import os
import syslog as sl
def wait_for_cas_ctrl():
for i in range(30): # timeout 30s
if os.path.exists('/dev/cas_ctrl'):
return
time.sleep(1)
try:
subprocess.call(['/sbin/modprobe', 'cas_cache'])
except:
sl.syslog(sl.LOG_ERR, 'Unable to probe cas_cache module')
exit(1)
try:
config = opencas.cas_config.from_file('/etc/opencas/opencas.conf',
allow_incomplete=True)
except Exception as e:
sl.syslog(sl.LOG_ERR,
'Unable to load opencas config. Reason: {0}'.format(str(e)))
exit(1)
for cache in config.caches.values():
if sys.argv[1] == os.path.realpath(cache.device):
try:
wait_for_cas_ctrl()
opencas.start_cache(cache, True)
except opencas.casadm.CasadmError as e:
sl.syslog(sl.LOG_WARNING,
'Unable to load cache {0} ({1}). Reason: {2}'
.format(cache.cache_id, cache.device, e.result.stderr))
exit(e.result.exit_code)
exit(0)
for core in cache.cores.values():
if sys.argv[1] == os.path.realpath(core.device):
try:
wait_for_cas_ctrl()
opencas.add_core(core, True)
except opencas.casadm.CasadmError as e:
sl.syslog(sl.LOG_WARNING,
'Unable to attach core {0} from cache {1}. Reason: {2}'
.format(core.device, cache.cache_id, e.result.stderr))
exit(e.result.exit_code)
exit(0)

26
utils/open-cas-mount-utility Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
# Find all mount units, cut to remove list-units decorations
logger "Open CAS Mount Utility checking for $1 FS label..."
MOUNT_UNITS=`systemctl --plain list-units | grep \.mount | grep -v '\-\.mount' | awk '{print $1}'`
for unit in $MOUNT_UNITS
do
# Find BindsTo keyword, pry out FS label from the .device unit name
label=`systemctl show $unit | grep BindsTo | sed "s/.*label\-\(.*\)\.device/\1/;tx;d;:x"`
if [ "$label" == "" ]; then
continue
fi
label_unescaped=$(systemd-escape -u $(systemd-escape -u $label))
if [ "$label_unescaped" == "$1" ]; then
# If FS label matches restart unit
logger "Open CAS Mount Utility restarting $unit..."
systemctl restart $unit &> /dev/null
exit 0
fi
done

57
utils/open-cas-shutdown Normal file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
#
# open-cas-shutdown Stops Open CAS
#
# chkconfig: 235 05 95
# description: Open Cache Acceleration Software Shutdown Trigger
#
# processname: open-cas-shutdown
### BEGIN INIT INFO
# Provides: open-cas-shutdown
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Default-Start: 2 3 5
# Default-Stop: 0 1 6
# Short-Description: Open Cache Acceleration Software Shutdown Trigger
# Description: Open Cache Acceleration Software Shutdown Trigger
### END INIT INFO
# Execution flow
runfile=/var/lock/subsys/open-cas-shutdown
function umount_cache_volumes()
{
BLOCK_DEV_PREFIX=/dev/cas
INSTANCES=`ls ${BLOCK_DEV_PREFIX}* | egrep [1-9][0-9]*-[1-9][0-9]*`
for inst in $INSTANCES ; do
# Umount any mounted Open CAS devices first
if [[ `cat /etc/mtab | grep $inst | wc -l` -gt 0 ]] ; then
umount $inst &> /dev/null
fi
done
}
case "$1" in
start|restart|reload)
mkdir -p `dirname $runfile`
touch $runfile
exit 0
;;
status)
exit 0
;;
stop)
umount_cache_volumes
/sbin/cas stop
rm -f $runfile
exit $?
;;
*)
exit 1
esac

View File

@@ -0,0 +1,14 @@
[Unit]
Description=Open Cache Acceleration Software Shutdown Trigger
After=umount.target
Before=final.target
JobTimeoutSec=604800
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/sbin/casctl stop
TimeoutStopSec=604800
[Install]
WantedBy=final.target

10
utils/open-cas.shutdown Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
# systemd-shutdown plugin to stop all remaining Open CAS devices
/usr/bin/echo "Open CAS cleanup handler" > /dev/kmsg
/sbin/casctl stop

29
utils/opencas.conf Normal file
View File

@@ -0,0 +1,29 @@
version=19.3.0
# Version tag has to be first line in this file
#
# Open CAS configuration file - for reference on syntax
# of this file please refer to appropriate documentation
# NOTES:
# 1) It is highly recommended to specify cache/core device using path
# that is constant across reboots - e.g. disk device links in
# /dev/disk/by-id/, preferably those using device WWN if available:
# /dev/disk/by-id/wwn-0x123456789abcdef0
# Referencing devices via /dev/sd* may result in cache misconfiguration after
# system reboot due to change(s) in drive order.
## Caches configuration section
[caches]
## Cache ID Cache device Cache mode Extra fields (optional)
## Uncomment and edit the below line for cache configuration
#1 /dev/disk/by-id/nvme-INTEL_SSDP.. WT
## Core devices configuration
[cores]
## Cache ID Core ID Core device
## Uncomment and edit the below line for core configuration
#1 1 /dev/disk/by-id/wwn-0x123456789abcdef0
## To specify use of the IO Classification file, place content of the following line in the
## Caches configuration section under Extra fields (optional)
## ioclass_file=/etc/opencas/ioclass-config.csv

61
utils/opencas.conf.5 Normal file
View File

@@ -0,0 +1,61 @@
.TH opencas.conf 5 __CAS_DATE__ v_CAS_VERSION__
.SH NAME
opencas.conf \- cas configuration file.
.SH SYNOPSIS
.B /etc/opencas/opencas.conf
.SH COPYRIGHT
Copyright(c) 2012-2019 by the Intel Corporation.
.SH DESCRIPTION
Contains configurations for cache and core devices to run cache on system startup. Constist of following sections:
.RS 3
.TP
\fBversion\fR First line has to be version tag.
.TP
\fB[caches]\fR Caches configuration. Following columns are required:
.RS 5
.IP
Cache ID <1-16384>
.br
Cache device <DEVICE>
.br
Cache mode {wt|wb|wa|pt}
.br
Extra fields (optional) ioclass_file=<file>,cleaning_policy=<alru,nop>
.RE
.TP
\fB[cores]\fR Cores configuration. Following columns are required:
.RS 5
.IP
Cache ID <1-16384>
.br
Core ID <0-4095>
.br
Core device <DEVICE>
.br
.RE
.TP
\fBNOTES\fR
.RS
1) It is highly recommended to specify cache/core device using path that is constant across reboots - e.g. disk device links in /dev/disk/by-id/, preferably those using device WWN if available: /dev/disk/by-id/wwn-0x123456789abcdef0. Referencing devices via /dev/sd* may result in cache misconfiguration after system reboot due to change(s) in drive order.
.TP
2) To specify use of the IC Classification file, place ioclass_file=path/to/file.csv in caches configuration section under Extra fields (optional)
.SH FILES
.TP
/etc/opencas/opencas.conf
Contains configurations for cache and core devices to run cache on system startup or via cas tool.
.SH REPORTING BUGS
Patches and issues may be submitted to the official repository at
\fBhttps://open-cas.github.io\fR
.SH SEE ALSO
.TP
casctl(8), casadm(8)

678
utils/opencas.py Normal file
View File

@@ -0,0 +1,678 @@
#!/usr/bin/env python2
#
# Copyright(c) 2012-2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
import subprocess
import csv
import re
import os
import stat
# Casadm functionality
class casadm:
casadm_path = '/sbin/casadm'
class result:
def __init__(self, cmd):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.exit_code = p.wait()
output = p.communicate()
self.stdout = output[0]
self.stderr = output[1]
class CasadmError(Exception):
def __init__(self, result):
super(casadm.CasadmError, self).__init__('casadm error')
self.result = result
@classmethod
def run_cmd(cls, cmd):
result = cls.result(cmd)
if result.exit_code != 0:
raise cls.CasadmError(result)
return result
@classmethod
def get_version(cls):
cmd = [cls.casadm_path,
'--version',
'--output-format', 'csv']
return cls.run_cmd(cmd)
@classmethod
def list_caches(cls):
cmd = [cls.casadm_path,
'--list-caches',
'--output-format', 'csv']
return cls.run_cmd(cmd)
@classmethod
def check_cache_device(cls, device):
cmd = [cls.casadm_path,
'--script',
'--check-cache-device',
'--cache-device', device]
return cls.run_cmd(cmd)
@classmethod
def start_cache(cls, device, cache_id=None, cache_mode=None,
cache_line_size=None, load=False, force=False):
cmd = [cls.casadm_path,
'--start-cache',
'--cache-device', device]
if cache_id:
cmd += ['--cache-id', str(cache_id)]
if cache_mode:
cmd += ['--cache-mode', cache_mode]
if cache_line_size:
cmd += ['--cache-line-size', str(cache_line_size)]
if load:
cmd += ['--load']
if force:
cmd += ['--force']
return cls.run_cmd(cmd)
@classmethod
def add_core(cls, device, cache_id, core_id=None, try_add=False):
cmd = [cls.casadm_path,
'--script',
'--add-core',
'--core-device', device,
'--cache-id', str(cache_id)]
if core_id is not None:
cmd += ['--core-id', str(core_id)]
if try_add:
cmd += ['--try-add']
return cls.run_cmd(cmd)
@classmethod
def stop_cache(cls, cache_id, no_flush=False):
cmd = [cls.casadm_path,
'--stop-cache',
'--cache-id', str(cache_id)]
if no_flush:
cmd += ['--no-data-flush']
return cls.run_cmd(cmd)
@classmethod
def remove_core(cls, cache_id, core_id, detach=False, force=False):
cmd = [cls.casadm_path,
'--script',
'--remove-core',
'--cache-id', str(cache_id),
'--core-id', str(core_id)]
if detach:
cmd += ['--detach']
if force:
cmd += ['--no-flush']
return cls.run_cmd(cmd)
@classmethod
def set_param(cls, namespace, cache_id, **kwargs):
cmd = [cls.casadm_path,
'--set-param', '--name', namespace,
'--cache-id', str(cache_id)]
for param, value in kwargs.items():
cmd += ['--'+param.replace('_', '-'), str(value)]
return cls.run_cmd(cmd)
@classmethod
def get_params(cls, namespace, cache_id, **kwargs):
cmd = [cls.casadm_path,
'--get-param', '--name', namespace,
'--cache-id', str(cache_id)]
for param, value in kwargs.items():
cmd += ['--'+param.replace('_', '-'), str(value)]
cmd += ['-o', 'csv']
return cls.run_cmd(cmd)
@classmethod
def flush_parameters(cls, cache_id, policy_type):
cmd = [cls.casadm_path,
'--flush-parameters',
'--cache-id', str(cache_id),
'--cleaning-policy-type', policy_type]
return cls.run_cmd(cmd)
@classmethod
def io_class_load_config(cls, cache_id, ioclass_file):
cmd = [cls.casadm_path,
'--io-class',
'--load-config',
'--cache-id', str(cache_id),
'--file', ioclass_file]
return cls.run_cmd(cmd)
# Configuration file parser
class cas_config(object):
default_location = '/etc/opencas/opencas.conf'
class ConflictingConfigException(ValueError):
pass
class AlreadyConfiguredException(ValueError):
pass
@staticmethod
def get_by_id_path(path):
for id_path in os.listdir('/dev/disk/by-id'):
full_path = '/dev/disk/by-id/{0}'.format(id_path)
if os.path.realpath(full_path) == os.path.realpath(path):
return full_path
raise ValueError('By-id device link not found for {0}'.format(path))
@staticmethod
def check_block_device(path):
if not os.path.exists(path) and path.startswith('/dev/cas'):
return
try:
mode = os.stat(path).st_mode
except:
raise ValueError('{0} not found'.format(path))
if not stat.S_ISBLK(mode):
raise ValueError('{0} is not block device'.format(path))
class cache_config(object):
def __init__(self, cache_id, device, cache_mode, **params):
self.cache_id = int(cache_id)
self.device = device
self.cache_mode = cache_mode
self.params = params
self.cores = dict()
@classmethod
def from_line(cls, line, allow_incomplete=False):
values = line.split()
if len(values) < 3:
raise ValueError('Invalid cache configuration (too few columns)')
elif len(values) > 4:
raise ValueError('Invalid cache configuration (too many columns)')
cache_id = int(values[0])
device = values[1]
cache_mode = values[2].lower()
params = dict()
if len(values) > 3:
for param in values[3].split(','):
param_name, param_value = param.split('=')
if param_name in params:
raise ValueError('Invalid cache configuration (repeated parameter')
params[param_name] = param_value
cache_config = cls(cache_id, device, cache_mode, **params)
cache_config.validate_config(False, allow_incomplete)
return cache_config
def validate_config(self, force, allow_incomplete=False):
type(self).check_cache_id_valid(self.cache_id)
self.check_recursive()
self.check_cache_mode_valid(self.cache_mode)
for param_name, param_value in self.params.iteritems():
self.validate_parameter(param_name, param_value)
if not allow_incomplete:
cas_config.check_block_device(self.device)
if not force:
self.check_cache_device_empty()
def validate_parameter(self, param_name, param_value):
if param_name == 'ioclass_file':
if not os.path.exists(param_value):
raise ValueError('Incorrect path to io_class file')
elif param_name == 'cleaning_policy':
self.check_cleaning_policy_valid(param_value)
elif param_name == 'cache_line_size':
self.check_cache_line_size_valid(param_value)
else:
raise ValueError('{0} is unknown parameter name'.format(param_name))
@staticmethod
def check_cache_id_valid(cache_id):
if not 1 <= int(cache_id) <= 16384:
raise ValueError('{0} is invalid cache id'.format(cache_id))
def check_cache_device_empty(self):
try:
result = casadm.run_cmd(['lsblk', '-o', 'NAME', '-l', '-n', self.device])
except:
# lsblk returns non-0 if it can't probe for partitions
# this means that we're probably dealing with atomic device
# let it through
return
if len(list(filter(lambda a: a != '', result.stdout.split('\n')))) > 1:
raise ValueError(
'Partitions found on device {0}. Use force option to ignore'.
format(self.device))
def check_cache_mode_valid(self, cache_mode):
if cache_mode.lower() not in ['wt', 'pt', 'wa', 'wb']:
raise ValueError('Invalid cache mode {0}'.format(cache_mode))
def check_cleaning_policy_valid(self, cleaning_policy):
if cleaning_policy.lower() not in ['acp', 'alru', 'nop']:
raise ValueError('{0} is invalid cleaning policy name'.format(
cleaning_policy))
def check_cache_line_size_valid(self, cache_line_size):
if cache_line_size not in ['4', '8', '16', '32', '64']:
raise ValueError('{0} is invalid cache line size'.format(
cache_line_size))
def check_recursive(self):
if not self.device.startswith('/dev/cas'):
return
ids = self.device.split('/dev/cas')[1]
device_cache_id, _ = ids.split('-')
if int(device_cache_id) == self.cache_id:
raise ValueError('Recursive configuration detected')
def to_line(self):
ret = '{0}\t{1}\t{2}'.format(self.cache_id, self.device, self.cache_mode)
if len(self.params) > 0:
i = 0
for param, value in self.params.iteritems():
if i > 0:
ret += ','
else:
ret += '\t'
ret += '{0}={1}'.format(param, value)
i += 1
ret += '\n'
return ret
class core_config(object):
def __init__(self, cache_id, core_id, path):
self.cache_id = int(cache_id)
self.core_id = int(core_id)
self.device = path
@classmethod
def from_line(cls, line, allow_incomplete=False):
values = line.split()
if len(values) > 3:
raise ValueError('Invalid core configuration (too many columns)')
elif len(values) < 3:
raise ValueError('Invalid core configuration (too few columns)')
cache_id = int(values[0])
core_id = int(values[1])
device = values[2]
core_config = cls(cache_id, core_id, device)
core_config.validate_config(allow_incomplete)
return core_config
def validate_config(self, allow_incomplete=False):
self.check_core_id_valid()
self.check_recursive()
cas_config.cache_config.check_cache_id_valid(self.cache_id)
if not allow_incomplete:
cas_config.check_block_device(self.device)
def check_core_id_valid(self):
if not 0 <= int(self.core_id) <= 4095:
raise ValueError('{0} is invalid core id'.format(self.core_id))
def check_recursive(self):
if not self.device.startswith('/dev/cas'):
return
ids = self.device.split('/dev/cas')[1]
device_cache_id, _ = ids.split('-')
if int(device_cache_id) == self.cache_id:
raise ValueError('Recursive configuration detected')
def to_line(self):
return '{0}\t{1}\t{2}\n'.format(self.cache_id, self.core_id, self.device)
def __init__(self, caches=None, cores=None, version_tag=None):
self.caches = caches if caches else dict()
self.cores = cores if cores else list()
self.version_tag = version_tag
@classmethod
def from_file(cls, config_file, allow_incomplete=False):
section_caches = False
section_cores = False
try:
with open(config_file, 'r') as conf:
version_tag = conf.readline()
if not re.findall(r'^version=.*$', version_tag):
raise ValueError('No version tag found!')
config = cls(version_tag=version_tag)
for line in conf:
line = line.split('#')[0].rstrip()
if not line:
continue
if line == '[caches]':
section_caches = True
continue
if line == '[cores]':
section_caches = False
section_cores = True
continue
if section_caches:
cache = cas_config.cache_config.from_line(line, allow_incomplete)
config.insert_cache(cache)
elif section_cores:
core = cas_config.core_config.from_line(line, allow_incomplete)
config.insert_core(core)
except ValueError:
raise
except IOError:
raise Exception('Couldn\'t open config file')
except:
raise
return config
def insert_cache(self, new_cache_config):
if new_cache_config.cache_id in self.caches:
if (os.path.realpath(self.caches[new_cache_config.cache_id].device)
!= os.path.realpath(new_cache_config.device)):
raise cas_config.ConflictingConfigException(
'Other cache device configured under this id')
else:
raise cas_config.AlreadyConfiguredException(
'Cache already configured')
for cache_id, cache in self.caches.iteritems():
if cache_id != new_cache_config.cache_id:
if (os.path.realpath(new_cache_config.device)
== os.path.realpath(cache.device)):
raise cas_config.ConflictingConfigException(
'This cache device is already configured as a cache')
for _, core in cache.cores.iteritems():
if (os.path.realpath(core.device)
== os.path.realpath(new_cache_config.device)):
raise cas_config.ConflictingConfigException(
'This cache device is already configured as a core')
try:
new_cache_config.device = cas_config.get_by_id_path(new_cache_config.device)
except:
pass
self.caches[new_cache_config.cache_id] = new_cache_config
def insert_core(self, new_core_config):
if new_core_config.cache_id not in self.caches:
raise KeyError('Cache id {0} doesn\'t exist'.format(new_core_config.cache_id))
try:
for cache_id, cache in self.caches.iteritems():
if (os.path.realpath(cache.device)
== os.path.realpath(new_core_config.device)):
raise cas_config.ConflictingConfigException(
'Core device already configured as a cache')
for core_id, core in cache.cores.iteritems():
if (cache_id == new_core_config.cache_id
and core_id == new_core_config.core_id):
if (os.path.realpath(core.device)
== os.path.realpath(new_core_config.device)):
raise cas_config.AlreadyConfiguredException(
'Core already configured')
else:
raise cas_config.ConflictingConfigException(
'Other core device configured under this id')
else:
if (os.path.realpath(core.device)
== os.path.realpath(new_core_config.device)):
raise cas_config.ConflictingConfigException(
'This core device is already configured as a core')
except KeyError:
pass
try:
new_core_config.device = cas_config.get_by_id_path(new_core_config.device)
except:
pass
self.caches[new_core_config.cache_id].cores[new_core_config.core_id] = new_core_config
self.cores += [new_core_config]
def is_empty(self):
if len(self.caches) > 0 or len(self.cores) > 0:
return False
return True
def write(self, config_file):
try:
with open(config_file, 'w') as conf:
conf.write('{0}\n'.format(self.version_tag))
conf.write('# This config was automatically generated\n')
conf.write('[caches]\n')
for _, cache in self.caches.iteritems():
conf.write(cache.to_line())
conf.write('\n[cores]\n')
for core in self.cores:
conf.write(core.to_line())
except:
raise Exception('Couldn\'t write config file')
# Config helper functions
def start_cache(cache, load, force=False):
casadm.start_cache(
device=cache.device,
cache_id=cache.cache_id,
cache_mode=cache.cache_mode,
cache_line_size=cache.params.get('cache_line_size'),
load=load,
force=force)
def configure_cache(cache):
if cache.params.has_key('cleaning_policy'):
casadm.set_param('cleaning', cache_id=cache.cache_id,
policy=cache.params['cleaning_policy'])
if cache.params.has_key('ioclass_file'):
casadm.io_class_load_config(cache_id=cache.cache_id,
ioclass_file=cache.params['ioclass_file'])
def add_core(core, attach):
casadm.add_core(
device=core.device,
cache_id=core.cache_id,
core_id=core.core_id,
try_add=attach)
# Another helper functions
def is_cache_started(cache_config):
dev_list = get_caches_list()
for dev in dev_list:
if dev['type'] == 'cache' and int(dev['id']) == cache_config.cache_id:
return True
return False
def is_core_added(core_config):
dev_list = get_caches_list()
cache_id = 0
for dev in dev_list:
if dev['type'] == 'cache':
cache_id = int(dev['id'])
if (dev['type'] == 'core' and
cache_id == core_config.cache_id and
int(dev['id']) == core_config.core_id):
return True
return False
def get_caches_list():
result = casadm.list_caches()
return list(csv.DictReader(result.stdout.split('\n')))
def check_cache_device(device):
result = casadm.check_cache_device(device)
return list(csv.DictReader(result.stdout.split('\n')))[0]
def get_cas_version():
version = casadm.get_version()
ret = {}
for line in version.stdout.split('\n')[1:]:
try:
component, version = line.split(',')
except:
continue
ret[component] = version
return ret
class CompoundException(Exception):
def __init__(self):
super(CompoundException, self).__init__()
self.exception_list = list()
def __str__(self):
s = "Multiple exceptions occured:\n" if len(self.exception_list) > 1 else ""
for e in self.exception_list:
s += '{0}\n'.format(str(e))
return s
def add_exception(self, e):
if type(e) is CompoundException:
self.exception_list += e.exception_list
else:
self.exception_list += [e]
def is_empty(self):
return len(self.exception_list) == 0
def raise_nonempty(self):
if self.is_empty():
return
else:
raise self
def detach_core_recursive(cache_id, core_id, flush):
# Catching exceptions is left to uppermost caller of detach_core_recursive
# as the immediate caller that made a recursive call depends on the callee
# to remove core and thus release reference to lower level cache volume.
l_cache_id = ''
for dev in get_caches_list():
if dev['type'] == 'cache':
l_cache_id = dev['id']
elif dev['type'] == 'core' and dev['status'] == 'Active':
if '/dev/cas{0}-{1}'.format(cache_id, core_id) in dev['disk']:
detach_core_recursive(l_cache_id, dev['id'], flush)
elif l_cache_id == cache_id and dev['id'] == core_id and dev['status'] != 'Active':
return
casadm.remove_core(cache_id, core_id, detach = True, force = not flush)
def detach_all_cores(flush):
error = CompoundException()
try:
dev_list = get_caches_list()
except casadm.CasadmError as e:
raise Exception('Unable to list caches. Reason:\n{0}'.format(
e.result.stderr))
except:
raise Exception('Unable to list caches.')
for dev in dev_list:
if dev['type'] == 'cache':
cache_id = dev['id']
elif dev['type'] == 'core' and dev['status'] == "Active":
# In case of exception we proceed with detaching remaining core instances
# to gracefully shutdown as many cache instances as possible.
try:
detach_core_recursive(cache_id, dev['id'], flush)
except casadm.CasadmError as e:
error.add_exception(Exception(
'Unable to detach core {0}. Reason:\n{1}'.format(
dev['disk'], e.result.stderr)))
except:
error.add_exception(Exception(
'Unable to detach core {0}.'.format(dev['disk'])))
error.raise_nonempty()
def stop_all_caches(flush):
error = CompoundException()
try:
dev_list = get_caches_list()
except casadm.CasadmError as e:
raise Exception('Unable to list caches. Reason:\n{0}'.format(
e.result.stderr))
except:
raise Exception('Unable to list caches.')
for dev in dev_list:
if dev['type'] == 'cache':
# In case of exception we proceed with stopping subsequent cache instances
# to gracefully shutdown as many cache instances as possible.
try:
casadm.stop_cache(dev['id'], not flush)
except casadm.CasadmError as e:
error.add_exception(Exception(
'Unable to stop cache {0}. Reason:\n{1}'.format(
dev['disk'], e.result.stderr)))
except:
error.add_exception(Exception(
'Unable to stop cache {0}.'.format(dev['disk'])))
error.raise_nonempty()
def stop(flush):
error = CompoundException()
try:
detach_all_cores(flush)
except Exception as e:
error.add_exception(e)
try:
stop_all_caches(False)
except Exception as e:
error.add_exception(e)
error.raise_nonempty()