def del_zvol(self, message, props): # input: del_zvol message # output: zvol_deleted message --> requestor # state updates: zvols table, Entry deleted zvol_name = message['zvol'] zpool_name = message['zpool'] self.logger.debug('Deleting zvol %s' % zvol_name) with sqlite3.connect(self.SQLITE_DB) as con: cur = con.cursor() try: self.lock_zvol(zvol_name, props.reply_to) cur.execute( 'SELECT remotehost, iscsi_target FROM zvols WHERE zvol = ?', [zvol_name]) row = cur.fetchone() if row == None: raise ActionError('ZVol %s not found in database' % zvol_name) if row[0] != None: raise ActionError('Error deleting zvol %s: is mapped' % zvol_name) self.logger.debug('Invoking zfs destroy %s/%s' % (zpool_name, zvol_name)) yield runCommandBackground([ 'zfs', 'destroy', '%s/%s' % (zpool_name, zvol_name), '-r' ]) self.logger.debug('zfs destroy success %s' % zvol_name) cur.execute('DELETE FROM zvolattrs WHERE zvol = ?', [zvol_name]) cur.execute('DELETE FROM zvols WHERE zvol = ?', [zvol_name]) con.commit() self.release_zvol(zvol_name) self.queue_connector.publish_message( json.dumps({ 'action': 'zvol_deleted', 'status': 'success' }), exchange='', routing_key=props.reply_to) except ActionError, err: if not isinstance(err, ZvolBusyActionError): self.release_zvol(zvol_name) self.failAction(props.reply_to, 'zvol_deleted', str(err))
def runCommandBackground(cmdlist, shell=False): """ Wrapper around subprocess call using Tornado's Subprocess class. This routine can fork a process in the background without blocking the main IOloop, the the forked process can run for a long time without problem """ LOG = logging.getLogger('imgstorage.imgstoragenas.NasDaemon') LOG.debug('Executing: ' + str(cmdlist)) # tornado.process.initialize() sub_process = tornado.process.Subprocess(cmdlist, stdout=STREAM, stderr=STREAM, shell=shell) # we need to set_exit_callback to fetch the return value # the function can even be empty by it must be set or the # sub_process.returncode will be always None retval = 0 sub_process.set_exit_callback(lambda value: value) (result, error) = \ (yield [Task(sub_process.stdout.read_until_close), Task(sub_process.stderr.read_until_close)]) if sub_process.returncode: raise ActionError('Error executing %s: %s' % (cmdlist, error)) raise Return((result.splitlines(), error))
def my_side_effect(*args, **kwargs): if args[0][:3] == ['iscsiadm', '-m', 'session']: return StringIO(iscsiadm_session_response % (target, bdev)) elif args[0][:3] == ['iscsiadm', '-m', 'discovery']: return StringIO(iscsiadm_discovery_response % target) # find remote targets elif args[0][:3] == ['iscsiadm', '-m', 'node']: raise ActionError('Some error happened')
def schedule_next_sync(self): try: with self.dbconnect() as con: cur = con.cursor() for (zvol, job_result) in self.results.items(): if job_result.ready(): del self.results[zvol] cur.execute( '''SELECT remotehost, is_sending, zvol, zpool, is_delete_remote,remotepool FROM sync_queue WHERE zvol = ?''', [zvol]) row = cur.fetchone() try: if not row: raise ActionError( 'Not found record for %s in sync_queue table' % zvol) (remotehost, is_sending, zvol, zpool, is_delete_remote, remotepool) = row #self.logger.debug('Sync %s is ready' % zvol) job_result.get( ) # will raise exception is there was one during job execution if is_sending: cur.execute( 'SELECT iscsi_target FROM zvols WHERE zvol = ?', [zvol]) target = cur.fetchone()[0] self.queue_connector.publish_message( json.dumps({ 'action': 'sync_zvol', 'zvol': zvol, 'target': target }), remotehost, self.NODE_NAME, on_fail=lambda: self.logger.error( 'Compute node %s is unavailable to sync zvol %s' % (remotehost, zvol ))) # reply back to compute node elif is_delete_remote: cur.execute( 'UPDATE zvols SET remotehost = NULL, remotepool = NULL where zvol = ?', [zvol]) con.commit() self.release_zvol(zvol) except ActionError, msg: self.logger.exception( 'Error performing sync for %s: %s' % (zvol, str(msg))) finally:
def zvol_unmapped(self, message, props): target = message['target'] zvol = message['zvol'] self.logger.debug('Got zvol %s unmapped message' % target) reply_to = None try: with sqlite3.connect(self.SQLITE_DB) as con: cur = con.cursor() # get request destination cur.execute( '''SELECT reply_to, zpool, remotepool, sync FROM zvol_calls JOIN zvols ON zvol_calls.zvol = zvols.zvol WHERE zvols.zvol = ?''', [zvol]) [reply_to, zpool, remotepool, sync] = cur.fetchone() if message['status'] == 'error': raise ActionError( 'Error detaching iSCSI target from compute node: %s' % message.get('error')) if not sync: self.detach_target(target, True) self.release_zvol(zvol) else: self.detach_target(target, False) cur.execute( 'UPDATE sync_queue SET is_delete_remote = 1 WHERE zvol = ?', [zvol]) if cur.rowcount == 0: cur.execute( '''INSERT INTO sync_queue(zvol,zpool,remotehost,remotepool,is_sending, is_delete_remote, time) VALUES(?,?,?,?,0,1,?)''', [ zvol, zpool, props.reply_to, remotepool, time.time() ]) con.commit() self.queue_connector.publish_message(json.dumps({ 'action': 'zvol_unmapped', 'status': 'success' }), exchange='', routing_key=reply_to) except ActionError, err: self.release_zvol(zvol) self.failAction(reply_to, 'zvol_unmapped', str(err))
def unmap_zvol(self, message, props): # input: unmap_zvol message # output: unmap_zvol message --> remotehost # state updates: None zvol_name = message['zvol'] self.logger.debug('Tearing down zvol %s' % zvol_name) with sqlite3.connect(self.SQLITE_DB) as con: cur = con.cursor() try: cur.execute( 'SELECT remotehost, iscsi_target FROM zvols WHERE zvol = ?', [zvol_name]) row = cur.fetchone() if row == None: raise ActionError('ZVol %s not found in database' % zvol_name) (remotehost, target) = row if remotehost == None: raise ActionError('ZVol %s is not mapped' % zvol_name) self.lock_zvol(zvol_name, props.reply_to) self.queue_connector.publish_message( json.dumps({ 'action': 'unmap_zvol', 'target': target, 'zvol': zvol_name }), remotehost, self.NODE_NAME, on_fail=lambda: self.failAction( props.reply_to, 'zvol_unmapped', 'Compute node %s is unavailable' % remotehost)) self.logger.debug('Tearing down zvol %s sent' % zvol_name) except ActionError, err: if not isinstance(err, ZvolBusyActionError): self.release_zvol(zvol_name) self.failAction(props.reply_to, 'zvol_unmapped', str(err)) else: return False
def zvol_mapped(self, message, props): target = message['target'] zvol = None reply_to = None self.logger.debug('Got zvol mapped message %s' % target) with sqlite3.connect(self.SQLITE_DB) as con: try: cur = con.cursor() cur.execute( '''SELECT zvol_calls.reply_to, zvol_calls.zvol, zvols.zpool, zvols.sync FROM zvol_calls JOIN zvols ON zvol_calls.zvol = zvols.zvol WHERE zvols.iscsi_target = ?''', [target]) (reply_to, zvol, zpool, sync) = cur.fetchone() if message['status'] != 'success': raise ActionError( 'Error attaching iSCSI target to compute node: %s' % message.get('error')) if not sync: self.release_zvol(zvol) else: cur.execute('DELETE FROM sync_queue WHERE zvol = ?', [zvol]) cur.execute( '''INSERT INTO sync_queue(zvol,zpool,remotehost,is_sending,is_delete_remote,time,remotepool) SELECT zvol,?,?,1,1,?,remotepool FROM zvols WHERE iscsi_target = ? ''', [zpool, props.reply_to, time.time(), target]) con.commit() self.queue_connector.publish_message(json.dumps({ 'action': 'zvol_mapped', 'bdev': message['bdev'], 'status': 'success' }), exchange='', routing_key=reply_to) except ActionError, err: self.release_zvol(zvol) self.failAction(reply_to, 'zvol_mapped', str(err))
def map_zvol(self, message, props): # input: map_zvol message # output: map_zvol message --> remotehost # state updates: zvol table remotehost = message['remotehost'] remotepool = message['remotepool'] zpool_name = message['zpool'] zvol_name = message['zvol'] sync = message['sync'] try: initiator = message['initiator'] except: raise ActionError( 'Error when setting up: no initiator specified') # print "XXX map_zvol (message): ", message self.logger.debug('Setting zvol %s' % zvol_name) with sqlite3.connect(self.SQLITE_DB) as con: cur = con.cursor() try: self.lock_zvol(zvol_name, props.reply_to) cur.execute( 'SELECT count(*) FROM zvols WHERE zvol = ?', [zvol_name]) volume = "%s/%s" % (zpool_name, zvol_name) if cur.fetchone()[0] == 0: """ Create a zvol, if it doesn't already exist """ # check if volume already exists # print 'XXX checking if zvol %s exists' % volume self.logger.debug('checking if zvol %s exists' % volume) rcode = subprocess.call(["zfs", "list", volume]) # print 'XXX check complete (%s)' % volume self.logger.debug('check complete (%s)' % volume) if rcode != 0: # create the zfs FS yield runCommandBackground(zfs_create + ['-V', '%sgb' % message['size'], volume]) self.logger.debug('Created new zvol %s' % volume) else: self.logger.debug('Vol %s exists' % volume) # Record the creation of the volume cur.execute('''INSERT OR REPLACE INTO zvols(zvol,zpool,iscsi_target,remotehost,remotepool,sync) VALUES (?,?,?,?,?,?) ''' , (zvol_name, zpool_name, None, None, None, False)) con.commit() # print 'XXX Created new zvol %s' % volume self.logger.debug('Created new zvol %s' % volume) cur.execute( 'SELECT remotehost FROM zvols WHERE zvol = ?', [zvol_name]) row = cur.fetchone() if row != None and row[0] != None: # zvol is mapped raise ActionError( 'Error when mapping zvol: already mapped') ip = None use_ib = False if self.ib_net: try: ip = socket.gethostbyname(nodenameToDomain(remotehost, self.ib_net)) use_ib = True except: pass if not use_ib: try: ip = socket.gethostbyname(remotehost) except: raise ActionError('Host %s is unknown' % remotehost) iscsi_target = '' (out, err) = (yield runCommandBackground([ '/opt/rocks/bin/tgt-setup-lun-lock', '-n', zvol_name, '-d', '/dev/%s/%s' % (zpool_name, zvol_name), initiator, ])) for line in out: if 'Creating new target' in line: start = 'Creating new target (name='.__len__() iscsi_target = line[start:line.index(',')] for line in out: if 'Created Target' in line: iscsi_target = line.split()[-1] # print 'XXX Mapped %s to iscsi target %s' % (zvol_name, # iscsi_target) self.logger.debug('Mapped %s to iscsi target %s' % (zvol_name, iscsi_target)) # Update the Zvols table with the target, remote and sync # attributes cur.execute('''INSERT OR REPLACE INTO zvols(zvol,zpool,iscsi_target,remotehost, remotepool,sync) VALUES (?,?,?,?,?,?) ''' , (zvol_name, zpool_name, iscsi_target, remotehost, remotepool, sync)) con.commit() # print 'XXX iscsi target %s inserted into DB' % iscsi_target def failDeliver( target, zvol, reply_to, remotehost, ): self.detach_target(target, True) self.failAction(props.reply_to, 'zvol_mapped', 'Compute node %s is unavailable' % remotehost) self.release_zvol(zvol_name) # Send a map_zvol message to remotehost # XXX: Should rename this message self.queue_connector.publish_message(json.dumps({ 'action': 'map_zvol', 'target': iscsi_target, 'nas': (nodenameToDomain(self.NODE_NAME, self.ib_net) if use_ib else self.NODE_NAME), 'size': message['size'], 'zvol': zvol_name, 'sync': sync, 'remotehost': remotehost, 'remotepool': remotepool, }), remotehost, self.NODE_NAME, on_fail=lambda: failDeliver(iscsi_target, zvol_name, props.reply_to, remotehost)) self.logger.debug('Setting iscsi %s sent' % iscsi_target) except ActionError, err: if not isinstance(err, ZvolBusyActionError): self.release_zvol(zvol_name) self.failAction(props.reply_to, 'zvol_mapped', str(err))