pystorcli.storcli

StorCLI python class

  1# -*- coding: utf-8 -*-
  2
  3# Copyright (c) 2018, Martin Dojcak <martin@dojcak.sk>
  4# Copyright (c) 2022, Rafael Leira & Naudit HPCN S.L. <rafael.leira@naudit.es>
  5# See LICENSE for details.
  6
  7'''StorCLI python class
  8'''
  9
 10import os
 11import re
 12import json
 13import shutil
 14import threading
 15import subprocess
 16
 17from . import common
 18from . import exc
 19from . import cmdRunner
 20
 21_SINGLETON_STORCLI_MODULE_LOCK = threading.Lock()
 22_SINGLETON_STORCLI_MODULE_ENABLE = False
 23
 24
 25class StorCLI(object):
 26    """StorCLI command line wrapper
 27
 28    Instance of this class is storcli command line wrapper
 29
 30    Args:
 31        binary (str): storcli binary or full path to the binary
 32
 33    Properties:
 34        cache_enable (boolean): enable disable resposne cache (also setter)
 35        cache (dict): get / set raw cache content
 36
 37    Methods:
 38        run (dict): output data from command line
 39        check_response_status (): check ouput command line status from storcli
 40        clear_cache (): purge cache
 41
 42    TODO:
 43        * implement TTL for cache
 44
 45    """
 46    __singleton_instance = None
 47    __cache_lock = threading.Lock()
 48    __cache_enabled = False
 49    __response_cache = {}
 50    __cmdrunner = None
 51
 52    def __new__(cls, *args, **kwargs):
 53        """Thread safe singleton
 54        """
 55        global _SINGLETON_STORCLI_MODULE_LOCK
 56        with _SINGLETON_STORCLI_MODULE_LOCK:
 57            if _SINGLETON_STORCLI_MODULE_ENABLE:
 58                if StorCLI.__singleton_instance is None:
 59                    StorCLI.__singleton_instance = super(
 60                        StorCLI, cls).__new__(cls)
 61                return StorCLI.__singleton_instance
 62            else:
 63                return super(StorCLI, cls).__new__(cls)
 64
 65    def __init__(self, binary='storcli64', cmdrunner=None):
 66        """Constructor - create StorCLI object wrapper
 67
 68        Args:
 69            binary (str): storcli binary or full path to the binary
 70        """
 71
 72        if cmdrunner is None:
 73            if self.__cmdrunner is None:
 74                self.__cmdrunner = cmdRunner.CMDRunner()
 75        else:
 76            self.__cmdrunner = cmdrunner
 77
 78        if _SINGLETON_STORCLI_MODULE_ENABLE:
 79            if not hasattr(self, '_storcli'):
 80                # do not override _storcli in singleton if already exist
 81                self._storcli = self.__cmdrunner.binaryCheck(binary)
 82
 83        if not _SINGLETON_STORCLI_MODULE_ENABLE:
 84            # dont share singleton lock and binary
 85            self._storcli = self.__cmdrunner.binaryCheck(binary)
 86            self.__cache_lock = threading.Lock()
 87
 88    @property
 89    def cache_enable(self):
 90        """Enable/Disable resposne cache (atomic)
 91
 92        Returns:
 93            bool: true/false
 94        """
 95
 96        return self.__cache_enabled
 97
 98    @cache_enable.setter
 99    def cache_enable(self, value):
