def walk(self, oid): """ Perform an SNMP walk Params ----- oid : string The starting OID """ version = '-v1' if self.version == 2: version = '-v2' args = [ snmpwalk_cmd, '-t', '120', version, '-c', self.readCommunity, self.host, oid ] Log.debug(100, ' '.join(args)) (output, exitcode) = Command.run(args) values = None if exitcode == 0 and output: values = [] for line in output: line.rstrip('\n') if len(line) > 0: values.append(self.parseSNMPLine(line)) return values
def clone(self, params): if params is None: raise ValueError('zfs clone function received no parameters') command = ['clone'] snapshot = params.get('src', None) properties = params.get('properties', None) dest = params.get('dst', None) createParent = params.get('createParent', None) if not snapshot: raise ValueError('zfs clone function needs a snapshot parameter') if not dest: raise ValueError('zfs clone function needs a dest parameter') if createParent: command.append('-p') if not properties is None: for property, value in properties.iteritems(): command.append('-o') command.append('%s=%s' % (property, value)) command.append(snapshot) command.append(dest) if self.all.has_key(snapshot): if self.all.has_key(dest): Log.info('%s already exists. Not creating clone' % dest) else: self.run(command) else: raise ValueError('Snapshot %s does not exist, clone %s cannot be created' % (snapshot, dest))
def create_tpg(self, target, tag): tpg_list = self.target[target.wwn]['tpg'] tpg_list_tag = tpg_list.get(tag, None) if tpg_list_tag is None: Log.info('creating tpg (%s, %s)' % (target, tag)) # Create and configure the target portal group tpg = TPG(target, tag) tpg.set_attribute("authentication", 0) tpg.enable = 1 # Set up the list of TPGs for this target tpg_list[tag] = { 'tpg': tpg, 'acl': {'mapped_lun': {}}, 'lun': {}, 'portal': {} } else: Log.info('tpg (%s, %s) already exists, not creating' % (target, tag)) tpg = tpg_list[tag]['tpg'] return tpg
def parseSNMPLine(self, line): """ Private member, parses a line of output from snmpwalk Returns ------- A tuple (oid, value) """ oid = None value = None # Split the line on the first '=' character parts = line.split('=', 1) if len(parts) == 2: oid = parts[0].rstrip().lstrip() value = parts[1].rstrip().lstrip() valueParts = value.split(':', 1) if len(valueParts) == 2: type = valueParts[0].rstrip().lstrip() value = valueParts[1].rstrip().lstrip() if type == 'STRING': value = value.rstrip('"').lstrip('"') else: Log.error('Cannot parse SNMP value: "%s"' % (valueType)) else: Log.error('Cannot parse SNMP key/value pair: "%s"' % (line)) return (oid, value)
def walk(self, oid): """ Perform an SNMP walk Params ----- oid : string The starting OID """ version = '-v1' if self.version == 2: version = '-v2' args = [snmpwalk_cmd, '-t', '120', version, '-c', self.readCommunity, self.host, oid] Log.debug(100, ' '.join(args)) (output, exitcode) = Command.run(args) values = None if exitcode == 0 and output: values = [] for line in output: line.rstrip('\n') if len(line) > 0: values.append(self.parseSNMPLine(line)) return values
def putDocument(self, docId, params): """ PUT a document on the CouchDB server. If the document already exists, you will need to pass in a _rev param with the revision you would like to update. The saveDocument() method handles this for you, if all you want to do is save a document, regardless of whether or not it already exists. Params ------ docId : string The unique ID of the document params : dictionary A dictionary of parameters that define the document """ path = '/%s/%s' % (self.db, docId.replace(' ', '+')) Log.debug(100, 'Putting %s' % path) connection = httplib.HTTPConnection(self.host, self.port) connection.connect() params['updatedAt'] = CouchDB.now() connection.request('PUT', path, json.dumps(params), { 'Content-Type': 'application/json' }) result = json.loads(connection.getresponse().read()) if result.has_key(u'error'): Log.error('PUT %s: %s' % (path, result)) return result
def __init__(self, params): Log.info('params: %s' % params) required = ['target_ip', 'target_wwn', 'target_lun', 'target_part', 'console_port', 'console_speed', 'console_params', 'ctrl_iface', 'ctrl_mac', 'initiator_fqdn', 'initiator_wwn'] missing = [] for arg in required: if params.get(arg, None) is None: missing.append(arg) Log.info(params) if len(missing) == 1: raise ValueError('Missing parameter: %s' % missing[0]) elif len(missing) > 1: raise ValueError('Missing parameters: %s' % ', '.join(missing)) params['device'] = self.iscsi_device(params) params['chroot'] = self.chroot_dir(params) # Something outside this library lowercase the wwn, so # we lowercase the input to stay consistent params['target_wwn'] = params['target_wwn'].lower() self.prep(params)
def powerState(self): """ Get the power state for all slots """ walkAttr = 'remoteControlBladePowerState' oid = 'BLADE-MIB::%s' % walkAttr # Start async snmpwalk. This call returns immediately # and spawns a background thread to perform the SNMPWalk snmpWalk = SNMPWalk.withConfigFile(self.host, oid) state = [] # While the snmpwalk is running while not snmpWalk.eof: # Get snmp oid/value pairs off the queue while not snmpWalk.queue.empty(): (oid, value) = snmpWalk.queue.get() (mibName, attr, lastOctet) = snmpWalk.extractOidParts(oid) if attr == walkAttr: if value == 'on(1)': state.append(1) else: state.append(0) snmpWalk.join() Log.debug(100, 'ch %03d power state: %s' % (self.num, state)) return state
def retrieveWithMac(cls, couch, mac): """ Find a slot that matches the specified mac address Params ------ couch: object couch database connection mac: string The mac address for which to search Returns ------- Dictionary of key/value pairs for the slot that matches the specified mac address """ if mac == 'not available' or mac == 'not installed': return None output = couch.getView('slot', 'mac_to_slot', mac) Log.debug(200, output) if output and len(output) > 0: key, value = output.popitem() if value: return value return None
def create_tpg(self, target, tag): tpg_list = self.target[target.wwn]['tpg'] tpg_list_tag = tpg_list.get(tag, None) if tpg_list_tag is None: Log.info('creating tpg (%s, %s)' % (target, tag)) # Create and configure the target portal group tpg = TPG(target, tag) tpg.set_attribute("authentication", 0) tpg.enable = 1 # Set up the list of TPGs for this target tpg_list[tag] = { 'tpg': tpg, 'acl': { 'mapped_lun': {} }, 'lun': {}, 'portal': {} } else: Log.info('tpg (%s, %s) already exists, not creating' % (target, tag)) tpg = tpg_list[tag]['tpg'] return tpg
def getViewDict(self, design, view, params = {}): """ Get a view, return the results as a dictionary of key => values. If the particular key occurs multiple times, then the value will be an array of values for that key Params ------ design: string The design document name view: string The view name params: dictv Dictionary of parameters: key: The key to which results will be limited start: The starting key for the range to which results will be limited end: The ending key for the range to which results will be limited includeDocs: Set this to anything, and the entire document will be returned as the value Returns ------- A dictionary of view key/value pairs. If no results were returned, then None is returned """ path = self.getViewPath(design, view, params) Log.debug(10, 'Getting view path %s' % path) output = self.getDocument(path) results = {} if output.has_key(u'total_rows'): Log.debug(10, '%s returned %s rows' % (path, output[u'total_rows'])) if output.has_key(u'rows'): for row in output[u'rows']: key = row[u'key'] value = row.get(u'value', None) if params.has_key('includeDocs'): value = row.get(u'doc', None) # If this key is already in the results, then append this value # to the list if results.has_key(key): storedValue = results[key] # If the stored value is a dict, change to an array of dicts if isinstance(storedValue, (frozenset, list, set, tuple)): storedValue.append(value) value = storedValue else: value = [storedValue, value] results[key] = value # If no results, then return a None object if len(results) <= 0: results = {} return results
def discoverIncorrectBIOSSettings(cls, expectedFilename, dumpCmd): """ Discover incorrect BIOS settings Params ------ expectedFilename: string A filename of expected bios settings dumpCmd: array Command to dump bios settings Returns A dictionary of key value pairs of incorrect bios settings """ try: # Read expected bios settings expectedFile = Blade.openFile(expectedFilename) expected = Blade.parseBios(expectedFile) # Read actual bios settings (actualFile, exitcode) = Command.run(dumpCmd) actual = Blade.parseBios(actualFile) return Blade.compareBiosSettings(expected, actual) except (OSError, IOError) as e: Log.error(str(e)) sys.exit(1) return None
def powerCycle(self): """ Power cycle all slots """ if self.ping(): for slot in range(1, 15): self.powerCycleSlot(slot) else: Log.info('%s not pingable, cannot power cycle all blades' % self.host)
def collectChassisInfo(self, snmp): """ Retrieve chassis information """ for (attr, oid) in self.chassisOidDict.items(): values = snmp.walk(oid) if values: Log.debug(10, '%s: %s' % (attr, values[0][1])) setattr(self, attr, values[0][1])
def create(self, params): '''zfs create params -- { name: name, properties: {property: value, p2: v2, ...}, volume: {size: size, sparse: true|false} createParent: true or false} } ''' # If no arguments provided, return immediately if params is None: raise ValueError('zfs create function received no parameters') command = ['create'] name = params.get('name', None) properties = params.get('properties', None) volume = params.get('volume', None) createParent = params.get('createParent', None) if not name: raise ValueError('zfs create function needs a name parameter') if createParent: command.append('-p') if not properties is None: for property, value in properties.iteritems(): command.append('-o') command.append('%s=%s' % (property, value)) if not volume is None: size = volume.get('size', None) sparse = volume.get('sparse', None) if not size: raise ValueError('Volumes must have a size attribute') command.append('-V') command.append(size) if sparse and sparse != False: command.append('-s') command.append(name) # Flag is set if the dataset is created created = None if self.all.has_key(name): if volume: Log.info('%s already exists. Not creating volume' % name) else: Log.info('%s already exists. Not creating filesystem' % name) else: self.run(command) created = True return created
def create_mapped_lun(self, acl, num, lun): mapped_lun = None if not list(acl.mapped_luns): Log.info('creating mapped lun (%s, %s, %s)' % (acl, num, lun)) mapped_lun = MappedLUN(acl, num, lun) else: Log.info('mapped lun (%s, %s, %s) already exists' % (acl, num, lun)) return mapped_lun
def run(self, args): '''Run a zfs command''' cmd = args cmd.insert(0, self.zfs) output = '' try: Log.info(' '.join(cmd)) output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).split('\n') except subprocess.CalledProcessError, e: raise ValueError(str(e.output))
def create_task(self, job_type_name): try: args = self.array_parser.parse_args() details = LUNCloneJob.with_args(args, job_type_name) Log.info('%s job %s added to queue' % (job_type_name, details.id)) return {'status': 'ok', 'job_id': details.id}, 201 except (Exception), e: Log.error('%s:\n%s' % (str(e), traceback.format_exc())) return {'status': str(e), 'stacktrace': traceback.format_exc()}, 501
def collectAndClearEventLog(self, couch): log = {} # OIDs to retrieve oids = ['BLADE-MIB::readEnhancedEventLogAttribute', 'BLADE-MIB::readEnhancedEventLogMessage'] # Get the mapping of blade serial numbers to blade document ids serialNumberToDocId = Blade.serialNumberToDocId(couch) for oid in oids: # Start async snmpwalk. This call returns immediately # and spawns a background thread to perform the SNMPWalk snmpWalk = SNMPWalk.withConfigFile(self.host, oid) # While the snmpwalk is running while not snmpWalk.eof: # Get snmp oid/value pairs off the queue while not snmpWalk.queue.empty(): (oid, value) = snmpWalk.queue.get() (mibName, oidBase, lastOctet) = snmpWalk.extractOidParts(oid) if oidBase != 'readEnhancedEventLogNumber': # Start with an empty dictionary dict = {} # Get the existing log entry, if it exists if log.has_key(lastOctet): dict = log[lastOctet] # Update the dictionary with this line from the snmpwalk if oidBase == 'readEnhancedEventLogAttribute': dict.update(self.parseEventLogAttribute(value, serialNumberToDocId)) else: match = re.search('^Text:(.*)$', value) if match: value = match.group(1) dict.update({'message': value}) # Update the log entry list log[lastOctet] = dict # On the final snmp walk command, create CouchDB objects if dict and oidBase == 'readEnhancedEventLogMessage': logEntry = LogEntry(dict) logEntry.persist(couch) # Join snmpwalk background thread snmpWalk.join() snmp = SNMP.withConfigFile(self.host) snmp.set('BLADE-MIB::clearEventLog.0', 'i', '1') Log.info('%s system log entries collected from %s' % (len(log), self.name))
def run(self, args): '''Run a command, capture stderr and report the exception, if an exception happens''' Log.info('%s' % ' '.join(args)) pipes = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = pipes.communicate() if pipes.returncode != 0: # an error happened! err_msg = "%s. Code: %s" % (stderr.strip(), pipes.returncode) raise Exception(err_msg)
def run(cls, args): output = None exitCode = 0 try: Log.debug(100, ' '.join(args)) proc = Popen(args, stdout=PIPE) output = proc.communicate()[0].split('\n') exitCode = proc.returncode except (OSError) as e: Log.error(str(e)) return (output, exitCode)
def powerOnOff(self, func): """ Power on / off all slots in the chassis func: int 0 = off, 1 = on, 2 = soft off """ if self.ping(): for slot in range(1, 15): self.powerOnOffSlot(slot, func) else: Log.info('%s is not pingable, cannot apply power func %d all slots' % (self.host, func))
def run(cls, args): output = None exitCode = 0 try: Log.debug(100, " ".join(args)) proc = Popen(args, stdout=PIPE) output = proc.communicate()[0].split("\n") exitCode = proc.returncode except (OSError) as e: Log.error(str(e)) return (output, exitCode)
def delete_block_store(self, name): store = self.block_store.get(name) # If blockstore doesn't exist, do not proceed if store is None: Log.info('No block store %s. Not deleting' % name) return Log.info('deleting block store %s' % (name)) # Delete the block store. The backing device, file, etc, still exists store.delete() del self.block_store[name]
def getView(self, design, view, key = None): """ Get a view Params ------ design: string The design document name view: string The view name key: string (optional) a key to select Returns ------- A dictionary of view key/value pairs. If no results were returned, then None is returned """ path = '_design/%s/_view/%s' % (design, view) if key: path = '%s?key="%s"' % (path, key) output = self.getDocument(path) results = {} if output.has_key(u'total_rows'): Log.debug(10, '%s returned %s rows' % (path, output[u'total_rows'])) if output.has_key(u'rows'): for row in output[u'rows']: value = row[u'value'] key = row[u'key'] if results.has_key(key): storedValue = results[key] # If the stored value is a dict, change to an array of dicts if isinstance(storedValue, (frozenset, list, set, tuple)): storedValue.append(value) value = storedValue else: value = [storedValue, value] results[key] = value # If no results, then return a None object if len(results) <= 0: results = {} return results
def process(self): try: errors = [] for task in LUNCloneJob.pending(): try: Log.info('%s job %s started' % (task.job_type.code, task.id)) task.set_status_start_job() if task.is_create(): self.create_clones(task) else: self.delete_clones(task) task.set_status_complete() Log.info('%s job %s completed' % (task.job_type.code, task.id)) except (Exception), e: error = u'%s:\n%s' % (str(e), traceback.format_exc()) Log.error('%s job %s failed' % (task.job_type.code, task.id)) Log.error(error) task.set_status_failed(error) errors.append(task.error) if len(errors) > 0: message = {'message': errors} return {'status': 'error', 'errors': errors}
def remove_clone(self, task, zfs, args): # If deleteClones = true, then the dst parameter is also required. deleteClones = args.get('deleteClones', None) if deleteClones: if args.get('dst', None) is None: message = {'message': 'deleteClones parameter requires dst ' 'parameter'} return message zfs.destroy(args) else: Log.info('Delete task %s did not specify deleteClones param for %s' % (task.id, args[u'dst']))
def snapshot(self, params): if params is None: raise ValueError('zfs snapshot function received no parameters') name = params.get('name', None) if name is None: raise ValueError('zfs snapshot function needs a name parameter') if not self.all.has_key(name): command = ['snapshot', name] self.run(command) else: Log.info('%s already exists. Not creating snapshot' % name)
def powerOnOff(self, func): """ Power on / off all slots in the chassis func: int 0 = off, 1 = on, 2 = soft off """ if self.ping(): for slot in range(1, 15): self.powerOnOffSlot(slot, func) else: Log.info( '%s is not pingable, cannot apply power func %d all slots' % (self.host, func))
def run(self): """ Perform an SNMP walk """ version = '-v1' if self.version == 2: version = '-v2' command = [ snmpwalk_cmd, '-t', '120', version, '-c', self.readCommunity, self.host, self.oid ] Log.debug(100, ' '.join(command)) # Launch the command as subprocess. process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Launch the asynchronous readers of the process' stdout and stderr. stdoutQueue = Queue.Queue() stdoutReader = AsynchronousFileReader(process.stdout, stdoutQueue) stdoutReader.start() stderrQueue = Queue.Queue() stderrReader = AsynchronousFileReader(process.stderr, stderrQueue) stderrReader.start() # Check the queues if we received some output (until there is nothing more to get). while not stdoutReader.eof() or not stderrReader.eof(): # Show what we received from standard output. while not stdoutQueue.empty(): line = stdoutQueue.get() self.queue.put(self.split(line)) # Show what we received from standard error. while not stderrQueue.empty(): line = stderrQueue.get() print 'Received line on standard error: ' + repr(line) # Sleep a bit before asking the readers again. time.sleep(.1) # Let's be tidy and join the threads we've started. stdoutReader.join() stderrReader.join() # Close subprocess' file descriptors. process.stdout.close() process.stderr.close()
def bladeInfo(self, snmp, inOid): """ Retrieve information about blades in a chassis """ values = snmp.walk(inOid) slotValues = [] for (oid, value) in values: slot = snmp.extractLastOctet(oid) if slot: slotValues.append( (slot, value) ) if not slotValues: Log.error('No results for %s %s' % (host, inOid)) return slotValues
def bladeInfo(self, snmp, inOid): """ Retrieve information about blades in a chassis """ values = snmp.walk(inOid) slotValues = [] for (oid, value) in values: slot = snmp.extractLastOctet(oid) if slot: slotValues.append((slot, value)) if not slotValues: Log.error('No results for %s %s' % (host, inOid)) return slotValues
def create_portal(self, tpg, ip, port): portal = None portal_id = self.get_portal_id(ip, port) wwn = tpg.parent_target.wwn portal_list = self.target[wwn]['tpg'][tpg.tag]['portal'] if portal_id in portal_list: Log.info('portal %s already exists, not creating' % (portal_id)) portal = portal_list[portal_id] else: Log.info('creating portal (%s, %s, %s)' % (tpg, ip, port)) portal = NetworkPortal(tpg, ip, port) portal_list[portal_id] = portal return portal
def create_acl(self, tpg, initiator_name): acl = None wwn = tpg.parent_target.wwn acl_list = self.target[wwn]['tpg'][tpg.tag]['acl'] if initiator_name in acl_list: Log.info('acl (%s, %s) already exists, not creating' % (tpg, initiator_name)) acl = acl_list[initiator_name] else: Log.info('creating acl (%s, %s)' % (tpg, initiator_name)) acl = NodeACL(tpg, initiator_name) acl_list[initiator_name] = acl return acl
def destroy(self, params): # Verify that parameters were received if params is None: raise ValueError('zfs destroy function received no parameters') # Get the name name = params.get('dst', None) if not name: raise ValueError('zfs destroy function needs a name parameter') if self.all.has_key(name): command = ['destroy', name] self.run(command) else: Log.info('%s does not exist. Cannot destroy' % name)
def create_lun(self, tpg, blockstore): wwn = tpg.parent_target.wwn lun_list = self.target[wwn]['tpg'][tpg.tag]['lun'] lun = lun_list.get(blockstore.name, None) if lun is None: Log.info('creating lun %s, blockstore %s' % (tpg, blockstore)) # Create the LUN lun = LUN(tpg, 0, blockstore) # Add it to the local data structure for tracking LUNs lun_list[blockstore.name] = lun else: # LUN already exists Log.info('lun %s already exists, not creating' % (blockstore.name)) return lun
def delete(self, params): """ Delete a document. Params must have an _id and _rev param """ path = '/%s/%s?rev=%s' % (self.db, params['_id'].replace(' ', '+'), params['_rev'].replace(' ', '+')) Log.debug(10, 'DELETE %s' % path) connection = httplib.HTTPConnection(self.host, self.port) connection.connect() connection.request('DELETE', path) result = json.loads(connection.getresponse().read()) if result.has_key(u'error'): if result[u'error'] != u'not_found': Log.error('GET %s: %s' % (path, result)) return result
def create_target(self, wwn): target_dict = self.target.get(wwn, None) target = None if target_dict is None: Log.info('creating target with wwn %s' % (wwn)) # The wwn will be lowercased automatically by something # outside this library. I'm not sure if its RTSLib or # the underlying Linux target system target = Target(self.iscsi, wwn) # Add target to data structure, initialize empty child nodes self.target[wwn] = {'target': target, 'tpg': {}} else: Log.info('target %s already exists, not creating' % (wwn)) target = target_dict['target'] return target
def remove_clone(self, task, zfs, args): # If deleteClones = true, then the dst parameter is also required. deleteClones = args.get('deleteClones', None) if deleteClones: if args.get('dst', None) is None: message = { 'message': 'deleteClones parameter requires dst ' 'parameter' } return message zfs.destroy(args) else: Log.info( 'Delete task %s did not specify deleteClones param for %s' % (task.id, args[u'dst']))
def allBladesFailToNetboot(cls, couch): """ Find a list of chassis where all blades failed to netboot Params ------ couch: object A CouchDB object Returns ------- A list of chassis docId """ results = couch.getView('blade', 'failed_netboot') chassis = {} failedChassis = [] for bladeDocId in results: slotInfo = Slot.retrieveWithMac(couch, bladeDocId) chassisDocId = None slotNum = None if slotInfo: Log.debug(200, 'slotInfo: %s' % slotInfo) if slotInfo.has_key(u'chassisDocId'): chassisDocId = slotInfo[u'chassisDocId'] if slotInfo.has_key(u'num'): slotNum = slotInfo[u'num'] if chassisDocId and slotInfo: if chassis.has_key(chassisDocId): chassis[chassisDocId].append(slotNum) else: chassis[chassisDocId] = [slotNum] for chassisDocId in sorted(chassis.iterkeys()): slots = chassis[chassisDocId] if len(slots) >= 14: failedChassis.append(chassisDocId) return failedChassis
def mount(self, params): '''Mount an image Parameters ---------- params : string Image parameter dictionary ''' chroot = params['chroot'] device = params['device'] Log.info('device %s' % device) # Sanity check, destination shouldn't be a file if os.path.isfile(chroot): log_error_node(params, '%s is a file, can not mount %s at this location' % (chroot, device, params['vnode_name'])) return # Make sure the destination exists if not os.path.exists(chroot): log_info_node(params, 'mkdir %s' % chroot) os.makedirs(chroot) # If the destination is already mounted, then # no need to mount again. It's likely the correctly mounted # device from a previous prep run that died if not os.path.ismount(chroot): log_info_node(params, 'mount %s %s' % (device, chroot)) self.run([mount_cmd, device, chroot]) else: log_info_node(params, '%s already mounted, hoping for the best' % chroot) # Mount /sys, /proc and /dev in image chroot bind_mounts = ['/sys', '/proc', '/dev'] if not os.path.ismount(chroot): log_error_node(params, 'mount of %s failed, cannot mount %s in chroot' % (chroot, bind_mounts)) else: for bind_mount in bind_mounts: dest = chroot + bind_mount log_info_node(params, 'mount -o bind %s %s' % (bind_mount, dest)) self.run([mount_cmd, '-o', 'bind', bind_mount, dest])
def getDocument(self, docId): """ GET a document from the server Params ------ docId : string The unique ID of the document """ path = '/%s/%s' % (self.db, docId.replace(' ', '+')) connection = httplib.HTTPConnection(self.host, self.port) connection.connect() connection.request('GET', path) result = json.loads( connection.getresponse().read()) if result.has_key(u'error'): if result[u'error'] != u'not_found': Log.error('GET %s: %s' % (path, result)) return result