class UKAIFUSE(LoggingMixIn, Operations): ''' UKAI FUSE connector. ''' def __init__(self, config): self._fd = 0 self._config = config self._rpc_client = UKAIXMLRPCClient(self._config) self._rpc_trans = UKAIXMLRPCTranslation() def init(self, path): ''' Initializing code. ''' pass def destroy(self, path): ''' Cleanup code. ''' pass def chmod(self, path, mode): return 0 def chown(self, path, uid, gid): return 0 def create(self, path, mode): return errno.EPERM def getattr(self, path, fh=None): (ret, st) = self._rpc_client.call('getattr', path) if ret != 0: raise FuseOSError(ret) return st def mkdir(self, path, mode): return errno.EPERM def open(self, path, flags): ret = self._rpc_client.call('open', path, flags) if ret != 0: raise FuseOSError(ret) self._fd += 1 return self._fd def release(self, path, fh): self._rpc_client.call('release', path) return 0 def read(self, path, size, offset, fh): # UKAI core returns the data as RPC safe encoded data. ret, encoded_data = self._rpc_client.call('read', path, size, offset) if ret != 0: raise FuseOSError(ret) return self._rpc_trans.decode(encoded_data) def readdir(self, path, fh): return self._rpc_client.call('readdir', path) def readlink(self, path): return errno.EPERM def rename(self, old, new): return errno.EPERM def rmdir(self, path): return errno.EPERM def statfs(self, path): return self._rpc_client.call('statfs', path) def symlink(self, target, source): return errno.EPERM def truncate(self, path, length, fh=None): #return errno.EPERM pass def unlink(self, path): return errno.EPERM def utimens(self, path, times=None): pass def write(self, path, data, offset, fh): # need to convert data to UKAI Core as a RPC safe # encoded data. encoded_data = self._rpc_trans.encode(data) ret, nwritten = self._rpc_client.call('write', path, encoded_data, offset) if ret != 0: raise FuseOSError(ret) return nwritten
class UKAIFUSE(LoggingMixIn, Operations): ''' The UKAIFUSE class provides a FUSE operation implementation. ''' def __init__(self, config): ''' Initializes the UKAUFUSE class. param config: an UKAIConfig instance ''' self._config = config self._rpc_client = UKAIXMLRPCClient(self._config) self._rpc_trans = UKAIXMLRPCTranslation() def init(self, path): ''' Initializes the FUSE operation. ''' pass def destroy(self, path): ''' Cleanups the FUSE operation. ''' pass def chmod(self, path, mode): ''' This interface is provided for changing file modes, howerver UKAI doesn't support any such operation. ''' return 0 def chown(self, path, uid, gid): ''' This interface is provided for changing owndership of a file, howerver UKAI doesn't support any such operation. ''' return 0 def create(self, path, mode): ''' This interface is provided for creating a file. At this moment, UKAI doesn't support creating a virtual disk using this interface. To create a virtual disk image, use the ukai_admin command. ''' raise FuseOSError(errno.EPERM) def getattr(self, path, fh=None): ''' Returns file stat information of a specified file. param path: the path name of a file param fh: the file handle of the file (not used) ''' (ret, json_st) = self._rpc_client.call('getattr', path) if ret != 0: raise FuseOSError(ret) return json.loads(json_st) def mkdir(self, path, mode): ''' This interface is provided for creating a directory, however UKAI doesn't support hierarchical directory structure at this moment. ''' raise FuseOSError(errno.EPERM) def open(self, path, flags): ''' Opens a file specified by the path parameter. param path: the path name of a file param flags: the flags passed via the open(2) system call ''' ret, fh = self._rpc_client.call('open', path, flags) if ret != 0: raise FuseOSError(ret) return fh def release(self, path, fh): ''' Releases a file opened before. param path: the path name of a file param fh: the file handle of the file ''' self._rpc_client.call('release', path, fh) return 0 def read(self, path, size, offset, fh): ''' Reads data from the UKAI core filesystem. param path: the path name of a file param size: the size to be read param offset: the offset from the beginning of the file param fh: the file handle of the file ''' # The data returned by the UKAICore.read() method is encoded # using a RPC encorder. ret, encoded_data = self._rpc_client.call('read', path, str(size), str(offset)) if ret != 0: raise FuseOSError(ret) return self._rpc_trans.decode(encoded_data) def readdir(self, path, fh): ''' Returns directory entries of a path. param path: a path name to be investigated ''' return self._rpc_client.call('readdir', path) def readlink(self, path): ''' This interface is provided for reading a symbolic link destination, however UKAI doesn't support symbolic links. ''' raise FuseOSError(errno.EPERM) def rename(self, old, new): ''' This interface is provided for renaming (moving) a file path, however UKAI doesn't support a rename operation. ''' raise FuseOSError(errno.EPERM) def rmdir(self, path): ''' This interface is provided for removing a directory, however UKAI doesn't support hierarchical directory structure at this moment. ''' raise FuseOSError(errno.EPERM) def statfs(self, path): ''' Returns a stat information of a file system where the specified file belongs to. param path: the path name of a file ''' return self._rpc_client.call('statfs', path) def symlink(self, target, source): ''' This interface is provided for creating a symbolic link file, however UKAI doesn't support symbolic links. ''' raise FuseOSError(errno.EPERM) def truncate(self, path, length, fh=None): ''' Changes the size of a file. param path: the path name of a file param length: the new size of the file param fh: the file handle of the file ''' ret = self._rpc_client.call('truncate', path, str(length)) if ret != 0: raise FuseOSError(ret) return ret def unlink(self, path): ''' This interface is provided for removing a file, howerver UKAI doesn't support removing files using this interface. To remove a file (virtual disk image), use the ukai_admin command. ''' raise FuseOSError(errno.EPERM) def utimens(self, path, times=None): ''' This interface is provided for setting time stamp information of a file, howerver, UKAI doesn't have such metadata. ''' pass def write(self, path, data, offset, fh): ''' Writes data to a file. param path: the path name of a file param data: the data to be written param offset: the offset from the beginning of the file param fh: the file handle of the file ''' # The data passed to the UKAICore.write interface must be # encoded using a proper RPC encoding mechanism. encoded_data = self._rpc_trans.encode(data) ret, nwritten = self._rpc_client.call('write', path, encoded_data, str(offset)) if ret != 0: raise FuseOSError(ret) return nwritten
class UKAIData(object): ''' The UKAIData class provides manipulation functions to modify the disk image contents. ''' def __init__(self, metadata, node_error_state_set, config): ''' Initializes the instance with the specified metadata object created with the UKAIMetadata class. ''' self._metadata = metadata self._node_error_state_set = node_error_state_set self._config = config self._rpc_trans = UKAIXMLRPCTranslation() # Lock objects per block index. self._lock = [] for blk_idx in range(0, len(metadata.blocks)): self._lock.append(threading.Lock()) def _gather_pieces(self, offset, size): ''' Returns a list of tupples specifying which block and index in the blocks are related to the offset and size of the disk image. The tupple format is shown below. (block index, start position, length) offset: offset from the beginning of the disk image. size: the length of the data to be handled. ''' assert size > 0 assert offset >= 0 assert (size + offset) <= self._metadata.size # piece format: (block index, start position, length) start_block = offset / self._metadata.block_size end_block = (offset + size - 1) / self._metadata.block_size start_block_pos = offset - (start_block * self._metadata.block_size) end_block_pos = (offset + size) - (end_block * self._metadata.block_size) pieces = [] if start_block == end_block: pieces.append((start_block, start_block_pos, size)) else: for block in range(start_block, end_block + 1): if block == start_block: pieces.append((block, start_block_pos, self._metadata.block_size - start_block_pos)) elif block == end_block: pieces.append((block, 0, end_block_pos)) else: pieces.append((block, 0, self._metadata.block_size)) return (pieces) def read(self, size, offset): ''' Reads size bytes from the specified location in the disk image specified as the offset argument. The read data is returned as a return value. ''' assert size > 0 assert offset >= 0 if offset >= self._metadata.used_size: # end of the file. return ('') if offset + size > self._metadata.used_size: # shorten the size not to overread the end of the file. size = self._metadata.used_size - offset data = '' partial_data = '' metadata_flush_required = False pieces = self._gather_pieces(offset, size) # read operation statistics. UKAIStatistics[self._metadata.name].read_op(pieces) try: for piece in pieces: self._metadata._lock[piece[0]].acquire() # XXX self._lock[piece[0]].acquire() for piece in pieces: blk_idx = piece[0] off_in_blk = piece[1] size_in_blk = piece[2] block = self._metadata.blocks[blk_idx] data_read = False while not data_read: candidate = self._find_read_candidate(blk_idx) if candidate is None: print 'XXX fatal. should raise an exception.' try: partial_data = self._get_data(candidate, blk_idx, off_in_blk, size_in_blk) data_read = True break except (IOError, xmlrpclib.Error), e: print e.__class__ self._metadata.set_sync_status(blk_idx, candidate, UKAI_OUT_OF_SYNC) metadata_flush_required = True self._node_error_state_set.add(candidate, 0) # try to find another candidate node. continue if data_read is False: # no node is available to get the peice of data. print 'XXX fatal. should raise an exception.' data = data + partial_data finally: for piece in pieces: self._metadata._lock[piece[0]].release() # XXX self._lock[piece[0]].release() if metadata_flush_required is True: self._metadata.flush() return (data) def _find_read_candidate(self, blk_idx): candidate = None for node in self._metadata.blocks[blk_idx].keys(): if self._node_error_state_set.is_in_failure(node) is True: continue if self._metadata.get_sync_status(blk_idx, node) != UKAI_IN_SYNC: continue if UKAIIsLocalNode(node): candidate = node break candidate = node return (candidate) def _get_data(self, node, blk_idx, off_in_blk, size_in_blk): ''' Returns a data read from a local store or a remote store depending on the node location. node: the target node from which we read the data. num: the block index of the disk image. offset: the offset relative to the beginning of the specified block. size: the length of the data to be read. ''' assert size_in_blk > 0 assert off_in_blk >= 0 assert (off_in_blk + size_in_blk) <= self._metadata.block_size if UKAIIsLocalNode(node): return (self._get_data_local(node, blk_idx, off_in_blk, size_in_blk)) else: return (self._get_data_remote(node, blk_idx, off_in_blk, size_in_blk)) def _get_data_local(self, node, blk_idx, off_in_blk, size_in_blk): ''' Returns a data read from a local store. node: the target node from which we read the data. num: the block index of the disk image. offset: the offset relative to the beginning of the specified block. size: the length of the data to be read. ''' data = ukai_local_read(self._metadata.name, self._metadata.block_size, blk_idx, off_in_blk, size_in_blk, self._config) return (data) def _get_data_remote(self, node, blk_idx, off_in_blk, size_in_blk): ''' Returns a data read from a remote store. The remote read command is sent to a remote proxy program using the XML RPC mechanism. node: the target node from which we read the data. num: the block index of the disk image. offset: the offset relative to the beginning of the specified block. size: the length of the data to be read. ''' rpc_call = UKAIXMLRPCCall(node, self._config.get('core_port')) encoded_data = rpc_call.call('proxy_read', self._metadata.name, str(self._metadata.block_size), str(blk_idx), str(off_in_blk), str(size_in_blk)) return zlib.decompress(self._rpc_trans.decode(encoded_data)) def write(self, data, offset): ''' Writes the data from the specified location in the disk image specified as the offset argument. The method returns the number of written data. ''' assert data is not None assert offset >= 0 assert (offset + len(data)) <= self._metadata.size metadata_flush_required = False pieces = self._gather_pieces(offset, len(data)) # write operation statistics. UKAIStatistics[self._metadata.name].write_op(pieces) data_offset = 0 try: for piece in pieces: self._metadata._lock[piece[0]].acquire() # XXX self._lock[piece[0]].acquire() for piece in pieces: blk_idx = piece[0] off_in_blk = piece[1] size_in_blk = piece[2] block = self._metadata.blocks[blk_idx] for node in block.keys(): try: if (self._node_error_state_set.is_in_failure(node) is True): if (self._metadata.get_sync_status(blk_idx, node) == UKAI_IN_SYNC): self._metadata.set_sync_status(blk_idx, node, UKAI_OUT_OF_SYNC) metadata_flush_required = True continue if (self._metadata.get_sync_status(blk_idx, node) != UKAI_IN_SYNC): self._synchronize_block(blk_idx, node) metadata_flush_required = True self._put_data(node, blk_idx, off_in_blk, data[data_offset:data_offset + size_in_blk]) except (IOError, xmlrpclib.Error), e: print e.__class__ self._metadata.set_sync_status(blk_idx, node, UKAI_OUT_OF_SYNC) metadata_flush_required = True self._node_error_state_set.add(node, 0) data_offset = data_offset + size_in_blk finally: if offset + len(data) > self._metadata.used_size: self._metadata.used_size = offset + len(data) metadata_flush_required = True for piece in pieces: self._metadata._lock[piece[0]].release() # XXX self._lock[piece[0]].release() if metadata_flush_required is True: self._metadata.flush() return (len(data)) def _put_data(self, node, blk_idx, off_in_blk, data): ''' Writes the data to a local store or a remote store depending on the node location. node: the target node from which we read the data. num: the block index of the disk image. offset: the offset relative to the beginning of the specified block. data: the data to be written. ''' assert off_in_blk >= 0 assert (off_in_blk + len(data)) <= self._metadata.block_size if UKAIIsLocalNode(node): return (self._put_data_local(node, blk_idx, off_in_blk, data)) else: return (self._put_data_remote(node, blk_idx, off_in_blk, data)) def _put_data_local(self, node, blk_idx, off_in_blk, data): ''' Writes the data to a local store. node: the target node from which we read the data. num: the block index of the disk image. offset: the offset relative to the beginning of the specified block. data: the data to be written. ''' return ukai_local_write(self._metadata.name, self._metadata.block_size, blk_idx, off_in_blk, data, self._config) def _put_data_remote(self, node, blk_idx, off_in_blk, data): ''' Writes the data to a remote store. The remote write command is sent to a remote proxy program using the XML RPC mechanism. node: the target node from which we read the data. num: the block index of the disk image. offset: the offset relative to the beginning of the specified block. data: the data to be written. ''' rpc_call = UKAIXMLRPCCall(node, self._config.get('core_port')) return rpc_call.call('proxy_write', self._metadata.name, str(self._metadata.block_size), str(blk_idx), str(off_in_blk), self._rpc_trans.encode(zlib.compress(data))) def synchronize_block(self, blk_idx): ''' Synchronizes the specified block specified by the blk_idx argument. Return value: True if metadata is modified, otherwise False. This function is used only by a background synchronization process, and must not be called by any other processes. ''' metadata_flush_required = False try: self._metadata._lock[blk_idx].acquire() # XXX self._lock[blk_idx].acquire() for node in self._metadata.blocks[blk_idx].keys(): if (self._metadata.get_sync_status(blk_idx, node) == UKAI_IN_SYNC): continue self._synchronize_block(blk_idx, node) metadata_flush_required = True finally: self._metadata._lock[blk_idx].release() # XXX self._lock[blk_idx].release() return (metadata_flush_required) def _synchronize_block(self, blk_idx, node): ''' Synchronizes the specified block by the blk_idx argument. This function first search the already synchronized node block and copy the data to all the other not-synchronized nodes. ''' block = self._metadata.blocks[blk_idx] final_candidate = None for candidate in block.keys(): if (self._metadata.get_sync_status(blk_idx, candidate) != UKAI_IN_SYNC): continue if UKAIIsLocalNode(candidate): final_candidate = candidate break final_candidate = candidate if final_candidate == None: # XXX fatal # should raise an exception print 'Disk image of %s has unrecoverble error.' % self._metadata.name self._allocate_dataspace(node, blk_idx) data = self._get_data(final_candidate, blk_idx, 0, self._metadata.block_size) self._put_data(node, blk_idx, 0, data) self._metadata.set_sync_status(blk_idx, node, UKAI_IN_SYNC) def _allocate_dataspace(self, node, blk_idx): ''' Allocates an empty data block in a local store specified by the blk_idx argument. ''' if UKAIIsLocalNode(node): path = '%s/%s/' % (self._config.get('data_root'), self._metadata.name) if not os.path.exists(path): os.makedirs(path) path = path + self._config.get('blockname_format') % blk_idx fh = open(path, 'w') fh.seek(self._metadata.block_size - 1) fh.write('\0') fh.close() else: rpc_call = UKAIXMLRPCCall( node, self._config.get('core_port')) rpc_call.call('proxy_allocate_dataspace', self._metadata.name, self._metadata.block_size, blk_idx)
class UKAICore(object): ''' UKAI core processing. ''' def __init__(self, config): self._metadata_dict = {} self._data_dict = {} self._config = config self._node_error_state_set = UKAINodeErrorStateSet() self._open_image_set = set() self._rpc_trans = UKAIXMLRPCTranslation() @property def metadata_server(self): return self._config.get('metadata_server') @property def core_server(self): return self._config.get('core_server') @property def core_port(self): return self._config.get('core_port') ''' Filesystem I/O processing. ''' def getattr(self, path): ret = 0 st = None if path == '/': st = dict(st_mode=(stat.S_IFDIR | 0755), st_ctime=0, st_mtime=0, st_atime=0, st_nlink=2) else: image_name = path[1:] if self._exists(image_name): st = dict(st_mode=(stat.S_IFREG | 0644), st_ctime=0, st_mtime=0, st_atime=0, st_nlink=1, st_size=self._metadata_dict[image_name].size) else: ret = errno.ENOENT return ret, st def open(self, path, flags): ret = 0 image_name = path[1:] if not self._exists(image_name): return errno.ENOENT if image_name in self._open_image_set: return errno.EBUSY else: self._open_image_set.add(image_name) return 0 def release(self, path): image_name = path[1:] if image_name in self._open_image_set: self._open_image_set.remove(image_name) return 0 def read(self, path, size, offset): image_name = path[1:] if not self._exists(image_name): return errno.ENOENT, None image_data = self._data_dict[image_name] data = image_data.read(size, offset) return 0, self._rpc_trans.encode(data) def readdir(self, path): return ['.', '..'] + self._metadata_dict.keys() def statfs(self, path): ''' TODO: the values are fake right now. ''' return dict(f_bsize=512, f_blocks=4096, f_bavail=2048) def truncate(self, path, length): return errno.EPERM def unlink(self, path): return errno.EPERM def write(self, path, encoded_data, offset): image_name = path[1:] if not self._exists(image_name): return errno.ENOENT, None image_data = self._data_dict[image_name] return 0, image_data.write(self._rpc_trans.decode(encoded_data), offset) def _exists(self, image_name): if image_name not in self._metadata_dict: return False if image_name not in self._data_dict: return False return True ''' Proxy server processing. ''' def proxy_read(self, image_name, block_size, block_index, offset, size): data = ukai_local_read(image_name, block_size, block_index, offset, size, self._config) return self._rpc_trans.encode(zlib.compress(data)) def proxy_write(self, image_name, block_size, block_index, offset, encoded_data): data = zlib.decompress(self._rpc_trans.decode(encoded_data)) return ukai_local_write(image_name, block_size, block_index, offset, data, self._config) def proxy_allocate_dataspace(self, image_name, block_size, block_index): return ukai_local_allocate_dataspace(image_name, block_size, block_index, self._config) def proxy_update_metadata(self, image_name, encoded_metadata): metadata_raw = json.loads(zlib.decompress(self._rpc_trans.decode( encoded_metadata))) if image_name in self._metadata_dict: self._metadata_dict[image_name].metadata = metadata_raw else: metadata = UKAIMetadata(image_name, self._config, metadata_raw) self._metadata_dict[image_name] = metadata self._data_dict[image_name] = UKAIData(metadata, self._node_error_state_set, self._config) UKAIStatistics[image_name] = UKAIImageStatistics() return 0 ''' Controll processing. ''' def ctl_create_image(self, image_name, size, block_size=None, location=None, hypervisor=None): assert image_name is not None assert size > 0 if block_size is None: defaults = self._config.get('create_default') block_size = defaults['block_size'] assert block_size > 0 assert size > block_size assert size % block_size == 0 block_count = size / block_size if location is None: location = self._config.get('core_server') if hypervisor is None: hypervisor = self._config.get('core_server') ukai_metadata_create(image_name, size, block_size, location, hypervisor, self._config) def ctl_add_image(self, image_name): if image_name in self._metadata_dict: return errno.EEXIST metadata = UKAIMetadata(image_name, self._config) self._metadata_dict[image_name] = metadata data = UKAIData(metadata=metadata, node_error_state_set=self._node_error_state_set, config=self._config) self._data_dict[image_name] = data UKAIStatistics[image_name] = UKAIImageStatistics() return 0 def ctl_remove_image(self, image_name): if image_name not in self._metadata_dict: return errno.ENOENT del self._metadata_dict[image_name] del self._data_dict[image_name] del UKAIStatistics[image_name] return 0 def ctl_get_metadata(self, image_name): if image_name not in self._metadata_dict: return errno.ENOENT, None return 0, json.dumps(self._metadata_dict[image_name].metadata) def ctl_add_location(self, image_name, location, start_index=0, end_index=-1, sync_status=UKAI_OUT_OF_SYNC): if image_name not in self._metadata_dict: return errno.ENOENT metadata = self._metadata_dict[image_name] metadata.add_location(location, start_index, end_index, sync_status) return 0 def ctl_remove_location(self, image_name, location, start_index=0, end_index=-1): if image_name not in self._metadata_dict: return errno.ENOENT metadata = self._metadata_dict[image_name] metadata.remove_location(location, start_index, end_index) return 0 def ctl_add_hypervisor(self, image_name, hypervisor): if image_name not in self._metadata_dict: return errno.ENOENT metadata = self._metadata_dict[image_name] metadata.add_hypervisor(hypervisor) return 0 def ctl_remove_hypervisor(self, image_name, hypervisor): if image_name not in self._metadata_dict: return errno.ENOENT metadata = self._metadata_dict[image_name] metadata.remove_hypervisor(hypervisor) return 0 def ctl_synchronize(self, image_name, start_index=0, end_index=-1, verbose=False): if image_name not in self._metadata_dict: return errno.ENOENT metadata = self._metadata_dict[image_name] data = self._data_dict[image_name] if end_index == -1: end_index = (metadata.size / metadata.block_size) - 1 for block_index in range(start_index, end_index + 1): if verbose is True: print 'Syncing block %d (from %d to %d)' % (block_index, start_index, end_index) if data.synchronize_block(block_index) is True: metadata.flush() return 0 def ctl_get_node_error_state_set(self): return self._node_error_state_set.get_list()
class UKAICore(object): ''' The UKAICore class implements core processing of the UKAI filesystem. ''' def __init__(self, config): self._metadata_dict = {} self._data_dict = {} self._config = config self._node_error_state_set = UKAINodeErrorStateSet() self._rpc_trans = UKAIXMLRPCTranslation() self._writers = UKAIWriters() self._open_count = UKAIOpenImageCount() self._fh = 0 ukai_db_client.connect(self._config) ''' Filesystem I/O processing. ''' def getattr(self, path): ret = 0 st = None if path == '/': st = dict(st_mode=(stat.S_IFDIR | 0755), st_ctime=0, st_mtime=0, st_atime=0, st_nlink=2) else: image_name = path[1:] metadata = self._get_metadata(image_name) if metadata is not None: st = dict(st_mode=(stat.S_IFREG | 0644), st_ctime=0, st_mtime=0, st_atime=0, st_nlink=1, st_size=metadata['used_size']) else: ret = errno.ENOENT return ret, json.dumps(st) def open(self, path, flags): try: lock.acquire() ret = 0 image_name = path[1:] metadata = self._get_metadata(image_name) if metadata is None: return errno.ENOENT, None self._fh += 1 if (flags & 3) != os.O_RDONLY: if self._writers.add_writer(image_name, self._fh) == errno.EBUSY: return errno.EBUSY, None if self._open_count.increment(image_name) == 1: self._add_image(image_name) return 0, self._fh finally: lock.release() def release(self, path, fh): try: lock.acquire() image_name = path[1:] self._writers.remove_writer(image_name, fh) if self._open_count.decrement(image_name) == 0: self._remove_image(image_name) return 0 finally: lock.release() def read(self, path, str_size, str_offset): image_name = path[1:] size = int(str_size) offset = int(str_offset) if not self._exists(image_name): return errno.ENOENT, None image_data = self._data_dict[image_name] data = image_data.read(size, offset) return 0, self._rpc_trans.encode(data) def readdir(self, path): return ['.', '..'] + self._metadata_dict.keys() def statfs(self, path): ''' TODO: the values are fake right now. ''' return dict(f_bsize=512, f_blocks=4096, f_bavail=2048) def truncate(self, path, str_length): image_name = path[1:] length = int(str_length) if not self._exists(image_name): return errno.ENOENT image_metadata = self._metadata_dict[image_name] if image_metadata.size < length: return errno.EINVAL image_metadata.used_size = length image_metadata.flush() return 0 def unlink(self, path): return errno.EPERM def write(self, path, encoded_data, str_offset): image_name = path[1:] offset = int(str_offset) if not self._exists(image_name): return errno.ENOENT, None image_data = self._data_dict[image_name] return 0, image_data.write(self._rpc_trans.decode(encoded_data), offset) def _get_metadata(self, image_name): return ukai_db_client.get_metadata(image_name) def _add_image(self, image_name): assert image_name not in self._metadata_dict metadata = UKAIMetadata(image_name, self._config) ukai_db_client.join_reader(image_name, self._config.get('id')) self._metadata_dict[image_name] = metadata data = UKAIData(metadata=metadata, node_error_state_set=self._node_error_state_set, config=self._config) self._data_dict[image_name] = data UKAIStatistics[image_name] = UKAIImageStatistics() def _remove_image(self, image_name): assert image_name in self._metadata_dict ukai_db_client.leave_reader(image_name, self._config.get('id')) del self._metadata_dict[image_name] del self._data_dict[image_name] del UKAIStatistics[image_name] def _exists(self, image_name): if image_name not in self._metadata_dict: return False if image_name not in self._data_dict: return False return True ''' Proxy server processing. ''' def proxy_read(self, image_name, str_block_size, str_block_index, str_offset, str_size): block_size = int(str_block_size) block_index = int(str_block_index) offset = int(str_offset) size = int(str_size) data = ukai_local_read(image_name, block_size, block_index, offset, size, self._config) return self._rpc_trans.encode(zlib.compress(data)) def proxy_write(self, image_name, str_block_size, str_block_index, str_offset, encoded_data): block_size = int(str_block_size) block_index = int(str_block_index) offset = int(str_offset) data = zlib.decompress(self._rpc_trans.decode(encoded_data)) return ukai_local_write(image_name, block_size, block_index, offset, data, self._config) def proxy_allocate_dataspace(self, image_name, block_size, block_index): return ukai_local_allocate_dataspace(image_name, block_size, block_index, self._config) def proxy_deallocate_dataspace(self, image_name, block_index): return ukai_local_deallocate_dataspace(image_name, block_index, self._config) def proxy_update_metadata(self, image_name, encoded_metadata): metadata_raw = json.loads(zlib.decompress(self._rpc_trans.decode( encoded_metadata))) if image_name in self._metadata_dict: self._metadata_dict[image_name].metadata = metadata_raw else: metadata = UKAIMetadata(image_name, self._config, metadata_raw) self._metadata_dict[image_name] = metadata self._data_dict[image_name] = UKAIData(metadata, self._node_error_state_set, self._config) UKAIStatistics[image_name] = UKAIImageStatistics() return 0 def proxy_destroy_image(self, image_name): return ukai_local_destroy_image(image_name, self._config) ''' Controll processing. ''' def ctl_create_image(self, image_name, str_size, block_size=None, location=None): assert image_name is not None size = int(str_size) assert size > 0 if block_size is None: defaults = self._config.get('create_default') block_size = defaults['block_size'] assert block_size > 0 assert size > block_size assert size % block_size == 0 block_count = size / block_size if location is None: location = self._config.get('core_server') ukai_metadata_create(image_name, size, block_size, location, self._config) def ctl_destroy_image(self, image_name): assert image_name is not None ukai_data_destroy(image_name, self._config) ukai_metadata_destroy(image_name, self._config) def ctl_get_metadata(self, image_name): metadata = self._get_metadata(image_name) if metadata is None: return errno.ENOENT, None return 0, json.dumps(metadata) def ctl_add_location(self, image_name, location, start_index=0, end_index=-1, sync_status=UKAI_OUT_OF_SYNC): metadata = None if image_name in self._metadata_dict: # the image is in use on this node. metadata = self._metadata_dict[image_name] else: # XXX need to check if no one is using this image. metadata_raw = self._get_metadata(image_name) if metadata_raw is None: return errno.ENOENT metadata = UKAIMetadata(image_name, self._config, metadata_raw) metadata.add_location(location, start_index, end_index, sync_status) return 0 def ctl_remove_location(self, image_name, location, start_index=0, end_index=-1): metadata = None if image_name in self._metadata_dict: # the image is in use on this node. metadata = self._metadata_dict[image_name] else: # XXX need to check if no one is using this image. metadata_raw = self._get_metadata(image_name) if metadata_raw is None: return errno.ENOENT metadata = UKAIMetadata(image_name, self._config, metadata_raw) metadata.remove_location(location, start_index, end_index) ukai_data_location_destroy(image_name, location, self._config) return 0 def ctl_add_hypervisor(self, image_name, hypervisor): metadata = None if image_name in self._metadata_dict: # the image is in use on this node. metadata = self._metadata_dict[image_name] else: # XXX need to check if no one is using this image. metadata_raw = self._get_metadata(image_name) if metadata_raw is None: return errno.ENOENT metadata = UKAIMetadata(image_name, self._config, metadata_raw) metadata.add_hypervisor(hypervisor) return 0 def ctl_remove_hypervisor(self, image_name, hypervisor): metadata = None if image_name in self._metadata_dict: # the image is in use on this node. metadata = self._metadata_dict[image_name] else: # XXX need to check if no one is using this image. metadata_raw = self._get_metadata(image_name) if metadata_raw is None: return errno.ENOENT metadata = UKAIMetadata(image_name, self._config, metadata_raw) metadata.remove_hypervisor(hypervisor) return 0 def ctl_synchronize(self, image_name, start_index=0, end_index=-1, verbose=False): metadata = None if image_name in self._metadata_dict: # the image is in use on this node. metadata = self._metadata_dict[image_name] data = self._data_dict[image_name] else: # XXX need to check if no one is using this image. metadata_raw = self._get_metadata(image_name) if metadata_raw is None: return errno.ENOENT metadata = UKAIMetadata(image_name, self._config, metadata_raw) data = UKAIData(metadata, self._node_error_state_set, self._config) if end_index == -1: end_index = (metadata.size / metadata.block_size) - 1 for block_index in range(start_index, end_index + 1): if verbose is True: print 'Syncing block %d (from %d to %d)' % (block_index, start_index, end_index) if data.synchronize_block(block_index) is True: metadata.flush() return 0 def ctl_get_node_error_state_set(self): return self._node_error_state_set.get_list() def ctl_get_image_names(self): return ukai_db_client.get_image_names() def ctl_diag(self): print self._open_count._images print self._writers._images print self._metadata_dict return 0