100        with self.__cache_lock:
101            self.__cache_enabled = value
102
103    def clear_cache(self):
104        """Clear cache (atomic)
105        """
106        with self.__cache_lock:
107            self.__response_cache = {}
108
109    @property
110    def cache(self):
111        """Get/Set raw cache
112
113        Args:
114            (dict): raw cache
115
116        Returns:
117            (dict): cache
118        """
119        return self.__response_cache
120
121    @cache.setter
122    def cache(self, value):
123        with self.__cache_lock:
124            self.__response_cache = value
125
126    @staticmethod
127    def check_response_status(cmd, out):
128        """Check ouput command line status from storcli.
129
130        Args:
131            cmd (list of str): full command line
132            out (dict): output from command line
133
134        Raises:
135            StorCliCmdError
136        """
137        cmd_status = common.response_cmd(out)
138        if cmd_status['Status'] == 'Failure':
139            if 'Detailed Status' in cmd_status:
140                raise exc.StorCliCmdError(
141                    cmd, "{0}".format(cmd_status['Detailed Status']))
142            else:
143                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))
144
145    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
146        """Execute storcli command line with arguments.
147
148        Run command line and check output for errors.
149
150        Args:
151            args (list of str): cmd line arguments (without binary)
152            stdout (fd): controll subprocess stdout fd
153            stderr (fd): controll subporcess stderr fd
154            **kwargs: arguments to subprocess run
155
156        Returns:
157            dict: output data from command line
158
159        Raises:
160            exc.StorCliCmdError
161            exc.StorCliRunTimeError
162            exc.StorCliRunTimeout
163        """
164        cmd = [self._storcli]
165        cmd.extend(args)
166        # output in JSON format
167        cmd.append('J')
168        cmd_cache_key = ''.join(cmd)
169
170        if self.cache_enable:
171            if cmd_cache_key in self.__response_cache:
172                return self.__response_cache[cmd_cache_key]
173
174        with self.__cache_lock:
175            try:
176                ret = self.__cmdrunner.run(
177                    args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs)
178                try:
179                    ret_json = json.loads(ret.stdout)
180                    self.check_response_status(cmd, ret_json)
181                    ret.check_returncode()
182                    if self.cache_enable:
183                        self.__response_cache[cmd_cache_key] = ret_json
184                    return ret_json
185                except json.JSONDecodeError:
186                    # :/
187                    err = re.search('(^.*)Storage.*Command.*$',
188                                    ret.stdout, re.MULTILINE | re.DOTALL).group(1)
189                    raise exc.StorCliCmdError(cmd, err)
190            except subprocess.TimeoutExpired as err:
191                raise exc.StorCliRunTimeout(err)
192            except subprocess.SubprocessError as err:
193                raise exc.StorCliRunTimeError(err)
194
195    # Singleton stuff
196    @staticmethod
197    def __set_singleton(value):
198        global _SINGLETON_STORCLI_MODULE_ENABLE
199        global _SINGLETON_STORCLI_MODULE_LOCK
200        with _SINGLETON_STORCLI_MODULE_LOCK:
201            _SINGLETON_STORCLI_MODULE_ENABLE = value
202
203    @staticmethod
204    def enable_singleton():
205        """Enable StorCLI to be singleton on module level
206
207        Use StorCLI singleton across all objects. All pystorcli 
208        class instances use their own StorCLI object. With enabled cache
209        we can speedup for example metric lookups.
210
211        """
212        StorCLI.__set_singleton(True)
213
214    @staticmethod
215    def disable_singleton():
216        """Disable StoreCLI class as signleton
217        """
218        StorCLI.__set_singleton(False)
219
220    @staticmethod
221    def is_singleton() -> bool:
222        """Check if singleton is enabled
223        """
224        return _SINGLETON_STORCLI_MODULE_ENABLE
class StorCLI:
 26class StorCLI(object):
 27    """StorCLI command line wrapper
 28
 29    Instance of this class is storcli command line wrapper
 30
 31    Args:
 32        binary (str): storcli binary or full path to the binary
 33
 34    Properties:
 35        cache_enable (boolean): enable disable resposne cache (also setter)
 36        cache (dict): get / set raw cache content
 37
 38    Methods:
 39        run (dict): output data from command line
 40        check_response_status (): check ouput command line status from storcli
 41        clear_cache (): purge cache
 42
 43    TODO:
 44        * implement TTL for cache
 45
 46    """
 47    __singleton_instance = None
 48    __cache_lock = threading.Lock()
 49    __cache_enabled = False
 50    __response_cache = {}
 51    __cmdrunner = None
 52
 53    def __new__(cls, *args, **kwargs):
 54        """Thread safe singleton
 55        """
 56        global _SINGLETON_STORCLI_MODULE_LOCK
 57        with _SINGLETON_STORCLI_MODULE_LOCK:
 58            if _SINGLETON_STORCLI_MODULE_ENABLE:
 59                if StorCLI.__singleton_instance is None:
 60                    StorCLI.__singleton_instance = super(
 61                        StorCLI, cls).__new__(cls)
 62                return StorCLI.__singleton_instance
 63            else:
 64                return super(StorCLI, cls).__new__(cls)
 65
 66    def __init__(self, binary='storcli64', cmdrunner=None):
 67        """Constructor - create StorCLI object wrapper
 68
 69        Args:
 70            binary (str): storcli binary or full path to the binary
 71        """
 72
 73        if cmdrunner is None:
 74            if self.__cmdrunner is None:
 75                self.__cmdrunner = cmdRunner.CMDRunner()
 76        else:
 77            self.__cmdrunner = cmdrunner
 78
 79        if _SINGLETON_STORCLI_MODULE_ENABLE:
 80            if not hasattr(self, '_storcli'):
 81                # do not override _storcli in singleton if already exist
 82                self._storcli = self.__cmdrunner.binaryCheck(binary)
 83
 84        if not _SINGLETON_STORCLI_MODULE_ENABLE:
 85            # dont share singleton lock and binary
 86            self._storcli = self.__cmdrunner.binaryCheck(binary)
 87            self.__cache_lock = threading.Lock()
 88
 89    @property
 90    def cache_enable(self):
 91        """Enable/Disable resposne cache (atomic)
 92
 93        Returns:
 94            bool: true/false
 95        """
 96
 97        return self.__cache_enabled
 98
 99    @cache_enable.setter
