Beispiel #1
0
 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()
Beispiel #2
0
    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()
Beispiel #4
0
 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)
Beispiel #5
0
 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
Beispiel #9
0
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()
Beispiel #10
0
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)
Beispiel #11
0
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
Beispiel #12
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()