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
Exemple #3
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)
Exemple #4
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()
Exemple #5
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