def do_setup(self, context): """Any initialization the volume driver does while starting.""" super(CohoDriver, self).do_setup(context) self._context = context config = self.configuration.coho_rpc_port if not config: msg = _("Coho rpc port is not configured") LOG.warning(msg) raise exception.CohoException(msg) if config < 1 or config > 65535: msg = (_("Invalid port number %(config)s for Coho rpc port") % {'config': config}) LOG.warning(msg) raise exception.CohoException(msg)
def init_socket(self): try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.address, self.port)) except socket.error: msg = _('Failed to establish connection with Coho cluster') raise exception.CohoException(msg)
def _recvfrag(self): header = self.sock.recv(4) if len(header) < 4: raise exception.CohoException( _('Invalid response header from RPC server')) x = (six.indexbytes(header, 0) << 24 | six.indexbytes(header, 1) << 16 | six.indexbytes(header, 2) << 8 | six.indexbytes(header, 3)) last = ((x & 0x80000000) != 0) n = int(x & 0x7fffffff) frag = six.b('') while n > 0: buf = self.sock.recv(n) if not buf: raise exception.CohoException( _('RPC server response is incomplete')) n = n - len(buf) frag = frag + buf return last, frag
def _call(self, proc, args): for retry in range(COHO_MAX_RETRIES): try: self._make_call(proc, args) break except socket.error as e: if e.errno == errno.EPIPE: # Reopen connection to cluster and retry self.init_socket() else: msg = (_('Unable to send requests: %s') % six.text_type(e)) raise exception.CohoException(msg) else: msg = _('Failed to establish a stable connection') raise exception.CohoException(msg) res = self.unpacker.unpack_uint() if res != SUCCESS: raise exception.CohoException(os.strerror(res))
def unpack_replyheader(self): xid = self.unpacker.unpack_uint() mtype = self.unpacker.unpack_enum() if mtype != REPLY: raise exception.CohoException( _('no REPLY but %r') % (mtype,)) stat = self.unpacker.unpack_enum() if stat == MSG_DENIED: stat = self.unpacker.unpack_enum() if stat == RPC_MISMATCH: low = self.unpacker.unpack_uint() high = self.unpacker.unpack_uint() raise exception.CohoException( _('MSG_DENIED: RPC_MISMATCH: %r') % ((low, high),)) if stat == AUTH_ERROR: stat = self.unpacker.unpack_uint() raise exception.CohoException( _('MSG_DENIED: AUTH_ERROR: %r') % (stat,)) raise exception.CohoException(_('MSG_DENIED: %r') % (stat,)) if stat != MSG_ACCEPTED: raise exception.CohoException( _('Neither MSG_DENIED nor MSG_ACCEPTED: %r') % (stat,)) verf = self.unpack_auth() stat = self.unpacker.unpack_enum() if stat == PROG_UNAVAIL: raise exception.CohoException(_('call failed: PROG_UNAVAIL')) if stat == PROG_MISMATCH: low = self.unpacker.unpack_uint() high = self.unpacker.unpack_uint() raise exception.CohoException( _('call failed: PROG_MISMATCH: %r') % ((low, high),)) if stat == PROC_UNAVAIL: raise exception.CohoException(_('call failed: PROC_UNAVAIL')) if stat == GARBAGE_ARGS: raise exception.CohoException(_('call failed: GARBAGE_ARGS')) if stat != SUCCESS: raise exception.CohoException(_('call failed: %r') % (stat,)) return xid, verf
class CohoDriverTest(test.TestCase): """Test Coho Data's NFS volume driver.""" def __init__(self, *args, **kwargs): super(CohoDriverTest, self).__init__(*args, **kwargs) def setUp(self): super(CohoDriverTest, self).setUp() self.context = mock.Mock() self.configuration = mock.Mock(spec=conf.Configuration) self.configuration.max_over_subscription_ratio = 20.0 self.configuration.reserved_percentage = 0 self.configuration.volume_backend_name = 'coho-1' self.configuration.coho_rpc_port = 2049 self.configuration.nfs_shares_config = '/etc/cinder/coho_shares' self.configuration.nfs_sparsed_volumes = True self.configuration.nfs_mount_point_base = '/opt/stack/cinder/mnt' self.configuration.nfs_mount_options = None self.configuration.nas_host = None self.configuration.nas_share_path = None self.configuration.nas_mount_options = None def test_setup_failure_when_rpc_port_unconfigured(self): self.configuration.coho_rpc_port = None drv = coho.CohoDriver(configuration=self.configuration) self.mock_object(coho, 'LOG') self.mock_object(nfs.NfsDriver, 'do_setup') with self.assertRaisesRegex(exception.CohoException, ".*Coho rpc port is not configured.*"): drv.do_setup(self.context) self.assertTrue(coho.LOG.warning.called) self.assertTrue(nfs.NfsDriver.do_setup.called) def test_setup_failure_when_coho_rpc_port_is_invalid(self): self.configuration.coho_rpc_port = 99999 drv = coho.CohoDriver(configuration=self.configuration) self.mock_object(coho, 'LOG') self.mock_object(nfs.NfsDriver, 'do_setup') with self.assertRaisesRegex(exception.CohoException, "Invalid port number.*"): drv.do_setup(self.context) self.assertTrue(coho.LOG.warning.called) self.assertTrue(nfs.NfsDriver.do_setup.called) def test_create_snapshot(self): drv = coho.CohoDriver(configuration=self.configuration) mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') mock_get_volume_location = self.mock_object(coho.CohoDriver, '_get_volume_location') mock_get_volume_location.return_value = ADDR, PATH drv.create_snapshot(SNAPSHOT) mock_get_volume_location.assert_has_calls( [mock.call(SNAPSHOT['volume_id'])]) mock_rpc_client.assert_has_calls( [mock.call(ADDR, self.configuration.coho_rpc_port), mock.call().create_snapshot( os.path.join(PATH, SNAPSHOT['volume_name']), SNAPSHOT['name'], 0)]) def test_delete_snapshot(self): drv = coho.CohoDriver(configuration=self.configuration) mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') mock_get_volume_location = self.mock_object(coho.CohoDriver, '_get_volume_location') mock_get_volume_location.return_value = ADDR, PATH drv.delete_snapshot(SNAPSHOT) mock_get_volume_location.assert_has_calls( [mock.call(SNAPSHOT['volume_id'])]) mock_rpc_client.assert_has_calls( [mock.call(ADDR, self.configuration.coho_rpc_port), mock.call().delete_snapshot(SNAPSHOT['name'])]) def test_create_volume_from_snapshot(self): drv = coho.CohoDriver(configuration=self.configuration) mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') mock_find_share = self.mock_object(drv, '_find_share') mock_find_share.return_value = ADDR + ':' + PATH drv.create_volume_from_snapshot(VOLUME, SNAPSHOT) mock_find_share.assert_has_calls( [mock.call(VOLUME['size'])]) mock_rpc_client.assert_has_calls( [mock.call(ADDR, self.configuration.coho_rpc_port), mock.call().create_volume_from_snapshot( SNAPSHOT['name'], os.path.join(PATH, VOLUME['name']))]) def test_create_cloned_volume(self): drv = coho.CohoDriver(configuration=self.configuration) mock_find_share = self.mock_object(drv, '_find_share') mock_find_share.return_value = ADDR + ':' + PATH mock_execute = self.mock_object(drv, '_execute') mock_local_path = self.mock_object(drv, 'local_path') mock_local_path.return_value = LOCAL_PATH drv.create_cloned_volume(VOLUME, CLONE_VOL) mock_find_share.assert_has_calls( [mock.call(VOLUME['size'])]) mock_local_path.assert_has_calls( [mock.call(VOLUME), mock.call(CLONE_VOL)]) mock_execute.assert_has_calls( [mock.call('cp', LOCAL_PATH, LOCAL_PATH, run_as_root=True)]) def test_extend_volume(self): drv = coho.CohoDriver(configuration=self.configuration) mock_execute = self.mock_object(drv, '_execute') mock_local_path = self.mock_object(drv, 'local_path') mock_local_path.return_value = LOCAL_PATH drv.extend_volume(VOLUME, 512) mock_local_path.assert_has_calls( [mock.call(VOLUME)]) mock_execute.assert_has_calls( [mock.call('truncate', '-s', '512G', LOCAL_PATH, run_as_root=True)]) def test_snapshot_failure_when_source_does_not_exist(self): drv = coho.CohoDriver(configuration=self.configuration) self.mock_object(coho.Client, '_make_call') mock_init_socket = self.mock_object(coho.Client, 'init_socket') mock_unpack_uint = self.mock_object(xdrlib.Unpacker, 'unpack_uint') mock_unpack_uint.return_value = errno.ENOENT mock_get_volume_location = self.mock_object(coho.CohoDriver, '_get_volume_location') mock_get_volume_location.return_value = ADDR, PATH with self.assertRaisesRegex(exception.CohoException, "No such file or directory.*"): drv.create_snapshot(SNAPSHOT) self.assertTrue(mock_init_socket.called) self.assertTrue(mock_unpack_uint.called) mock_get_volume_location.assert_has_calls( [mock.call(SNAPSHOT['volume_id'])]) def test_snapshot_failure_with_invalid_input(self): drv = coho.CohoDriver(configuration=self.configuration) self.mock_object(coho.Client, '_make_call') mock_init_socket = self.mock_object(coho.Client, 'init_socket') mock_unpack_uint = self.mock_object(xdrlib.Unpacker, 'unpack_uint') mock_unpack_uint.return_value = errno.EINVAL mock_get_volume_location = self.mock_object(coho.CohoDriver, '_get_volume_location') mock_get_volume_location.return_value = ADDR, PATH with self.assertRaisesRegex(exception.CohoException, "Invalid argument"): drv.delete_snapshot(INVALID_SNAPSHOT) self.assertTrue(mock_init_socket.called) self.assertTrue(mock_unpack_uint.called) mock_get_volume_location.assert_has_calls( [mock.call(INVALID_SNAPSHOT['volume_id'])]) @mock.patch('cinder.volume.drivers.coho.Client.init_socket', side_effect=exception.CohoException( "Failed to establish connection.")) def test_snapshot_failure_when_remote_is_unreachable(self, mock_init_socket): drv = coho.CohoDriver(configuration=self.configuration) mock_get_volume_location = self.mock_object(coho.CohoDriver, '_get_volume_location') mock_get_volume_location.return_value = 'uknown-address', PATH with self.assertRaisesRegex(exception.CohoException, "Failed to establish connection.*"): drv.create_snapshot(SNAPSHOT) mock_get_volume_location.assert_has_calls( [mock.call(INVALID_SNAPSHOT['volume_id'])]) def test_rpc_client_make_call_proper_order(self): """This test ensures that the RPC client logic is correct. When the RPC client's make_call function is called it creates a packet and sends it to the Coho cluster RPC server. This test ensures that the functions needed to complete the process are called in the proper order with valid arguments. """ mock_packer = self.mock_object(xdrlib, 'Packer') mock_unpacker = self.mock_object(xdrlib, 'Unpacker') mock_unpacker.return_value.unpack_uint.return_value = 0 mock_socket = self.mock_object(socket, 'socket') mock_init_call = self.mock_object(coho.Client, 'init_call') mock_init_call.return_value = (1, 2) mock_sendrecord = self.mock_object(coho.Client, '_sendrecord') mock_recvrecord = self.mock_object(coho.Client, '_recvrecord') mock_recvrecord.return_value = 'test_reply' mock_unpack_replyheader = self.mock_object(coho.Client, 'unpack_replyheader') mock_unpack_replyheader.return_value = (123, 1) rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) rpc_client.create_volume_from_snapshot('src', 'dest') self.assertTrue(mock_sendrecord.called) self.assertTrue(mock_unpack_replyheader.called) mock_packer.assert_has_calls([mock.call().reset()]) mock_unpacker.assert_has_calls( [mock.call().reset('test_reply'), mock.call().unpack_uint()]) mock_socket.assert_has_calls( [mock.call(socket.AF_INET, socket.SOCK_STREAM), mock.call().bind(('', 0)), mock.call().connect((ADDR, RPC_PORT))]) mock_init_call.assert_has_calls( [mock.call(coho.COHO1_CREATE_VOLUME_FROM_SNAPSHOT, [(six.b('src'), mock_packer().pack_string), (six.b('dest'), mock_packer().pack_string)])]) def test_rpc_client_error_in_reply_header(self): """Ensure excpetions in reply header are raised by the RPC client. Coho cluster's RPC server packs errors into the reply header. This test ensures that the RPC client parses the reply header correctly and raises exceptions on various errors that can be included in the reply header. """ mock_socket = self.mock_object(socket, 'socket') mock_recvrecord = self.mock_object(coho.Client, '_recvrecord') rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) mock_recvrecord.return_value = NO_REPLY_BIN with self.assertRaisesRegex(exception.CohoException, "no REPLY.*"): rpc_client.create_snapshot('src', 'dest', 0) mock_recvrecord.return_value = MSG_DENIED_BIN with self.assertRaisesRegex(exception.CohoException, ".*MSG_DENIED.*"): rpc_client.delete_snapshot('snapshot') mock_recvrecord.return_value = PROG_UNAVAIL_BIN with self.assertRaisesRegex(exception.CohoException, ".*PROG_UNAVAIL"): rpc_client.delete_snapshot('snapshot') mock_recvrecord.return_value = PROG_MISMATCH_BIN with self.assertRaisesRegex(exception.CohoException, ".*PROG_MISMATCH.*"): rpc_client.delete_snapshot('snapshot') mock_recvrecord.return_value = GARBAGE_ARGS_BIN with self.assertRaisesRegex(exception.CohoException, ".*GARBAGE_ARGS"): rpc_client.delete_snapshot('snapshot') mock_recvrecord.return_value = PROC_UNAVAIL_BIN with self.assertRaisesRegex(exception.CohoException, ".*PROC_UNAVAIL"): rpc_client.delete_snapshot('snapshot') self.assertTrue(mock_recvrecord.called) mock_socket.assert_has_calls( [mock.call(socket.AF_INET, socket.SOCK_STREAM), mock.call().bind(('', 0)), mock.call().connect((ADDR, RPC_PORT))]) def test_rpc_client_error_in_receive_fragment(self): """Ensure exception is raised when malformed packet is recieved.""" mock_sendrcd = self.mock_object(coho.Client, '_sendrecord') mock_socket = self.mock_object(socket, 'socket') mock_socket.return_value.recv.return_value = INVALID_HEADER_BIN rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) with self.assertRaisesRegex(exception.CohoException, "Invalid response header.*"): rpc_client.create_snapshot('src', 'dest', 0) self.assertTrue(mock_sendrcd.called) mock_socket.assert_has_calls( [mock.call(socket.AF_INET, socket.SOCK_STREAM), mock.call().bind(('', 0)), mock.call().connect((ADDR, RPC_PORT)), mock.call().recv(4)])
def _call(self, proc, args): self._make_call(proc, args) res = self.unpacker.unpack_uint() if res != SUCCESS: raise exception.CohoException(os.strerror(res))