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()
def __init__(self, image_name, config, metadata_raw=None): ''' Initializes the class with the specified file contents. The metadata_file is a JSON format file, which is generated by the UKAIMetadataCreate() function or the flush() method of this class. image_name: The name of a virtual disk image stored in a object storage. metadata_raw: a raw metadata which is specified when a new UKAIMetadata object is inserted. Return values: This function does not return any values. ''' self._config = config self._rpc_trans = UKAIXMLRPCTranslation() if (metadata_raw != None): self._metadata = metadata_raw else: self._metadata = None self._metadata = ukai_db_client.get_metadata(image_name) self._lock = [] for idx in range(0, len(self.blocks)): self._lock.append(threading.Lock())
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, 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)
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 __init__(self, config): self._fd = 0 self._config = config self._rpc_client = UKAIXMLRPCClient(self._config) self._rpc_trans = UKAIXMLRPCTranslation()
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 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 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): ''' 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
class UKAIMetadata(object): ''' The UKAIMetadata class contains metadata information of a disk image of the UKAI system. ''' def __init__(self, image_name, config, metadata_raw=None): ''' Initializes the class with the specified file contents. The metadata_file is a JSON format file, which is generated by the UKAIMetadataCreate() function or the flush() method of this class. image_name: The name of a virtual disk image stored in a object storage. metadata_raw: a raw metadata which is specified when a new UKAIMetadata object is inserted. Return values: This function does not return any values. ''' self._config = config self._rpc_trans = UKAIXMLRPCTranslation() if (metadata_raw != None): self._metadata = metadata_raw else: self._metadata = None self._metadata = ukai_db_client.get_metadata(image_name) self._lock = [] for idx in range(0, len(self.blocks)): self._lock.append(threading.Lock()) def flush(self): ''' Writes out the latest metadata information stored in memory to the metadata file. ''' try: self.acquire_lock() # Write out to the metadata storage. ukai_db_client.put_metadata(self.name, self._metadata) # Send the latest metadata information to all the hypervisors # using this virtual disk. for hv in ukai_db_client.get_readers(self.name): if UKAIIsLocalNode(hv): continue try: rpc_call = UKAIXMLRPCCall( hv, self._config.get('core_port')) rpc_call.call('proxy_update_metadata', self.name, self._rpc_trans.encode(zlib.compress(json.dumps(self._metadata)))) except (IOError, xmlrpclib.Error), e: print e.__class__ print 'Failed to update metadata at %s. You cannot migrate a virtual machine to %s' % (hv, hv) finally: self.release_lock() @property def metadata(self): ''' The metadata dictionary object of this instance. ''' return(self._metadata) @metadata.setter def metadata(self, metadata_raw): if self._metadata is None: # If this is the first time to load the metadata, just # load it. self._metadata = metadata_raw else: # If the instance has metadata already, need to lock # the object to avoid thread confliction. try: self.acquire_lock() self._metadata = metadata_raw finally: self.release_lock() @property def name(self): ''' The name of the disk image. ''' return (self._metadata['name']) @property def size(self): ''' The total size of the disk image. ''' return (int(self._metadata['size'])) @property def used_size(self): ''' The used size of the disk image. ''' return (int(self._metadata['used_size'])) @used_size.setter def used_size(self, used_size): self._metadata['used_size'] = used_size @property def block_size(self): ''' The block size of the disk image. ''' return (int(self._metadata['block_size'])) @property def blocks(self): ''' An array of all blocks. Need to acquire lock when modifying the contents. ''' return(self._metadata['blocks']) def acquire_lock(self, start_idx=0, end_idx=-1): ''' Acquires lock objects of the specified range of metadata blocks of the virtual disk. If you don't specify any index values, the entire metadata blocks are locked. start_idx: The first block index of metadata blocks at which the lock object is aquired. end_idx: The last block index of metadata blocks at which the lock object is aquired. Return values: This function does not return any values. ''' if end_idx == -1: end_idx = (self.size / self.block_size) - 1 assert start_idx >= 0 assert end_idx >= start_idx assert end_idx < (self.size / self.block_size) for blk_idx in range(0, end_idx + 1): self._lock[blk_idx].acquire() def release_lock(self, start_idx=0, end_idx=-1): ''' Releases lock objects acquired by the acquire_lock method. If you don't specify any index values, the entire metadata blocks are released, however if you try to release unlocked block, you will receive an assertion. start_idx: The first block index of metadata blocks at which the lock object is aquired. end_idx: The last block index of metadata blocks at which the lock object is aquired. Return values: This function does not return any values. ''' if end_idx == -1: end_idx = (self.size / self.block_size) - 1 assert start_idx >= 0 assert end_idx >= start_idx assert end_idx < (self.size / self.block_size) for blk_idx in range(0, end_idx + 1): self._lock[blk_idx].release() def set_sync_status(self, blk_idx, node, sync_status): ''' Sets the sync_status property of the specidied location of the specified block index. blk_idx: The index of a block. node: The location information specified by the IP address of a storage node. sync_status: A new synchronization status UKAI_IN_SYNC: The block is synchronized. UKAI_SYNCING: The block is being synchronized (NOT USED). UKAI_OUT_OF_SYNC: The block is not synchronized. Return values: This function does not return any values. ''' assert (sync_status == UKAI_IN_SYNC or sync_status == UKAI_SYNCING or sync_status == UKAI_OUT_OF_SYNC) self.blocks[blk_idx][node]['sync_status'] = sync_status def get_sync_status(self, blk_idx, node): ''' Returns the sync_status property of the specified location of the specified block index. blk_idx: The index of a block. node: The location information specified by the IP address of a storage node. Return values: The following values is returned. UKAI_IN_SYNC: The block is synchronized. UKAI_SYNCING: The block is being synchronized (NOT USED). UKAI_OUT_OF_SYNC: The block is not synchronized. ''' return (self.blocks[blk_idx][node]['sync_status']) def add_location(self, node, start_idx=0, end_idx=-1, sync_status=UKAI_OUT_OF_SYNC): ''' Adds location information (a node address) to specified range of blocks. node: the node (currently IPv4 numeric only) to be added. start_idx: the first index of the blocks array to add the node. end_idx: the end index of the blocks array to add the node. When specified -1, the end_block is replaced to the final index of the block array. sync_status: the initial synchronized status. Return values: This function does not return any values. ''' if end_idx == -1: end_idx = (self.size / self.block_size) - 1 assert start_idx >= 0 assert end_idx >= start_idx assert end_idx < (self.size / self.block_size) try: self.acquire_lock(start_idx, end_idx) for blk_idx in range(start_idx, end_idx + 1): if node not in self.blocks[blk_idx]: # if there is no node entry, create it. self.blocks[blk_idx][node] = {} self.set_sync_status(blk_idx, node, sync_status) finally: self.release_lock(start_idx, end_idx) self.flush() def remove_location(self, node, start_idx=0, end_idx=-1): ''' Removes location information (a node address) from specified range of blocks. node: the node (currently IPv4 numeric only) to be removed. start_idx: the first index of the blocks array to add the node. end_idx: the end index of the blocks array to add the node. When specified -1, the end_block is replaced to the final index of the block array. Return values: This function does not return any values. ''' if end_idx == -1: end_idx = (self.size / self.block_size) - 1 assert start_idx >= 0 assert end_idx >= start_idx assert end_idx < (self.size / self.block_size) try: self.acquire_lock(start_idx, end_idx) for blk_idx in range(start_idx, end_idx + 1): block = self.blocks[blk_idx] has_synced_node = False for member_node in block.keys(): if member_node == node: continue if (self.get_sync_status(blk_idx, member_node) == UKAI_IN_SYNC): has_synced_node = True break if has_synced_node is False: print 'block %d does not have synced block' % blk_idx continue if node in block.keys(): del block[node] finally: self.release_lock(start_idx, end_idx) self.flush()