100    def cache_enable(self, value):
101        with self.__cache_lock:
102            self.__cache_enabled = value
103
104    def clear_cache(self):
105        """Clear cache (atomic)
106        """
107        with self.__cache_lock:
108            self.__response_cache = {}
109
110    @property
111    def cache(self):
112        """Get/Set raw cache
113
114        Args:
115            (dict): raw cache
116
117        Returns:
118            (dict): cache
119        """
120        return self.__response_cache
121
122    @cache.setter
123    def cache(self, value):
124        with self.__cache_lock:
125            self.__response_cache = value
126
127    @staticmethod
128    def check_response_status(cmd, out):
129        """Check ouput command line status from storcli.
130
131        Args:
132            cmd (list of str): full command line
133            out (dict): output from command line
134
135        Raises:
136            StorCliCmdError
137        """
138        cmd_status = common.response_cmd(out)
139        if cmd_status['Status'] == 'Failure':
140            if 'Detailed Status' in cmd_status:
141                raise exc.StorCliCmdError(
142                    cmd, "{0}".format(cmd_status['Detailed Status']))
143            else:
144                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))
145
146    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
147        """Execute storcli command line with arguments.
148
149        Run command line and check output for errors.
150
151        Args:
152            args (list of str): cmd line arguments (without binary)
153            stdout (fd): controll subprocess stdout fd
154            stderr (fd): controll subporcess stderr fd
155            **kwargs: arguments to subprocess run
156
157        Returns:
158            dict: output data from command line
159
160        Raises:
161            exc.StorCliCmdError
162            exc.StorCliRunTimeError
163            exc.StorCliRunTimeout
164        """
165        cmd = [self._storcli]
166        cmd.extend(args)
167        # output in JSON format
168        cmd.append('J')
169        cmd_cache_key = ''.join(cmd)
170
171        if self.cache_enable:
172            if cmd_cache_key in self.__response_cache:
173                return self.__response_cache[cmd_cache_key]
174
175        with self.__cache_lock:
176            try:
177                ret = self.__cmdrunner.run(
178                    args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs)
179                try:
180                    ret_json = json.loads(ret.stdout)
181                    self.check_response_status(cmd, ret_json)
182                    ret.check_returncode()
183                    if self.cache_enable:
184                        self.__response_cache[cmd_cache_key] = ret_json
185                    return ret_json
186                except json.JSONDecodeError:
187                    # :/
188                    err = re.search('(^.*)Storage.*Command.*$',
189                                    ret.stdout, re.MULTILINE | re.DOTALL).group(1)
190                    raise exc.StorCliCmdError(cmd, err)
191            except subprocess.TimeoutExpired as err:
192                raise exc.StorCliRunTimeout(err)
193            except subprocess.SubprocessError as err:
194                raise exc.StorCliRunTimeError(err)
195
196    # Singleton stuff
197    @staticmethod
198    def __set_singleton(value):
199        global _SINGLETON_STORCLI_MODULE_ENABLE
200        global _SINGLETON_STORCLI_MODULE_LOCK
201        with _SINGLETON_STORCLI_MODULE_LOCK:
202            _SINGLETON_STORCLI_MODULE_ENABLE = value
203
204    @staticmethod
205    def enable_singleton():
206        """Enable StorCLI to be singleton on module level
207
208        Use StorCLI singleton across all objects. All pystorcli 
209        class instances use their own StorCLI object. With enabled cache
210        we can speedup for example metric lookups.
211
212        """
213        StorCLI.__set_singleton(True)
214
215    @staticmethod
216    def disable_singleton():
217        """Disable StoreCLI class as signleton
218        """
219        StorCLI.__set_singleton(False)
220
221    @staticmethod
222    def is_singleton() -> bool:
223        """Check if singleton is enabled
224        """
225        return _SINGLETON_STORCLI_MODULE_ENABLE

StorCLI command line wrapper

Instance of this class is storcli command line wrapper

Args: binary (str): storcli binary or full path to the binary

Properties: cache_enable (boolean): enable disable resposne cache (also setter) cache (dict): get / set raw cache content

Methods: run (dict): output data from command line check_response_status (): check ouput command line status from storcli clear_cache (): purge cache

TODO: * implement TTL for cache

StorCLI(binary='storcli64', cmdrunner=None)
66    def __init__(self, binary='storcli64', cmdrunner=None):
67        """Constructor - create StorCLI object wrapper
68
69        Args:
70            binary (str): storcli binary or full path to the binary
71        """
72
73        if cmdrunner is None:
74            if self.__cmdrunner is None:
75                self.__cmdrunner = cmdRunner.CMDRunner()
76        else:
77            self.__cmdrunner = cmdrunner
78
79        if _SINGLETON_STORCLI_MODULE_ENABLE:
80            if not hasattr(self, '_storcli'):
81                # do not override _storcli in singleton if already exist
82                self._storcli = self.__cmdrunner.binaryCheck(binary)
83
84        if not _SINGLETON_STORCLI_MODULE_ENABLE:
85            # dont share singleton lock and binary
86            self._storcli = self.__cmdrunner.binaryCheck(binary)
87            self.__cache_lock = threading.Lock()

