Source code for otto.appliances.srx

#!/usr/bin/env python
# encoding: utf-8

import os
import re
import logging
from time import sleep
from collections import OrderedDict, defaultdict

instance = os.environ.get('instance') or ''
logger = logging.getLogger('otto' + instance + '.appliances')
logger.addHandler(logging.NullHandler())

from otto.connections.cec import Cec
from otto.connections.ssh_pexpect import Ssh
from otto.lib.otypes import ReturnCode, ApplianceError, ApplianceUsage, AoEAddress, Namespace, Drive
from otto.utils import aoetostr, now, timefmt, since
from otto.lib.pexpect import TIMEOUT, EOF


[docs]class Srx(Cec): """ A class for interacting with the SRX using CEC. Since the commands are basically passed through see the SRX manual for more info. Individual drives may be accessed directly using *obj.sNum* where Num is the slot number:: >> sr = Srx(shelfnum,'en0') >> sr.connect() >> print sr.s1.model 'WD WD6001BKHG-02D22' """ def __init__(self, shelf, iface, password=None, prompt=None, use_slots=None, version=None): self.luncomp = re.compile( r"^\s*?(?P<size>\d+\.\d+)\s+(?P<element>\d+\.\d+\.\d+)\s+(?P<drive>\d+\.\d+|update|missing)\s+(?P<state>([a-z]+,?)+)(\s+)?(?P<percent>\d+\.\d+%)?") self.lunhdr = re.compile( r"^(?P<lun>\d+)\s{1,3}(?P<label>[a-zA-Z0-9_\s\-]{6,17})\s+(?P<status>online|offline)\s+(?P<type>[a-zA-Z0-9]+)\s+(?P<size>\d+\.\d+)\s+(?P<state>\S+)\Z") self.lineterm = '\r+\n' self.driveahdr = None self.sample_warn = None self.confirm = None self.confirm_update_lun_format = None if prompt is None: prompt = r'SRX\s+((shelf\s+(unset|\d.*)>)|EXPERTMODE#)\s+' super(Srx, self).__init__(shelf, iface, password, prompt) self.use_slots = use_slots #: a list of slots to restrict operations to. #: This is set to either 6|7 for backwards compatibile operations #: By default we auto negotioate this upon connection. self.version = None if version: self.version = int(version) else: self.version = version #: A dictionary for caching hard data that is otherwise slow to retrieve. Currently #: there is only 'drives' which contains the last run of the drives command. self.cache = dict() #: Number of slots in this chassis auto determined at connect. self.slots = None
[docs] def connect(self, timeout=10, expectation=True): if self.closed: # in case of reconnect super(Srx, self).connect(timeout=timeout, expectation=expectation) if self.version is None: r = self.run('release').strip() if r.startswith('RELEASE'): r = r.split()[-1] result = re.match(r"^SR[X]?-([0-9]+)\.([0-9]+).*", r) if result: self.version = int(result.group(1)) else: raise ApplianceError('Unable to identify SRX version running in shelf: %s: %s' % (result, r)) self.slots = self._enumerate_slots() for slot in range(int(self.slots)): setattr(self, 's%s' % slot, Drive(self.shelf, slot, self.expert_run))
[docs] def expert_run(self, cmd, expectation=True): if self.version < 7: # there was no expertmode before 7 ret = self.run_and_check(cmd, expectation) else: tprompt = self.prompt self.prompt = 'SRX EXPERTMODE#' self.run('/expertmode') ret = self.run_and_check(cmd, expectation) self.prompt = tprompt self.run('exit') return ret
@property def ipaddress(self): """ The ipaddress command returns a dictionary containing the information of the ipaddress command. Version support: 7 """ ipdd = defaultdict(lambda: {'port': None, 'address': None, 'mask': None, 'multipath': None}) if self.version >= 7: r = self.run_and_check('ipaddress') rs = r.message.splitlines() for l in rs[1:]: vals = l.split() port_num = vals[0][-1] ipdd[port_num] = {} ipdd[port_num]['port'] = vals[0] ipdd[port_num]['address'] = vals[1] ipdd[port_num]['mask'] = vals[2] ipdd[port_num]['multipath'] = vals[3] return ipdd @property def ipgateway(self): """ The ipgateway command returns a string containing the information of the ipgateway command Version support: 7 """ if self.version >= 7: r = self.run_and_check('ipgateway') rs = re.split(self.lineterm, r.message.strip()) return rs[1] else: return str() def run_and_check(self, cmd, expectation=True, force=False, timeout=10): """ Run a command check the result. If the caller cares about failure and the command fails we raise a generic exception. """ logger.info(cmd + " called") result = ReturnCode(True) if not self.confirm: self.confirm = re.compile("Enter\s+'y'\s+for yes,\s+'n'\s+for no\.\s+Continue\?\s+\[n\]") if not self.confirm_update_lun_format: self.confirm_update_lun_format = re.compile(r"Would you like to update the LUN format") if force: t = self.prompt self.prompt = [t, self.confirm, self.confirm_update_lun_format] result.message = self.run(cmd, timeout=timeout) self.prompt = t if self.match_index != 0: result.message = self.run('y') else: result.message = self.run(cmd, timeout=timeout) logger.debug("rx:" + result.message) errors = ['error:', 'usage:', 'directory entry not found', 'unrecoverable failure', 'unknown command', 'Update failed', 'No update files found'] for x in errors: if result.message.count(x): result.status = False break # result.message = result.message.strip().replace('\r\r\n', '\r\n') if not result.status: if expectation: logger.error("%s: %s" % (cmd, result.message)) else: return result raise ApplianceError("'%s' failed: %s" % (cmd, result.message)) return result @property def release(self): """ The release command returns a string containing the currently running release. On SRX6: the commands only returns a single line with the release already running. On SRX7 If a tarc has been uploaded, but the SRX has not been updated, then there will be two fields, like so:: SRX shelf 43> release RELEASE NEXTRELEASE SRX-7.0.0-R6 SRX-7.0.0-R7 SRX shelf 43> """ r = self.run_and_check('release') lines = list() for line in re.split(self.lineterm, r.message.strip()): lines.append(line.strip()) if len(lines) < 1: logger.error("parsing failure: '%s'" % r) return r if len(lines) == 1: # SRX 6.X r = r.message.split(' - ')[0] return r if len(lines) == 2: # SRX 7.X above rel = lines[1] flds = lines[1].split() if len(flds) > 1: rel = flds[0] return rel @property def next_release(self): """ The next_release command returns a string containing the release that will run upon the user executing the 'update' command, after the SRX reboots. If a tarc has been uploaded, but the SRX has not been updated, then there will be two fields, like so:: SRX shelf 43> release RELEASE NEXTRELEASE SRX-7.0.0-R6 SRX-7.0.0-R7 SRX shelf 43> """ r = self.run('release') lines = r.splitlines() if len(lines) < 2: logger.error("parsing failure: '%s'" % r) return r next_rel = None flds = lines[1].split() if len(flds) > 1: next_rel = flds[1] return next_rel @property def model(self): """ The model command returns a string containing the srx model. """ if self.version >= 7: r = self.run_and_check('model') r.message = r.message.splitlines() if len(r.message) > 1: r.message = r.message[1] else: r = self.run_and_check('model') return r.message @property def serial(self): """ The serial command returns a string containing the srx serial. """ if self.version >= 7: r = self.run_and_check('serial') m = r.message.splitlines() if len(m) > 1: r.message = m[1] else: r = self.run_and_check('serial') return r.message @serial.setter def serial(self, sn): """ Set the srx serial number. """ cmd = "serial -s %s" % sn self.run_and_check(cmd) @property def date(self): """ The date command returns a string containing the output of the date command. """ r = self.run_and_check('date') return r.message @property def motd(self): """ The motd command returns a string containing the output of the motd command. Version support: 7 """ if self.version >= 7: return self.run_and_check('motd').message else: return '' @property def list(self): """ see otto.appliances.srx.Srx.luns """ if self.version >= 7: return self.luns else: logger.info("making call to old code for list command") return self._list_6 @property def _list_6(self): cmd = 'list -l' newlun = False luns = list() wlun = dict() r = self.run_and_check(cmd) # this is the meat of the method # if we weren't screen scraping this would be # a little less messy c = r.message.splitlines() for l in c: l = l.strip() if not len(l): return dict() m = l.split()[0] # the first token on the line dots = m.count(".") if not dots: newlun = True raid = False component = False elif dots == 1: raid = True component = False elif dots == 2: component = True raid = False else: return False # is that right? if newlun: if len(wlun): luns.append(wlun) wlun = {} ls = l.split() if ls == 3: wlun['lun'], wlun['size'], wlun['online'] = ls else: wlun['lun'] = ls[0] wlun['size'] = ls[1] wlun['online'] = ls[2] label = ' '.join(ls[3:]).strip().strip("'") wlun['label'] = label wlun['raids'] = list() if wlun['online'] == 'online': wlun['online'] = True else: wlun['online'] = False newlun = False elif raid: try: number, size, kind, state = l.split() percent = None except ValueError: number, size, kind, state, percent = l.split() number = number.split(".")[1] r = {'number': number, 'size': size, 'kind': kind, 'state': state, 'components': list(), 'percent': percent} if wlun.get('raids'): wlun['raids'].append(r) else: wlun['raids'] = [r] elif component: feilds = l.split() if len(feilds) == 4: position, stat, size, device = feilds else: position, stat, size, device, percent = feilds for rd in wlun['raids']: if rd['number'] == position.split('.')[1]: # eg. 7 in 8.7.0 position = position.split('.')[-1] # eg. 0 in 8.7.0 z = {'position': position, 'stat': stat, 'size': size, 'device': device} rd['components'].append(z) # TODO error handling if none of the ifs hit? if len(wlun): luns.append(wlun) # here I'm punting on rewriting the function to build dicts. # this extra conversion will slow otto down a tiny bit ldict = dict() for l in luns: num = l.get('lun') ldict[num] = l if self.use_slots: # do not use slots/show LUNs that are not ours use = set(self.use_slots) for lun in ldict.keys(): for raid in ldict[lun]['raids']: for comp in raid['components']: if comp['device'].find('.') != -1: slot = comp['device'].split('.')[1] if slot not in use: if ldict.get(lun): ldict.pop(lun) return ldict @property def luns(self): """ Returns a dictionary with the LUNs' information like so:: {'0': { 'label': 'update_lun', 'lun': '0', 'online': False, 'raids': [], 'size': '0.067', 'state': 'normal', 'status': 'offline', 'type': 'raw'}, '1': { 'label': '', 'lun': '1', 'online': False, 'raids': [ { 'components': [ { 'device': '43.0', 'drive': '43.0', 'element': '1.0.0', 'position': '0', 'size': '500.108', 'stat': 'normal', 'state': 'normal'}, { 'device': '43.1', 'drive': '43.1', 'element': '1.0.1', 'position': '1', 'size': '500.108', 'stat': 'normal', 'state': 'normal'}, { 'device': '43.2', 'drive': '43.2', 'element': '1.0.2', 'position': '2', 'size': '500.108', 'stat': 'normal', 'state': 'normal'}]}], 'size': '1000.216', 'state': 'initing', 'status': 'offline', 'type': 'raid5'}} """ if self.version == 6: logger.info("redirected to use 'luns' for 6") return self.list d = dict() r = self.run_and_check('luns -a') if not r: return d lun = None # groups correspond to LUN, LABEL, STATUS, TYPE, SIZE, STATE if not hasattr(self, 'lunhdr'): pass # groups correspond to SIZE, ELEMENT, DRIVE, STATE if not hasattr(self, 'luncomp'): pass lines = r.message.splitlines() for line in lines: line = line.strip() if not line or line.startswith('LUN'): continue m = re.search(self.lunhdr, line) if m: lun = m.group('lun') d[lun] = m.groupdict() d[lun]['raids'] = list() # Strip the label string from the above step d[lun]['label'] = d[lun]['label'].strip() # below is for backwards compatibility with Srx 6.x CLI d[lun]['online'] = False if d[lun]['status'] == 'online': d[lun]['online'] = True continue if not lun: logger.error("parsing fail: no lun for '%s'" % line) continue m = re.search(self.luncomp, line) if m: comp = m.groupdict() flds = comp['element'].split('.') if len(flds) < 3: logger.error("element '%s' parsing fail" % m.group(2)) continue raid = int(flds[1]) # only add the new raid group, if necessary if raid == len(d[lun]['raids']): d[lun]['raids'].append({'components': list()}) d[lun]['raids'][raid]['components'].append(comp) # below is for backwards compatibility with Srx 6.x CLI comp['position'] = flds[2] comp['device'] = comp['drive'] comp['stat'] = comp['state'] else: logger.error("lun '%s' parsing fail: '%s'" % (lun, line)) # do not use slots/show LUNs that are not ours if self.use_slots: use = set(self.use_slots) for lun in d.keys(): for raid in d[lun]['raids']: for comp in raid['components']: if comp['drive'].find('.') != -1: slot = comp['drive'].split('.')[1] if slot not in use: if d.get(lun): d.pop(lun) return d
[docs] def online(self, lun, expectation=True): """ Online a specific lun online. """ cmd = "online %s" % lun r = self.run_and_check(cmd, expectation) return r
[docs] def offline(self, lun, expectation=True): # DISCUSS: run_and_check?? """ This command allows you to place a specific lun offline. """ cmd = "offline %s" % lun r = self.run_and_check(cmd, expectation) return r
[docs] def spare(self, drives, expectation=True): if self.version >= 7: return self.mkspare(drives=drives, expectation=expectation) else: logger.info("making call to older version of spare command") return self._spare_6(drives=drives, expectation=expectation)
def _spare_6(self, drives, expectation=True): if type(drives) == list: drives = ' '.join(drives) cmd = "spare %s" % drives r = self.run_and_check(cmd, expectation) return r
[docs] def mkspare(self, drives, expectation=True): """ 'Drives' is either a string or a list of strings. Returns a ReturnCode. """ if type(drives) == list: drives = ' '.join(drives) cmd = "mkspare %s" % drives r = self.run_and_check(cmd, expectation=expectation) return r
@property def spares(self): """ Returns a dictionary of parsed output. """ d = dict() if self.version >= 7: cmd = 'spares' else: cmd = 'spare' r = self.run_and_check(cmd) if not r: return d lines = re.split(self.lineterm, r.message) for line in lines: if not line or line.startswith('DRIVE'): continue flds = line.split() if len(flds) < 2: logger.error("parsing failure: %s" % line) continue d[flds[0]] = {'drive': flds[0], 'size': flds[1]} return d
[docs] def rmspare(self, drives, expectation=True): """ Removes the spare role from one or more drives in a shelf. Drives is expected to be either a string, or a list of strings. Returns a ReturnCode. Version support: 6, 7 """ if type(drives) == list: drives = ' '.join(drives) cmd = "rmspare %s" % drives r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def cmenable(self, luns, expectation=True): """ The cmenable command marks specified LUNs for NVRAM data protection and records the LUN serial number on the CacheMotion card. 'Luns' is expected to be a string, or a list of strings. Returns a ReturnCode. Version support: 6, 7. """ if type(luns) == list: luns = ' '.join(luns) if self.version >= 7: cmd = '/cmenable %s' % luns else: cmd = 'cmenable %s' % luns return self.run_and_check(cmd, expectation=expectation)
[docs] def cmdisable(self, luns, expectation=True): """ You can disable NVRAM data protection on a LUN by issuing cmdisable and specifying the LUN. 'Luns' is expected to be a string, or a list of strings. Returns a ReturnCode. Version support: 6, 7. """ if type(luns) == list: luns = ' '.join(luns) if self.version >= 7: cmd = '/cmdisable %s' % luns else: cmd = 'cmdisable %s' % luns return self.run_and_check(cmd, expectation=expectation)
@property def cmlist(self): """ Returns a dictionary indicating cache memory status of each existing lun. Version support: 6, 7. """ if self.version >= 7: raise ApplianceUsage("The 'cmlist' command no longer exists in SRX-7.x.") else: return self._cmlist_6 @property def _cmlist_6(self): """ Returns a dictionary indicating cache memory status of eachexisting lun. A sample of the output is like this:: SRX shelf 39> cmlist LUN SERIAL 0 7FED1FC0-00-4FC0351F 1 7FED1FC0-01-4FC0355F 2 7FED1FC0-02-4FC0357D 3 7FED1FC0-03-4FC03587 That will turn into the following dictionary:: { '0' : '7FED1FC0-00-4FC0351F', '1': '7FED1FC0-01-4FC0355F', ...... } """ cmd = 'cmlist' cache_report = dict() r = self.run_and_check(cmd) if r: rs = re.split(self.lineterm, r.message) for l in rs: if l.startswith('LUN'): # This is the output's header so we can safety ignore it pass else: lun, serial = l.split() cache_report[lun] = serial return cache_report @property def cmstat(self): """ Returns a dictionary indicating cache memory status of each lun:: { '3' : 'enabled', '10': 'disabled' } Version support: 6, 7. """ d = dict() if self.version >= 7: cmd = '/cmstat' else: cmd = 'cmstat' r = self.run_and_check(cmd) if not r: return d rs = re.split(self.lineterm, r.message) for l in rs: if not l or l.startswith('LUN'): continue else: lun, state = l.split() d[lun] = state return d @property def cmlunid(self): """ The SSD non-volatile storage device on the CacheMotion card is a LUN with an ID that is 254 by factory default. Issuing cmlunid without arguments displays the CacheMotion LUN ID of the local CacheMotion card. """ if self.version >= 7: cmd = '/cmlunid' else: cmd = 'cmlunid' return self.run_and_check(cmd) @cmlunid.setter def cmlunid(self, lun): """ Change the LUN parameter of the local CacheMotion """ if self.version >= 7: cmd = '/cmlunid %s' % lun else: cmd = 'cmlunid %s' % lun self.run_and_check(cmd) @property def cmcheck(self): """ The cmcheck command provides cache statistics for both CacheMotion and EtherFlash Cache. Statistics for Cache level 0 apply to CacheMotion, while statistics for Cache level 1 apply to EtherFlash Cache. See a sample below of the command ouptut. Parameters: None. Returns a dictionary representing the data reported by cmcheck, an empty dictionary will be returned if there's no data to report:: {'0': {'device': [{'blocks_in_cache': '917504', 'data_blocks': '910164', 'device_name': '#S/sdS0/data', 'direct_blocks': '1', 'emptypos': '6360', 'first_data_block': '7227', 'id': '0', 'metadata_blocks': '7226', 'super_blocks': '1'}], 'hit_rate': '75', 'recent_hit_rate': '75', 'target': [{'hit_rate': '75', 'id': '1', 'in_cache': '910164', 'recent_hit_rate': '75', 'working_set': '827524'}]}, '1': {'device': [{'blocks_in_cache': '335824', 'blocksize': '131072', 'data_blocks': '20483', 'device_name': '/raiddev/0/data', 'direct_blocks': '0', 'emptypos': '2', 'first_data_block': '85', 'flags': '0100', 'id': '0', 'metadata_blocks': '84', 'read_errors': '0', 'super_blocks': '1', 'version': '2', 'write_errors': '0'}, {'blocks_in_cache': '335824', 'blocksize': '131072', 'data_blocks': '20482', 'device_name': '/raiddev/3/data', 'direct_blocks': '0', 'emptypos': '2', 'first_data_block': '85', 'flags': '0100', 'id': '3', 'metadata_blocks': '84', 'read_errors': '0', 'super_blocks': '1', 'version': '2', 'write_errors': '0'}], 'hit_rate': '98', 'recent_hit_rate': '100', 'target': [{'hit_rate': '94', 'id': '0', 'in_cache': '8', 'recent_hit_rate': '25', 'working_set': '1'}, {'hit_rate': '98', 'id': '1', 'in_cache': '81928', 'recent_hit_rate': '100', 'working_set': '8'}]}} """ cache = {} if self.version >= 7: cmd = '/cmcheck' else: cmd = 'cmcheck' result = self.expert_run(cmd) if result: data = result.message else: return cache targetCount = 0 for l in re.split(self.lineterm, data): m = re.match('Cache level (\d)', l) if m: cacheID = m.group(1) cache[cacheID] = {} # We add an element to the cache list targetCount = 0 continue m = re.match('device (?P<id>\d):\s(?P<device_name>.*)', l) if m: devID = int(m.group('id')) if 'devices' not in cache[cacheID]: cache[cacheID]['devices'] = {} # We add an element to the device dict cache[cacheID]['devices'][devID] = m.groupdict() # We add a dict of each device continue m = re.match( '(?P<blocks_in_cache>\d+) blocks in cache, first data block is (?P<first_data_block>\d+), emptypos=(?P<emptypos>\d+)', l) if m: cache[cacheID]['devices'][devID] = m.groupdict() continue m = re.match( 'version: (?P<version>\d+), blocksize: (?P<blocksize>\d+), flags: (?P<flags>\d+), write errors: (?P<write_errors>\d+), read errors: (?P<read_errors>\d+)', l) if m: cache[cacheID]['devices'][devID] = m.groupdict() continue m = re.match( '(?P<super_blocks>\d+) super blocks, (?P<metadata_blocks>\d+) metadata blocks, (?P<direct_blocks>\d+) direct blocks, (?P<data_blocks>\d+) data blocks', l) if m: cache[cacheID]['devices'][devID] = m.groupdict() continue m = re.match('hit rate (?P<hit_rate>\d+)% recent hit rate (?P<recent_hit_rate>\d+)%', l) if m: cache[cacheID].update(m.groupdict()) continue m = re.match( 'target (?P<id>\d+) in cache (?P<in_cache>\d+) working set (?P<working_set>\d+) hit rate (?P<hit_rate>\d+)% recent hit rate (?P<recent_hit_rate>\d+)%', l) if m: if 'target' not in cache[cacheID]: cache[cacheID]['target'] = {} cache[cacheID]['target'].update(m.groupdict()) targetCount += 1 return cache
[docs] def fcenable(self, luns, expectation=True): if type(luns) == list: luns = ' '.join(luns) if self.version >= 7: return self.fclunenable(luns, expectation) else: logger.info('calling old code for fcenable command') return self._fcenable_6(luns, expectation)
def _fcenable_6(self, luns, expectation=True): """ The fcenable commmand enables read cache functionality for the data on specified LUNs. Parameters: lun: the lun we want to start using flash cache """ return self.run_and_check('fcenable %s' % luns, expectation=expectation)
[docs] def fclunenable(self, luns, expectation=True): """ The fclunenable commmand enables read cache functionality for the data on specified LUNs. """ return self.expert_run('fclunenable %s' % luns, expectation=expectation)
[docs] def fcdisable(self, luns, expectation=True): """ The fcdisable command disables read cache functionality for the data on either specified LUNs or all LUNs on the shelf. Version support: 6, 7 """ if type(luns) == list: luns = ' '.join(luns) if self.version >= 7: return self.fclundisable(luns, expectation=expectation) else: logger.info("calling old code for fcdisable command") return self._fcdisable_6(luns, expectation=expectation)
def _fcdisable_6(self, luns, expectation=True): """ The fcdisable command disables read cache functionality for the data on either specified LUNs or all LUNs on the shelf. Parameters:: lun: the lun we want to stop using flash cache """ return self.run_and_check('fcdisable %s' % luns, expectation=expectation)
[docs] def fclundisable(self, lun, expectation=True): """ The fclundisable command disables read cache functionality for the data on either specified LUNs or all LUNs on the shelf. """ return self.expert_run('fclundisable %s' % lun, expectation=expectation)
[docs] def fcconfig(self, slot, pct=None, expectation=True): if type(slot) == list: slot = ' '.join(slot) if self.version >= 7: return self.fcadd(slot, pct, expectation=expectation) else: logger.info("calling old code for fcconfig command") return self._fcconfig_6(slot, pct, expectation=expectation)
def _fcconfig_6(self, slot, pct=None, expectation=True): """ Configures a slot or a set of slots on a shelf to be used for flash cache. """ if pct: cmd = 'fcconfig -o %s %s' % (pct, slot) else: cmd = 'fcconfig %s' % slot return self.run_and_check(cmd, expectation=expectation, timeout=60)
[docs] def fcadd(self, slot, pct=None, expectation=True): """ The fcadd command adds/configures one or more device(s) as a read cache, in order to significantly improve read performance on frequently acccessed data. Parameters:: drive: the drives to turn into flash cache drives pct: Allows you to configure the overcommit percentage for the specified range of devices. Leaving a percentage of available space unused can improve performance and durability of SSDs. The default overcommit percentage is 20% when not specified. """ if type(slot) == list: slot = ' '.join(slot) if pct: cmd = 'fcadd -o %s %s' % (pct, slot) else: cmd = 'fcadd %s' % slot return self.expert_run(cmd, expectation=expectation)
[docs] def rmfcache(self, slot=None, expectation=True): if type(slot) == list: slot = ' '.join(slot) if self.version >= 7: return self.fcremove(slot, expectation=expectation) else: logger.info("calling old code for rmfcache command") return self._rmfcache_6(expectation=expectation) # rmfcache on ver. 6 does not need a parameter
def _rmfcache_6(self, expectation=True): """ Remove the status of flash cache to all drives on the srx shelf. """ cmd = 'rmfcache' return self.run_and_check(cmd, expectation)
[docs] def fcremove(self, slot=None, expectation=True): """ The fcremove command removes the read cache from the shelf as well as the cache configuration of all affected LUNs. The 'drives' argument should be a string specifying either "all", the specific LUN, or a list of LUNs as strings. """ if slot is not None: cmd = 'fcremove %s' % slot else: cmd = 'fcremove all' return self.expert_run(cmd, expectation=expectation)
[docs] def fcstat(self): """ The fcstat command displays the drive and it's size for each drive whose role is 'cache'. """ d = dict() f = self.expert_run('fcstat') if self.version >= 7: if f: # Looks to me like fcstat on ver 7 is passing wrong data regex = re.compile('(\d+\.\d+)\s+(\d+\.\d+)') for line in re.split(self.lineterm, str(f)): if not line or line.startswith('DRIVE'): continue m = re.search(regex, line) if not m: raise ApplianceError("parse failure: '%s'" % line) drive = m.group(1) d[drive] = {'drive': drive, 'size': m.group(2)} else: if f: for line in str(f).split('\n'): regExp = re.search('(\d+):\s+(disabled|enabled)', line) if regExp: d[regExp.group(1)] = regExp.group(2) return d
@property def fcpriority(self): """ Reports flash cache performance settings for each lun. Example: { '9' : {'pri': '10', 'minpct': '10'}, '10' : {'pri': '0', 'minpct': '0'}, '8' : {'pri': '10', 'minpct': '0'}, '200': {'pri': '0', 'minpct': '0'} } """ if self.version >= 7: return self.fclunstat else: return self._fcpriority_6() @fcpriority.setter def fcpriority(self, prioritystr): lun = str() pri = str() pct = str() flds = prioritystr.split() nargs = len(flds) if nargs > 2: lun = flds[0] pri = flds[1] pct = flds[2] elif nargs > 1: lun = flds[0] pri = flds[1] elif nargs > 0: lun = flds[0] if self.version >= 7: self.fclunpriority(lun, pri, pct=pct) elif self.version < 6: self._fcpriority_6(lun, pri, pct) def _fcpriority_6(self, lun=None, pri=None, pct=None): if lun: cmd = 'fcpriority %s %s' % (lun, pri) if pct: cmd += '%s' % pct return self.run_and_check(cmd) else: cmd = 'fcpriority' result = dict() fcstatus = self.run_and_check(cmd) if fcstatus: for line in str(fcstatus).split('\n')[1:]: regExp = re.search('(\d+)\s+(\d+)\s+(\d+)', line) result[regExp.group(1)] = {'pri': regExp.group(2), 'pct': regExp.group(3)} return result @property def fclunstat(self): """ Returns a dictionary with the read cache status of all cache LUNs on the shelf. """ r = dict() f = self.expert_run('fclunstat') if f: regex = re.compile('(\d+)\s+(disabled|enabled)\s+(\d+)\s+(\d+)') for line in re.split(self.lineterm, f.message.strip()): if not line or line.startswith('LUN'): continue m = re.search(regex, line) if not m: raise ApplianceError("parse failure: '%s'" % line) lun = m.group(1) r[lun] = dict() r[lun]['lun'] = lun r[lun]['status'] = m.group(2) r[lun]['pri'] = m.group(3) r[lun]['pct'] = m.group(4) return r
[docs] def fclunpriority(self, lun, pri, pct=None, expectation=True): """ The fclunpriority command allows you to specify performance improvement relative to other LUNs as well as the approximate minimum percentage of the cache targeted for the specified LUN. Parameters are expected to be strings:: lun: the lun to affect with the command. pri: Enter a number from zero to 100. This number is a unitless value that specifies performance improvement relative to other LUNs. pct: Enter a number from one to 100. This number is the approximate minimum percentage of the cache targeted for the specified LUN. Returns a ReturnCode. """ cmd = 'fclunpriority %s %s' % (lun, pri) if pct: cmd += ' %s' % pct return self.expert_run(cmd, expectation=expectation)
[docs] def flushcacheenable(self, luns): """ 'luns' is expected to be a single lun as a string, or a list of luns as strings. Returns a ReturnCode. Version support: 7.0.1-R6 and above """ if self.release < 'SRX-7.0.1-R6': return ReturnCode(False, 'flushcacheenable command unsupported in release {0}'.format(self.release)) if type(luns) == list: luns = ' '.join(luns) return self.run_and_check('flushcacheenable {0}'.format(luns))
[docs] def flushcachedisable(self, luns): """ 'luns' is expected to be a single lun as a string, or a list of luns as strings. Returns a ReturnCode. Version support: 7.0.1-R6 and above """ if self.release < 'SRX-7.0.1-R6': return ReturnCode(False, 'flushcachedisable command unsupported in release {0}'.format(self.release)) if type(luns) == list: luns = ' '.join(luns) return self.run_and_check('flushcachedisable {0}'.format(luns))
@property def flushcachestat(self, luns=''): """ Returns a dictionary containing available/provided luns and the flushcache state for each one (enabled or disabled). For example, this on the CLI:: SRX shelf 43> flushcachestat LUN FLUSHCACHE 0 disabled 1 enabled 2 disabled 3 disabled 4 disabled SRX shelf 43> returns this:: { '0': { 'lun': '0', 'status': 'disabled'}, '1': { 'lun': '1', 'status': 'enabled'}, '2': { 'lun': '2', 'status': 'disabled'}, '3': { 'lun': '3', 'status': 'disabled'}, '4': { 'lun': '4', 'status': 'disabled'}} Version support: 7.0.1-R6 and above """ d = dict() if self.release < 'SRX-7.0.1-R6': logger.error('flushcachestat command unsupported in release {0}'.format(self.release)) return d if type(luns) == list: luns = ' '.join(luns) r = self.run_and_check('flushcachestat {0}'.format(luns)) if not r: return d lines = r.message.splitlines() for line in lines: if not line or line.startswith('LUN'): continue flds = line.split() if len(flds) < 2: logger.error("parsing failure: '%s'" % line) continue lun = flds[0] d[lun] = {'lun': lun, 'status': flds[1]} return d @property def sos(self): """ Run the sos command, and return the result as a string. Due to the CorOS integration into SRX-7.x, the sos command now returns a single line with an 'scp' command that the user can execute on a remote host in order to get a copy of the sos file that was saved to the staging area. Use the otto.lib.srx.sos7() method to take the data returned from this command, and grab the contents of the sos file. """ return self.run_and_check('sos', timeout=300).message def _check_range(self, slotrange, expectation=True): # check for slots in a range that are not in use_slots if self.use_slots is None: return ReturnCode(True, "no slots protected") first, last = slotrange.split('-') first = first.split('.')[1] selected = set(range(int(first), int(last) + 1)) use = set() for x in self.use_slots: use.add(int(x)) notmine = use.intersection(selected) if len(notmine) and expectation: raise Exception("implicit use of excluded slot(s) in range: %s" % notmine) else: return ReturnCode(False, "implicit use of excluded slot(s) in range: %s" % notmine)
[docs] def make(self, lun, raidtype, slots='', lunvers=None, clean=False, force=True, expectation=True): if self.version >= 7: return self.mklun(lun, raidtype, drives=slots, lunvers=lunvers, clean=clean, force=force, expectation=expectation) else: logger.info("calling old code for make command") return self._make_6(lun, raidtype, slots, lunvers, clean, force, expectation)
def _make_6(self, lun, raidtype, slots=None, lunvers=None, clean=False, force=True, expectation=True): """ make a lun clean skips parity build lunvers allows specification of lun format raidtype is not checked but the sr supports:: 'jbod', 'raidL', 'raidl', 'raid0', 'raid1', 'raid10', 'raid5', 'raid6rs', 'raw' """ makeclean = "" s = str() if type(slots) == list: # handle lists of type str or AoEAddress for j in slots: if type(j) == AoEAddress: slots[slots.index(j)] = aoetostr(j) else: if j.count("-"): self._check_range(j, expectation) elif self.use_slots: selected = set() use = set() for x in self.use_slots: use.add(int(x)) for x in slots: if x.count("-"): start, stop = x.split('-') start = start.split('.')[1] for i in range(int(start), int(stop) + 1): selected.add(i) else: selected.add(int(x.split('.')[1])) notmine = selected.difference(use) if notmine: if expectation: raise Exception("implicit use of excluded slot(s) in range: %s" % notmine) else: return ReturnCode(False, "implicit use of excluded slot(s) in range: %s" % notmine) s = "%s %s" % (s, j) elif type(slots) == str: if self.use_slots: if slots.count('-'): # determine need for looking in ranges slist = slots.split() for x in slist: result = ReturnCode(True) if x.count('-'): # if this item is a range result = self._check_range(x, expectation) if not result: return result else: # assuming a str that is a range-less list of LUNs selected = set() use = set([int(x) for x in self.use_slots]) for slot in slots.split(): # possibly ws separated selected.add(int(slot.split('.')[1])) notmine = selected.difference(use) if len(notmine): if expectation: raise Exception("use of excluded slot(s): %s" % notmine) else: return ReturnCode(status=False, message="use of excluded slot(s): %s" % notmine) s = slots if type(lunvers) is str: if not lunvers.startswith('-V'): lunvers = '-V %s ' % lunvers elif type(lunvers) is int: lunvers = '-V %s ' % lunvers elif lunvers is None: lunvers = "" if clean: makeclean = "-c " cmd = "make %s %s %s %s %s" % (lunvers, makeclean, lun, raidtype, s) result = self.run_and_check(cmd, expectation=expectation, force=force) if not result and expectation: raise ApplianceError("%s: %s" % (cmd, result.message)) return result
[docs] def mklun(self, lun, raidtype, drives=None, lunvers=None, clean=False, force=True, expectation=True): """ make a lun clean skips parity build lunvers allows specification of lun format raidtype is not checked but the sr supports:: 'jbod', 'raidL', 'raidl', 'raid0', 'raid1', 'raid10', 'raid5', 'raid6rs', 'raw' """ makeclean = "" s = str() if type(drives) == list: # handle lists of type str or AoEAddress for j in drives: if type(j) == AoEAddress: drives[drives.index(j)] = aoetostr(j) else: if j.count("-"): self._check_range(j, expectation=expectation) elif self.use_slots: selected = set() use = set() for x in self.use_slots: use.add(int(x)) for x in drives: if x.count("-"): start, stop = x.split('-') start = start.split('.')[1] for i in range(int(start), int(stop) + 1): selected.add(i) else: selected.add(int(x.split('.')[1])) notmine = use.difference(selected) if notmine and expectation: raise Exception("implicit use of excluded slot(s) in range: %s" % notmine) else: return ReturnCode(False, "implicit use of excluded slot(s) in range: %s" % notmine) s = "%s %s" % (s, j) elif type(drives) == str: if self.use_slots: if drives.count('-'): # determine need for looking in ranges slist = drives.split() for x in slist: result = ReturnCode(True) if x.count('-'): # if this item is a range result = self._check_range(x, expectation=expectation) if not result: return result else: # assuming a str that is a range-less list of LUNs selected = set() use = set([int(x) for x in self.use_slots]) for slot in drives.split(): # possibly ws separated selected.add(int(slot.split('.')[1])) notmine = selected.difference(use) if len(notmine): if expectation: raise Exception("use of excluded slot(s): %s" % notmine) else: return ReturnCode(status=False, message="use of excluded slot(s): %s" % notmine) s = drives if type(lunvers) is str: if not lunvers.startswith('-V'): lunvers = '-V %s ' % lunvers elif type(lunvers) is int: lunvers = '-V %s ' % lunvers elif lunvers is None: lunvers = "" if clean: makeclean = "-c " cmd = "mklun %s %s %s %s %s" % (lunvers, makeclean, lun, raidtype, s) result = self.run_and_check(cmd, expectation=expectation, force=force) if not result and expectation: raise ApplianceError("%s: %s" % (cmd, result.message)) return result
[docs] def jbod(self, slot, expectation=True, force=True): if type(slot) == list: slot = ' '.join(slot) if self.version >= 7: return self.mkjbod(slot, expectation=expectation, force=force) else: logger.info("calling old code for jbod command") return self._jbod_6(slot, expectation=expectation, force=force)
def _jbod_6(self, slot, expectation=True, force=True): cmd = "jbod %s" % slot return self.run_and_check(cmd, expectation=expectation, force=force)
[docs] def mkjbod(self, drive, expectation=True, force=True): cmd = 'mkjbod %s' % drive return self.run_and_check(cmd, expectation=expectation, force=force)
[docs] def remove(self, luns, expectation=True, force=True): if self.version >= 7: return self.rmlun(luns, expectation=expectation, force=force) else: logger.info("calling old code for remove command") return self._remove_6(luns, expectation, force=force)
def _remove_6(self, lun, expectation=True, force=True): """ Removes a lun. If the lun is offline """ if type(lun) == dict: lun = aoetostr(lun) else: lun = str(lun) if force: cmd = 'offline -f %s' % lun # TODO: this is not what force is for. result = self.run_and_check(cmd) if not expectation: if result.message.endswith('not found, skipping'): logger.error("%s: %s" % (cmd, result.message)) result.status = False elif result.message.endswith('is not a valid lun value'): logger.critical("%s: %s" % (cmd, result.message)) result.status = False cmd = "remove -f %s" % lun result = self.run_and_check(cmd) if not expectation: if result.message.endswith('not found, skipping'): logger.error("%s: %s" % (cmd, result.message)) result.status = False elif result.message.endswith('is not a valid lun value'): logger.critical("%s: %s" % (cmd, result.message)) result.status = False return result
[docs] def rmlun(self, luns, expectation=True, force=True): """ Remove specified luns. If it/they is/are online, first offline it/them. Returns a ReturnCode. """ if type(luns) == dict: luns = aoetostr(luns) elif type(luns) == list: luns = ' '.join(luns) else: luns = str(luns) flagf = str() if force: flagf = '-f' return self.run_and_check("rmlun %s %s" % (flagf, luns), expectation=expectation)
@property def disks(self): """ Returns a dictionary with information of disks. """ if self.version >= 7: return self.drives else: logger.info("calling old code for disks command") return self._disks_6 @property def _disks_6(self): """ Get disk info !this does not use the disks command! Returns an ordered dict:: s.disks['22'] {'FW': 'SN04', 'Model': 'ST9500530NS', 'SN': '9SP2K7M3', 'config': None, 'geometry': '976773168 512', 'hresets': '0', 'link': '1.5 Gb/s', 'r0resets': '0', 'sstate': 'up', 'state': 'up', 'type': 'sata', 'version': None} """ # I think this is gated by the speed of the console cmd = 'ls /raiddev/' diskd = OrderedDict() result = self.run_and_check(cmd) if result: if self.use_slots: use = set(self.use_slots) else: use = set() ignored = {'events', 'extra', 'stat', 'ctl'} have = set() rs = re.split(self.lineterm, result.message) for line in rs: flds = line.split('/raiddev/') if len(flds) > 1: have.add(flds[1]) have = have.difference(ignored) if use: use = have.intersection(use) else: use = have ouse = [y for y in use] ouse.sort(key=int) for disk in ouse: current = dict() cmd = 'cat /raiddev/{0:>s}/stat'.format(disk) result = self.run_and_check(cmd) r = result.message rs = re.split(self.lineterm, r.strip()) for line in rs: if line: kvpair = line.split(':') k = kvpair[0] if len(kvpair) < 2: logger.error("failed to split, expected a colon here: '%s'" % line) continue v = kvpair[1].strip() current[k] = v if current['sstate'] != 'missing': diskd[disk] = current else: raise ApplianceError(result.message) for d in diskd: r = self.run_and_check('disks -a %s.%s' % (self.shelf, d)) rs = r.message.splitlines() if not self.driveahdr: self.driveahdr = \ re.compile(r"^(?P<drive>\d+.\d+)\s+" r"(?P<size>(\*?\d+\.\d+GB|missing|up))" r"(\s+(?P<role>[0-9+\.0-9+\.0-9+|cache|no disk|spare]*?)\s+" r"(?P<model>[\w\s\-]+)\s+" r"(?P<firmware>[a-zA-Z0-9\.\-]+)\s+" r"(?P<mode>(sata|sas)\s+\d+.\d+Gb/s))?") tablehdrfound = False diskhdrfound = False for l in rs: if tablehdrfound is False: if l.startswith('DISK'): tablehdrfound = True continue disk = d if not diskhdrfound: summary = re.search(self.driveahdr, l) try: diskd[disk] = summary.groupdict() except AttributeError: raise ApplianceError('Somethings wrong with my input line:\n%s' % l) diskhdrfound = True continue try: k, v = l.strip().split(':') except ValueError: raise ApplianceError("expecting a key:value output, got this: %s" % l.strip()) v = v.replace("'", "") diskd[d][k] = v.strip() if self.use_slots and d not in self.use_slots: continue for i in diskd.keys(): disk = "%s.%s" % (self.shelf, i) cmd = "disks -c %s" % disk ret = self.run_and_check(cmd) cline = re.split(self.lineterm, ret.message.strip()) cline = cline[1].split() if len(cline) > 2: version = cline[1] config = ' '.join(cline[2:]) diskd[i]['version'] = version diskd[i]['config'] = config else: diskd[i]['version'] = None diskd[i]['config'] = None diskd[i]['slot'] = i self._set_disks(diskd) return diskd @property def drives(self): """ Returns an ordered dict of drive info indexed by slot:: >> sr.drives['0'] {'FW': 'SN04', 'Model': 'ST9500530NS', 'SN': '9SP2K7M3', 'config': None, 'geometry': '976773168 512', 'hresets': '0', 'link': '1.5 Gb/s', 'r0resets': '0', 'sstate': 'up', 'state': 'up', 'type': 'sata', 'version': None} """ diskd = OrderedDict() dlist = list() d = self.run_and_check('drives') ds = re.split(self.lineterm, d.message.strip()) for line in ds: if line.startswith('DRIVE'): continue did, dstate = line.split()[:2] if dstate != 'missing': if self.use_slots: slot = did.split('.')[1] if int(slot) in self.use_slots or slot in self.use_slots: dlist.append(did) else: dlist.append(did) for d in dlist: r = self.run_and_check('drives -a %s' % d) rs = re.split(self.lineterm, r.message.strip()) if not self.driveahdr: self.driveahdr = \ re.compile(r"^(?P<drive>\d+.\d+)\s+" r"(?P<size>\*?\d+\.\d+)\s+" r"(?P<role>[0-9+\.0-9+\.0-9+|cache|spare]*?)\s+" r"(?P<model>[a-zA-Z0-9\s\])\s+" r"(?P<firmware>[a-zA-Z0-9\.\-]+)\s+" r"(?P<mode>(sata|sas)\s+\d+.\d+Gb/s)") foundheader = False for l in rs: if l.startswith('DRIVE'): continue disk = d.split('.')[1] if not foundheader: summary = re.search(self.driveahdr, l) try: diskd[disk] = summary.groupdict() except AttributeError: raise ApplianceError("regex:\n%s\ndid not match:\n %s" % (self.driveahdr.pattern, l)) foundheader = True continue try: k, v = l.strip().split(':') except ValueError: raise ApplianceError("expecting a key:value output, got this: %s" % l.strip()) v = v.replace("'", "") diskd[d.split('.')[1]][k] = v.strip() if self.use_slots and d not in self.use_slots: continue ret = self.run_and_check("drives -c %s" % d) cline = ret.message.splitlines() cline = cline[1].split() if len(cline) > 1: version = cline[1] config = ' '.join(cline[2:]) diskd[disk]['version'] = version diskd[disk]['config'] = config else: diskd[disk]['version'] = None diskd[disk]['config'] = None self._set_disks(diskd) return diskd @property def temp(self): """ Returns the state of the shelf's temperature using srx command temp """ columns = ['location', 'temp'] rdict = dict() r = self.run_and_check('temp') rs = r.message.splitlines() for line in rs: if not line or line.startswith(r'LOCATION'): continue ls = line.split() ps = dict(zip(columns, ls)) rdict[ps['location']] = ps['temp'] return rdict @property def power(self): """ returns the state of the power supplies Version support: 6, 7 """ columns = ['psu', 'status', 'temp', 'fan1rpm', 'fan2rpm'] rdict = dict() r = self.run_and_check('power') rs = r.message.splitlines() for line in rs: if not line or line.startswith(r'PSU'): continue ls = line.split() ps = dict(zip(columns, ls)) rdict[ps['psu']] = ps return rdict
[docs] def reboot(self): """ Reboot the appliance Version support: 6, 7. """ cmd = "reboot" if self.version >= 7: cmd = "reboot -f" s = self.run(cmd, wait=False) if s.find("error") != -1: return ReturnCode(False, s) sleep(1) return ReturnCode(True)
[docs] def debug(self, setto): if self.version >= 7: # This code functionality depends on SRX-3460 get fixed. # cmd = '/debug %s' % setto r = self.expert_run('debug %s' % setto) logger.warning('setting debug to %s' % setto) return r else: logger.warning('setting debug to %s' % setto) cmd = "debug %s" % setto r = self.run_and_check(cmd) return r
@property def ifstat(self): """ Returns a dictionary of interfaces encoded as dictionaries:: {'ether0': {'link': {'current': '1000', 'max': '1000'}, 'mac': '003048b92888', 'name': 'ether0'}, 'ether1': {'link': {'current': '1000', 'max': '1000'}, 'mac': '003048b92889', 'name': 'ether1'}, 'ether2': {'link': {'current': '0', 'max': '10000'}, 'mac': '003048da5b00', 'name': 'ether2'}, 'ether3': {'link': {'current': '0', 'max': '10000'}, 'mac': '003048da5b01', 'name': 'ether3'}} """ cmd = 'ifstat' columns = ['name', 'mac', 'link'] mheader = re.compile(r"NAME[ \t]*ADDR[ \t]*LINK \(Mbps\)[ \t]*MTU") oheader = re.compile(r"NAME[ \t]*ADDR[ \t]*LINK \(Mbps\)") r = self.run_and_check(cmd) rs = re.split(self.lineterm, r.message.strip()) ifaces = dict() hfound = False for line in rs: if not hfound: if oheader.search(line): hfound = True if mheader.search(line): columns = ['name', 'mac', 'link', 'mtu'] continue ls = line.split() if len(ls) > len(columns): current = ls[2] mx = ls[3] ls.pop(3) ls[2] = current + mx p = dict(zip(columns, ls)) f = ['current', 'max'] t = p['link'].split('/') p['link'] = dict(zip(f, t)) ifaces[p['name']] = p return ifaces @property def fans(self): """ Returns fan status as a dictionary. """ d = dict() columns = ['fan', 'rpm'] r = self.run_and_check('fans') if not r: return d rs = re.split(self.lineterm, r.message) for line in rs: if not line or line.startswith('FAN'): continue ls = line.split() p = dict(zip(columns, ls)) d[p['fan']] = p['rpm'] return d def _wipe_spare(self): """ Removes all spare drives configured in this shelf. """ if self.version >= 7: cmd = 'spares' else: cmd = 'spare' result = self.run_and_check(cmd) if result: r = result.message for line in re.split(self.lineterm, r): regExp = re.search(r'(\d+\.\d+)\s+.*', line) if regExp: self.rmspare(regExp.group(1)) def _wipe_cache(self): """ Removes all cache drives configured in this shelf. """ try: self.rmfcache() except ApplianceError as e: raise e
[docs] def wipe(self, resetsize='-c'): """ Remove all luns on this shelf. """ while 1: luns = self.list if not luns: break for l in luns: if not l: continue self.offline(l) self.remove(l, force=True) self._wipe_spare() self._wipe_cache() for d in range(self.slots): if getattr(self, 's%s' % d).get('Model'): self.setsize(resetsize, '{0}.{1}'.format(self.shelf, d))
@property def maxsize(self): """ Maxsize sums the disk capacity in the use_slots list for this SR. It returns the value in bytes. """ sz = 0 if self.version >= 7: ddict = self.drives else: ddict = self.disks for d in ddict: if self.use_slots and not (d in self.use_slots): continue gl = ddict[d]['geometry'].split() nsect = int(gl[0]) sectsz = int(gl[1]) sz += nsect * sectsz return sz # TODO does this belong in this class?
[docs] def diskmap(self, lun): """ Return a dictionary with the shelf.slot with associated with the raid device index (lun.comp.drive). Returns a dictionary like:: {'84.8':'7.0.0', '84.2':'7.0.1', '84.3':'7.0.2'} """ dmap = dict() sh = self.shelf if self.version >= 7: s = self.expert_run("cat /raid/%s/raidstat" % lun) regex = re.compile(r"^([0-9]+\.[0-9]+\.[0-9]+)\s+[0-9]+\s+[a-zA-Z,]+\s+" + "/(raiddev|sys/config)/([0-9]+|update)(/)?.*") lines = s.message.splitlines() for l in lines[1:]: m = re.match(regex, l) if m: slot = m.group(3) if slot == 'update': slot = 'ramfs' drive = "%s.%s" % (sh, slot) dmap[drive] = m.group(1) else: f = l.split() if len(f) > 3 and f[3] != "missing": logger.error("unhandled diskmap line: %s" % l) else: rs = self.run_and_check("cat /raid/%s/raidstat" % lun) lines = rs.message.splitlines() for l in lines[1:]: m = re.match(r"^([0-9]+\.[0-9]+\.[0-9]+) [0-9]+ " + "[a-zA-Z\, ]+ \/raiddev\/([0-9]+)\/.*", l) if m: dmap["%s.%s" % (sh, m.group(2))] = m.group(1) else: f = l.split() if len(f) > 3 and f[3] != "missing": print "unhandled diskmap line: %s" % l return dmap
[docs] def fail(self, disk, expectation=True): """ This will fail the specified drive. """ if self.version >= 7: return self.faildrive(drive=disk, expectation=expectation) else: logger.info("calling old code for disks command") return self._fail_6(disk, expectation)
def _fail_6(self, disk, expectation=True): """ Fail the specified disk. Disk is in lun.part.element format. Returns a ReturnCode. """ cmd = "fail %s" % disk r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def unfail(self, disk, expectation=True, slot=None): """ Will unfail a drive. """ if self.version >= 7: return self.replacedrive(disk, expectation, slot) else: logger.info("calling old code for unfail command") return self._unfail_6(disk, expectation)
def _unfail_6(self, disk, expectation=True): """ Un-fail the specified disk. Disk is in lun.part.element format. Returns a ReturnCode. """ cmd = "unfail %s" % disk r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def faildrive(self, drive, expectation=True): """ Fail the specified drive. Drive is in lun.part.element format. Returns a ReturnCode. """ r = self.run_and_check("faildrive %s" % drive, expectation=expectation) return r
@property def uptime(self): """ Displays the amount of time the appliance has been running since the last reboot. """ cmd = "uptime" r = self.run_and_check(cmd) return r.message @property def when(self): """ Returns a dictionary:: {'lun':'1.0' 'percent': '44.06 ' 'rate': '83542.02' 'time':'3:43:15'} """ columns = ['lun', 'percent', 'rate', 'time'] d = dict() r = self.run_and_check('when') if not len(r.message): return d for line in r.message.splitlines(): if self.version >= 7: if not line or line.startswith('LUN'): # header continue ls = line.split() d[ls[0]] = dict(zip(columns, ls)) else: ls = line.split() if len(ls) >= 6: ls = ls[0], ls[1][:-1], ls[2], ls[4] # remove non-data d[ls[0]] = dict(zip(columns, ls)) return d
[docs] def update(self, reboot=True, lun=True, fname=None): """ The update command updates the CorOS release on the appliance. If reboot is True, sets the '-r' flag, and forces a reboot. Either 'lun' can be True, or 'fname' can be a string of the already scp'd tarc name to use, but both 'lun' and 'fname' can not be set at the same time. If neither is set, then returns the possibilities for updating, if any. 'fname' can be '-f' to get the update code to "find" the update fname. Warning: it will find one (of possibly many) fname in /staging/, so you need to be sure there's only one there and that it's the one you want to use. """ if self.version >= 7: if lun and fname: raise Exception("Srx.update(): both 'lun' and 'fname' should not be set.") wait = False cmd = "update" if reboot: cmd += " -r" if lun: cmd += " " # 7.0 release mandated that, update = update lun elif fname: cmd += " %s" % fname else: wait = True r = self.run(cmd, wait=wait) if wait: # will only be True if 'lun' and 'fname' are not set tarc = re.compile(r"^SRX\-[0-9]+\.[0-9]+\.[0-9]+\-R[0-9]+\.tarc") for line in re.split(self.lineterm, r): if line.find("No update files found") != -1: break if re.match(tarc, line): r = line.strip() break elif reboot: # wait for the CLI message that assures we # won't be allowed to run further CLI commands self.expect(["System rebooting ...", TIMEOUT], timeout=120) else: cmd = "update -f" r = self.run(cmd, wait=False) return r
[docs] def disktest(self, mode, disk, expectation=True): """ Destructive read/write test of a drive. """ cmd = "disktest %s %s" % (mode, disk) try: if self.version >= 7: r = self.expert_run(cmd, expectation=expectation) else: r = self.run_and_check(cmd, expectation=expectation) except ApplianceError as e: raise e return r
[docs] def setsize(self, size, drives): # TODO: file SR bug: size is not optional """ Makes a drive looks lower than or equal to drive size. size = '-c' means restore the disk to its actual size drives can be a string or a list of strings """ if type(drives) is list: drives = ' '.join(drives) if self.version >= 7: cmd = "/setsize %s %s" % (size, drives) else: cmd = "setsize %s %s" % (size, drives) return self.run_and_check(cmd)
@property def mask(self): """ Returns a dictionary with a list of macs per LUN. """ d = dict() r = self.run_and_check('mask') if not r: return d masks = re.split(self.lineterm, r.message) for line in masks: if not line or line.startswith('LUN'): continue ls = line.strip().split() if len(ls) > 1: d[ls[0]] = ls[1:] return d @mask.setter def mask(self, maskstring): """ Mask a Lun Unfortunately, the interface remained the same, but the order of arguments between SRX-6.x and SRX-7.x were reversed, and we have to figure it out here. SRX-6.x usage: mask lun ... [ +mac ... ] [ -mac...] SRX-7.x usage: mask [ {+|-} mac ... ] [ lun ... ] """ if self.version >= 7: luns = str() args = str() for arg in maskstring.split(): if arg[0] == '+' or arg[0] == '-': args += ' ' + arg else: # we have to assume it's a lun luns += ' ' + arg cmd = "mask %s %s" % (args, luns) r = self.run_and_check(cmd) if not r: logger.error(r.message) else: maskstring = maskstring.split() lun = maskstring[0].strip() for curr in maskstring[1:]: if curr[0] == "+": cmd = "echo mask %s > /raid/%s/ctl" % (curr[1:], lun) self.run_and_check(cmd) elif curr[0] == "-": cmd = "echo rmmask %s > /raid/%s/ctl" % (curr[1:], lun) self.run_and_check(cmd) @property def iostats(self): """ Returns io statistics for each lun and it's underlying disks:: {'10': {'id': '10', 'kind': 'lun', 'read': {'MB': '0.000', 'avg': '0', 'max': '0'}, 'write': {'MB': '0.000', 'avg': '0', 'max': '0'}}, '10.0.0': {'id': '10.0.0', 'kind': 'disk', 'read': {'MB': '0.677', 'avg': '2', 'max': '15'}, 'write': {'MB': '0.000', 'avg': '0', 'max': '0'}}, '10.0.1': {'id': '10.0.1', 'kind': 'disk', 'read': {'MB': '0.677', 'avg': '1', 'max': '20'}, 'write': {'MB': '0.000', 'avg': '0', 'max': '0'}} } """ d = dict() r = self.run_and_check('iostats') if not r: return d if not self.sample_warn: self.sample_warn = re.compile( r"warning:[ \t]*iosample[ \t]*smaller[ \t]*than[ \t]*requested[ \t]*\[1 != 3\]") rs = r.message.splitlines() headerlen = 8 columns = ['id', 'read', 'write', 'kind'] for line in rs: if not line or line.startswith('LUN'): continue if self.sample_warn.search(line): continue ls = line.split() if len(ls) < headerlen - 1: continue m = ls[0] # the first token on the line dots = m.count(".") rd = {'MB': ls[1].split('MB')[0], 'avg': ls[2].split('ms')[0], 'max': ls[3].split('ms')[0]} wd = {'MB': ls[4].split('MB')[0], 'avg': ls[5].split('ms')[0], 'max': ls[6].split('ms')[0]} if dots: kind = 'disk' else: kind = 'lun' entry = [ls[0], rd, wd, kind] p = dict(zip(columns, entry)) d[p['id']] = p return d @property def sysstat(self): """ Return the utilization of each cpu in a dict:: {'0': {'cpu': '0', 'idle%': '99', 'int%': '0'}, '1': {'cpu': '1', 'idle%': '99', 'int%': '0'}, '2': {'cpu': '2', 'idle%': '98', 'int%': '0'}, '3': {'cpu': '3', 'idle%': '99', 'int%': '0'}} """ if self.version >= 7: ret = self.expert_run('sysstat', expectation=False) else: cmd = "sysstat" ret = self.run_and_check(cmd) statd = dict() if ret: r = re.split('(?:\r+\n){2}', ret.message.strip()) for cpu in r: curr = dict() for fld in re.split(self.lineterm, cpu): k, v = fld.split('=') curr[k] = v statd[curr['cpu']] = curr return statd
[docs] def smartdisable(self, drives, expectation=True): """ drives is either a single drive as a string, or a list of drives as strings. drives can also be a series-expanded set of drives as a string, eg: '43.0-23'. Returns a ReturnCode. """ if type(drives) == list: drives = ' '.join(drives) cmd = "smartdisable %s" % drives r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def smartenable(self, drives, expectation=True): """ drives is either a single drive as a string, or a list of drives as strings. drives can also be a series-expanded set of drives as a string, eg: '43.0-23'. Returns a ReturnCode. """ if type(drives) == list: drives = ' '.join(drives) cmd = "smartenable %s" % drives r = self.run_and_check(cmd, expectation=expectation) return r
@property def iomode(self): """ 'Luns' should either be a string or a list of strings. Return a dictionary with iomode information:: {'0': {'lun': '0', 'mode': 'sequential'}, '1': {'lun': '1', 'mode': 'random'}, '2': {'lun': '2', 'mode': 'random'}} """ d = dict() r = self.run_and_check('iomode') if not r: return d for line in r.message.splitlines(): if not line or line.startswith('LUN'): continue lun, mode = line.split() d[lun] = {'lun': lun, 'mode': mode} return d
[docs] def setiomode(self, mode, lun, expectation=True): """ Change the io access mode of a lun or list of luns. """ if type(lun) == list: lun = ' '.join(lun) cmd = "setiomode %s %s" % (mode, lun) r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def cecenable(self, ifs): """ 'ifs' is expected to be a single interface as a string, or a list of interfaces as strings. Returns a ReturnCode. Version support: 7 """ if type(ifs) == list: ifs = ' '.join(ifs) return self.run_and_check('cecenable %s' % ifs)
[docs] def cecdisable(self, ifs): """ 'ifs' is expected to be a single interface as a string, or a list of interfaces as strings. Returns a ReturnCode. Version support: 7 """ if type(ifs) == list: ifs = ' '.join(ifs) return self.run_and_check('cecdisable %s' % ifs)
@property def cecstat(self): """ Returns a dictionary containing available interfaces and the cec state for each one (enabled or disabled). For example, this on the CLI:: SRX shelf 43> cecstat NAME CEC ether0 disabled ether1 enabled ether2 disabled ether3 disabled ether4 disabled SRX shelf 43> returns this:: { 'ether0': { 'ifc': 'ether0', 'status': 'disabled'}, 'ether1': { 'ifc': 'ether1', 'status': 'enabled'}, 'ether2': { 'ifc': 'ether2', 'status': 'disabled'}, 'ether3': { 'ifc': 'ether3', 'status': 'disabled'}, 'ether4': { 'ifc': 'ether4', 'status': 'disabled'}} Version support: 7 """ d = dict() r = self.run_and_check('cecstat') if not r: return d lines = r.message.splitlines() for line in lines: if not line or line.startswith('NAME'): continue flds = line.split() if len(flds) < 2: logger.error("parsing failure: '%s'" % line) continue ifc = flds[0] d[ifc] = {'ifc': ifc, 'status': flds[1]} return d
[docs] def replace(self, comp, drive, expectation=True): """ Replace a filed drive.. """ if self.version >= 7: return self.replacedrive(comp, expectation, drive) else: return self._replace_6(comp, drive, expectation)
def _replace_6(self, comp, drive, expectation=True): """ Replace a failed drive. Can replace itself. comp is in 'lun.part.element' format, drive is in 'shelf.slot' format. Returns a ReturnCode. """ cmd = "replace %s %s" % (comp, drive) return self.run_and_check(cmd, expectation=expectation)
[docs] def replacedrive(self, drive, expectation=True, slot=None): # Check this last parameter for None """ Replace a failed component with a new drive (or possibly itself). comp is expected to be in 'lun.part.drive' format. Returns a ReturnCode. """ if not slot: slot = "%s.%s" % (self.shelf, drive.split('.')[-1]) r = self.run_and_check("replacedrive %s %s" % (drive, slot), expectation=expectation) if not r and expectation: raise ApplianceError(r.message) return r
[docs] def resetdrive(self, drives, expectation=True): """ This is only useful when drives enter a connectfail state. 'Drives' is expected to be a string or a list of strings. Returns a ReturnCode. Version support: 7 """ if type(drives) == list: drives = ' '.join(drives) r = self.run_and_check("resetdrive %s" % drives) if not r and expectation: raise ApplianceError(r.message) return r
[docs] def eject(self, luns, expectation=True): """ This will eject one or more luns. Similar to the remove command, but eject does not clear the RAID config on the component drives of a lun. Returns a ReturnCode. """ if self.version >= 7: return self.ejectlun(luns, expectation) else: logger.info("calling old code for eject command") return self._eject_6(luns, expectation)
def _eject_6(self, luns, expectation=True): if type(luns) == dict: luns = aoetostr(luns) elif type(luns) == list: luns = ' '.join(luns) else: luns = str(luns) cmd = 'eject -f %s' % luns return self.run_and_check(cmd, expectation)
[docs] def ejectlun(self, luns, expectation=True): """ This will eject one or more luns. """ if type(luns) == dict: luns = aoetostr(luns) elif type(luns) == list: luns = ' '.join(luns) else: luns = str(luns) cmd = 'ejectlun -f %s' % luns return self.run_and_check(cmd, expectation)
[docs] def restore(self, oldshelf=None, oldlun=None, newlun=None, flagl=None, expectation=True): """ This will restore a lun reading drives's config in an SRX. """ if self.version >= 7: return self.restorelun(oldshelf, oldlun, newlun, flagl, expectation) else: return self._restore_6(oldshelf, oldlun, newlun, flagl, expectation)
def _restore_6(self, oldshelf=None, oldlun=None, newlun=None, flagl=None, expectation=True): cmd = "restore" if flagl: cmd += " -l" if oldshelf: cmd += " %s" % oldshelf if oldlun: cmd += " %s" % oldlun if newlun: cmd += " %s" % newlun r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def restorelun(self, oldshelf=None, oldlun=None, newlun=None, flagl=None, expectation=True): """ Returns a ReturnCode. usage: restorelun [ -l ] [ oldshelfno [ oldlun [ newlun ] ] ] Version support: 7 """ cmd = "restorelun" if flagl: cmd += " -l" if oldshelf: cmd += " %s" % oldshelf if oldlun: cmd += " %s" % oldlun if newlun: cmd += " %s" % newlun r = self.run_and_check(cmd, expectation=expectation) return r
[docs] def setslotled(self, state, slots, expectation=True): """ 'Slots' is either a string or a list of strings. Returns a ReturnCode """ if type(slots) == list: slots = ' '.join(slots) r = self.run_and_check("setslotled %s %s" % (state, slots), expectation=expectation) return r
[docs] def slotled(self, slots=None, expectation=True): """ 'Slots' is either a string or a list of strings. Returns a dictionary. """ d = dict() if not slots: slots = str() elif type(slots) == list: slots = ' '.join(slots) r = self.run_and_check("slotled %s" % slots, expectation=expectation) if not r: return d lines = re.split(self.lineterm, r.message) for line in lines: if not line or line.startswith('SLOT'): continue flds = line.split() if len(flds) < 2: logger.error("parsing error: %s" % line) continue d[flds[0]] = {'slot': flds[0], 'state': flds[1]} return d
[docs] def smartlog(self, drives=None): """ 'Drives' can either be a string or a list of strings. Returns a dictionary of drives and any associated S.M.A.R.T info. Version support: 7 """ d = dict() if not drives: drives = str() elif type(drives) == list: drives = ' '.join(drives) r = self.run_and_check("/smartlog %s" % drives) if not r: return d drive = None drivehdr = re.compile(r"^(\d+\.\d+)\s+([a-zA-Z0-9\-_]*)\s+(.*)") info = re.compile(r"^\s+(.*)") lines = re.split(self.lineterm, r.message) for line in lines: if not line or line.startswith('DRIVE'): continue m = re.search(drivehdr, line) if m: drive = m.group(1) d[drive] = {'drive': drive} d[drive]['model'] = m.group(2) d[drive]['fw'] = m.group(3).strip('\r') d[drive]['log'] = str() continue m = re.search(info, line) if m: if not drive: logger.error("parsing failure: %s" % line) continue d[drive]['log'] += m.group(1) return d
@property def spareled(self, expectation=True): """ Returns a string; either 'enabled' or 'disabled'. """ if self.version >= 7: cmd = '/spareled' else: cmd = 'spareled' r = self.run_and_check(cmd, expectation=expectation) return r.message @spareled.setter def spareled(self, state, expectation=True): """ Sets whether the spares' leds will flash or not. """ if state != 'enable' and state != 'disable': raise ApplianceUsage("you're doing it wrong!") if self.version >= 7: cmd = '/spareled' else: cmd = 'spareled' self.run_and_check('{0} {1}'.format(cmd, state), expectation=expectation) @property def syslog(self): """ Returns a dictionary of source, server and local interface:: {'source': '10.176.200.87', 'server': '10.176.110.1', # destinantion 'interface': 'ether0'} """ if self.version >= 7: return self._syslog_7 else: return self._syslog_6 @property def _syslog_7(self): d = {'source': 'unset', 'server': 'unset', 'interface': 'ether0'} r = self.run_and_check('syslog') if not r: return d lines = re.split(self.lineterm, r.message.strip()) if len(lines) < 2: logger.error("parsing failure: '%s'" % r.message) return d flds = lines[1].split() if len(flds) < 2: logger.error("parsing failure: '%s'" % lines[1]) return d d = {'source': flds[0], 'server': flds[1]} return d @property def _syslog_6(self): d = {'source': 'unset', 'server': 'unset', 'interface': 'ether0'} r = self.run_and_check('syslog -p') if not r: return d lines = re.split(self.lineterm, r.message.strip()) d = {'source': lines[1].split(':')[1].strip(), 'server': lines[0].split(':')[1].strip(), 'interface': lines[2].split(':')[1].strip()} return d @syslog.setter def syslog(self, syslog=None): """ Takes a single syslog server IP or a dict as input:: {'source': '10.176.200.87', 'server': '10.176.110.1', # destinantion 'interface': 'ether0'} """ s = dict() if type(syslog) is not dict: s['server'] = syslog s['source'] = "" s['interface'] = 'ether0' else: s = syslog if self.version >= 7: return self.run_and_check('syslog %s' % s['server']) else: result = ReturnCode(True) # Configure destination IP self.run('syslog -c', force=True, ans=s['server']) result.message = self.run(s['source'], force=True, ans=s['interface']) logger.info(result.message)
[docs] def syslogtest(self, msg, sev=None): """ Send a test syslog message """ if self.version >= 7: return self.run_and_check('syslogtest %s' % msg) else: if sev is None: sev = '' else: sev = '-s {0}'.format(sev) return self.run_and_check('syslog {0} {1}'.format(sev, msg))
[docs] def label(self, name, luns, expectation=True): """ Place a label on the requested LUN(s). 'Luns' is either a string or a list of strings. """ if type(luns) == list: luns = ' '.join(luns) return self.run_and_check('label %s %s' % (name, luns), expectation)
[docs] def unlabel(self, luns, expectation=True): """ Removes any label from the requested LUN(s). 'Luns' is either a string or a list of strings. """ if type(luns) == list: luns = ' '.join(luns) return self.run_and_check('unlabel %s' % luns, expectation)
[docs] def setvlan(self, vlanid, luns, expectation=True): """ Sets the vlanid for the given LUN(s). A valid vlan id is a number between 1 and 4094 (inclusive). 'Luns' is either a string or a list of strings. Returns a ReturnCode. Version support: 7 """ if type(luns) == list: luns = ' '.join(luns) cmd = "setvlan %s %s" % (vlanid, luns) return self.run_and_check(cmd, expectation=expectation)
[docs] def clrvlan(self, luns, expectation=True): """ Clears any vlan id for the given LUN(s). Version support: 7 """ if type(luns) == list: luns = ' '.join(luns) return self.run_and_check("clrvlan %s" % luns, expectation=expectation)
[docs] def vlans(self, luns=None): """ Returns a dictionary of VLANs for the specified LUNs, or all LUNs on the shelf. Any LUN that is not a part of a VLAN gets an empty value in the VLAN column. Version support: 7 """ if not luns: luns = str() if type(luns) == list: luns = ' '.join(luns) d = dict() r = self.run_and_check('vlans %s' % luns) if not r: return d lines = re.split(self.lineterm, r.message) for line in lines: if not line or line.startswith('LUN'): continue flds = line.split() d[flds[0]] = {'lun': flds[0], 'vlan': None} if len(flds) > 1: d[flds[0]]['vlan'] = flds[1] return d
[docs] def lunfailguarddisable(self, lun, expectation=True): """ Disable fail guard on specified lun. """ if self.version >= 7: return self._lunfailguarddisable(lun, expectation) else: return self._lunfailguarddisable_6(lun, expectation)
def _lunfailguarddisable(self, lun, expectation=True): """ Disable fail guard on specified lun. Don't call this directly. Call lunfailguarddisable instead. """ cmd = "/lunfailguarddisable -f %s" % lun return self.run_and_check(cmd, expectation) def _lunfailguarddisable_6(self, lun, expectation=True): """ Disable fail guard on specified lun. Don't call this directly. Call lunfailguarddisable instead. """ cmd = "setlunfailguard -f off %s" % lun return self.run_and_check(cmd, expectation)
[docs] def lunfailguardenable(self, lun, expectation=True): """ Enable fail guard on specified lun. """ if self.version >= 7: return self._lunfailguardenable(lun, expectation) else: return self._lunfailguardenable_6(lun, expectation)
def _lunfailguardenable(self, lun, expectation=True): """ Enable fail guard on specified lun. Don't call this directly. Call lunfailguardenable instead. """ cmd = "/lunfailguardenable -f %s" % lun return self.run_and_check(cmd, expectation) def _lunfailguardenable_6(self, lun, expectation=True): """ Enable fail guard on specified lun. Don't call this directly. Call lunfailguardenable instead. """ cmd = "setlunfailguard -f on %s" % lun return self.run_and_check(cmd, expectation) @property def timezone(self): """ The timezone command returns a string containing the srx timezone. Version support: 7 """ if self.version >= 7: cmd = "timezone" r = self.run_and_check(cmd) out = r.message timezone = out.split()[1] else: raise ApplianceUsage("The 'timezone' command doesn't exist for SRX 6.x and lower.") return timezone @timezone.setter def timezone(self, timezone): """ Change the timezone Version support: 7 """ if self.version >= 7: cmd = 'timezone %s' % timezone self.run_and_check(cmd) else: raise ApplianceUsage("The 'timezone' command doesn't exist for SRX 6.x and lower.") @property def timezones(self): """ The timezones command returns a list containing the available timezones. Version support: 7 """ if self.version >= 7: cmd = "timezone -l" r = self.run_and_check(cmd) out = r.message timezones = out.split() timezones.remove("Available") timezones.remove("timezones:") else: raise ApplianceUsage("The 'timezone' command doesn't exist for SRX 6.x and lower.") return timezones @property def timezone_list(self): return self.timezones @property def service(self): """ The service property returns a dictionary of services:: {'ftp': {'name': 'ftp', 'status': 'enabled'}, 'ntp': {'name': 'ntp', 'status': 'disabled'}, 'ssh': {'name': 'ssh', 'status': 'enabled'}} Version support: 7 """ services = {} if self.version >= 7: cmd = "service" r = self.run_and_check(cmd) lines = re.split(self.lineterm, r.message.strip()) for line in lines: line = line.strip() if not line or line.startswith('SERVICE'): continue name, status = line.split() services[name] = {} services[name]['name'] = name services[name]['status'] = status else: raise ApplianceUsage("The 'service' command doesn't exist for SRX 6.x and lower.") return services
[docs] def disable_service(self, service, expectation=True): """ Disable a service Version support: 7 """ if self.version >= 7: if service not in ('ftp', 'ntp', 'ssh'): raise ApplianceUsage("unknown service: %s" % service) cmd = "service %s disable" % service return self.run_and_check(cmd, expectation) else: raise ApplianceUsage("The 'service' command doesn't exist for SRX 6.x and lower.")
[docs] def enable_service(self, service, expectation=True): """ Enable a service Version support: 7 """ if self.version >= 7: if service not in ('ftp', 'ntp', 'ssh'): raise ApplianceUsage("unknown service: %s" % service) cmd = "service %s enable" % service return self.run_and_check(cmd, expectation) else: raise ApplianceUsage("The 'service' command doesn't exist for SRX 6.x and lower.")
def _set_disks(self, diskd): self.cache['disks'] = diskd for slot, data in diskd.iteritems(): setattr(self, "s%s" % slot, Namespace(data)) def _enumerate_slots(self): cmd = "ls /raiddev/| grep [0-9]+ | wc -l" ret = self.expert_run(cmd) if ret: return int(ret.message) else: raise ApplianceError("can't enumerate slots:%s\n returns\n%s" % (cmd, ret.message))
class SrxApplcon(Srx): """ Creates an Srx object that uses a specified gateway vsx to connect. This will allow multiple users to control the Srx without interfering with one another. """ def __init__(self, vsxuser, vsxaddr, vsxpassword, shelf, use_slots=None): self.vsx = Vsx(self.user, self.vsxaddr, self.password) self.run = self.vsx.run self.user = vsxuser self.vsxaddr = vsxaddr self.shelf = shelf self.prompt = '(LD|SR|SRX)\sshelf\s(unset|\d.*)>' self.sample_warn = None self.confirm = None self.confirm_update_lun_format = None self.host = self.shelf self.use_slots = use_slots self.password = vsxpassword self.closed = True # since we didn't call __spawn self.lineterm = '\r+\n' self.driveahdr = None self.sample_warn = None self.confirm = None self.confirm_update_lun_format = None self.cache = dict() def connect(self, timeout=40, args=None): """ connect to a vsx and tunnel through applcon """ if args == 'esm': self.vsx.prompt = r'ESM IP \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}>' self.vsx.connect() if args == 'esm': self.vsx.prompt = r'ESM EXPERTMODE# ' else: self.vsx.prompt = 'VSX EXPERTMODE# ' self.vsx.run('/expertmode') self.vsx.prompt = self.prompt r = self.vsx.run("applcon %s" % self.shelf) if r: self.closed = False version = self.run('release') if version.startswith('RELEASE'): version = r.split()[-1] result = re.match(r"^SR[X]?-([0-9]+)\.([0-9]+).*", version) if result: self.version = int(result.group(1)) else: raise ApplianceError('Unable to identify SRX version running in shelf: %s: %s' % (result, r)) return r @property def match_index(self): return self.vsx.match_index
[docs]class SrxSsh(Ssh, Srx): """ A class for interacting with the Srx using ssh. Since the commands are basically passed through see the Srx manual for more info. extended parameters:: expectation (Boolean) if False the library will not raise exceptions for error: or usage: force (Boolean) if True the method walks through the acceptance dialog """ def __init__(self, user, host, password, prompt=None, use_slots=None): self.user = user self.host = host self.password = password self.striped = False self.prompt = prompt self.version = 7 if prompt is None: self.prompt = 'SRX\sshelf\s(unset|inactive|\d.*)>\s' self.sample_warn = None self.confirm = None self.confirm_update_lun_format = None self.cache = dict() self.lineterm = '\r+\n' self.driveahdr = None self.use_slots = use_slots self.connected = False
[docs] def connect(self, timeout=40, args=None): """ Calls Ssh's connection method which will connect to and authenticate with host """ ret = super(SrxSsh, self).connect(timeout=timeout, args=None) if ret: self.connected = True ret = self.run_and_check('shelf') if ret: self.shelf = ret.message.strip() self.slots = self._enumerate_slots() for slot in range(int(self.slots)): setattr(self, 's%s' % slot, Drive(self.shelf, slot, self.expert_run)) return ret
[docs] def disconnect(self): """ Calls Ssh's disconnect method """ return super(SrxSsh, self).disconnect()
[docs] def reconnect(self, after=10, timeout=40, attempts=10): """ Calls Ssh's reconnection method which will reconnect to and authenticate with host """ # Needs to be removed sleep(3 * 60) # srx 7 takes about 3 minutes to reboot failed = 0 if timeout is None: timeout = self.timeout self.connected = False self.close() start = now() while not self.connected: sleep(after) try: if self.connect(timeout=timeout): break else: if failed > attempts: raise ApplianceError else: failed += 1 except (TIMEOUT, EOF) as e: self.connected = False self.close() logger.debug("reconnected after %s" % timefmt(since(start))) return True