Constructor - create StorCLI object wrapper

Args: binary (str): storcli binary or full path to the binary

cache_enable

Enable/Disable resposne cache (atomic)

Returns: bool: true/false

def clear_cache(self)
104    def clear_cache(self):
105        """Clear cache (atomic)
106        """
107        with self.__cache_lock:
108            self.__response_cache = {}

Clear cache (atomic)

cache

Get/Set raw cache

Args: (dict): raw cache

Returns: (dict): cache

@staticmethod
def check_response_status(cmd, out)
127    @staticmethod
128    def check_response_status(cmd, out):
129        """Check ouput command line status from storcli.
130
131        Args:
132            cmd (list of str): full command line
133            out (dict): output from command line
134
135        Raises:
136            StorCliCmdError
137        """
138        cmd_status = common.response_cmd(out)
139        if cmd_status['Status'] == 'Failure':
140            if 'Detailed Status' in cmd_status:
141                raise exc.StorCliCmdError(
142                    cmd, "{0}".format(cmd_status['Detailed Status']))
143            else:
144                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))

Check ouput command line status from storcli.

Args: cmd (list of str): full command line out (dict): output from command line

Raises: StorCliCmdError

def run(self, args, stdout=-1, stderr=-1, **kwargs)
146    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
147        """Execute storcli command line with arguments.
148
149        Run command line and check output for errors.
150
151        Args:
152            args (list of str): cmd line arguments (without binary)
153            stdout (fd): controll subprocess stdout fd
154            stderr (fd): controll subporcess stderr fd
155            **kwargs: arguments to subprocess run
156
157        Returns:
158            dict: output data from command line
159
160        Raises:
161            exc.StorCliCmdError
162            exc.StorCliRunTimeError
163            exc.StorCliRunTimeout
164        """
165        cmd = [self._storcli]
166        cmd.extend(args)
167        # output in JSON format
168        cmd.append('J')
169        cmd_cache_key = ''.join(cmd)
170
171        if self.cache_enable:
172            if cmd_cache_key in self.__response_cache:
173                return self.__response_cache[cmd_cache_key]
174
175        with self.__cache_lock:
176            try:
177                ret = self.__cmdrunner.run(
178                    args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs)
179                try:
180                    ret_json = json.loads(ret.stdout)
181                    self.check_response_status(cmd, ret_json)
182                    ret.check_returncode()
183                    if self.cache_enable:
184                        self.__response_cache[cmd_cache_key] = ret_json
185                    return ret_json
186                except json.JSONDecodeError:
187                    # :/
188                    err = re.search('(^.*)Storage.*Command.*$',
189                                    ret.stdout, re.MULTILINE | re.DOTALL).group(1)
190                    raise exc.StorCliCmdError(cmd, err)
191            except subprocess.TimeoutExpired as err:
192                raise exc.StorCliRunTimeout(err)
193            except subprocess.SubprocessError as err:
194                raise exc.StorCliRunTimeError(err)

Execute storcli command line with arguments.

Run command line and check output for errors.

Args: args (list of str): cmd line arguments (without binary) stdout (fd): controll subprocess stdout fd stderr (fd): controll subporcess stderr fd **kwargs: arguments to subprocess run

Returns: dict: output data from command line

Raises: exc.StorCliCmdError exc.StorCliRunTimeError exc.StorCliRunTimeout

@staticmethod
def enable_singleton()
204    @staticmethod
205    def enable_singleton():
206        """Enable StorCLI to be singleton on module level
207
208        Use StorCLI singleton across all objects. All pystorcli 
209        class instances use their own StorCLI object. With enabled cache
210        we can speedup for example metric lookups.
211
212        """
213        StorCLI.__set_singleton(True)

Enable StorCLI to be singleton on module level

Use StorCLI singleton across all objects. All pystorcli class instances use their own StorCLI object. With enabled cache we can speedup for example metric lookups.

@staticmethod
def disable_singleton()
215    @staticmethod
216    def disable_singleton():
217        """Disable StoreCLI class as signleton
218        """
219        StorCLI.__set_singleton(False)

Disable StoreCLI class as signleton

@staticmethod
def is_singleton() -> bool:
221    @staticmethod
222    def is_singleton() -> bool:
223        """Check if singleton is enabled
224        """
225        return _SINGLETON_STORCLI_MODULE_ENABLE

Check if singleton is enabled