diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index b4be6e35a0c7..c0eed149ec84 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -229,6 +229,9 @@ typedef enum { ZPOOL_PROP_TNAME, ZPOOL_PROP_MAXDNODESIZE, ZPOOL_PROP_MULTIHOST, +#ifdef _UZFS + ZPOOL_PROP_UZFS_READONLY, +#endif ZPOOL_NUM_PROPS } zpool_prop_t; diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h index 9cfd23cc27cf..2d495479e520 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -297,6 +297,9 @@ struct spa { taskq_t *spa_prefetch_taskq; /* Taskq for prefetch threads */ uint64_t spa_multihost; /* multihost aware (mmp) */ mmp_thread_t spa_mmp; /* multihost mmp thread */ +#ifdef _UZFS + boolean_t readonly; /* pool is readonly or not */ +#endif /* * spa_refcount & spa_config_lock must be the last elements diff --git a/module/zcommon/zpool_prop.c b/module/zcommon/zpool_prop.c index fd21f31176a5..b15ff0b4a5fa 100644 --- a/module/zcommon/zpool_prop.c +++ b/module/zcommon/zpool_prop.c @@ -73,6 +73,11 @@ zpool_prop_init(void) PROP_DEFAULT, ZFS_TYPE_POOL, " | none", "CACHEFILE"); zprop_register_string(ZPOOL_PROP_COMMENT, "comment", NULL, PROP_DEFAULT, ZFS_TYPE_POOL, "", "COMMENT"); +#ifdef _UZFS + zprop_register_string(ZPOOL_PROP_UZFS_READONLY, "io.openebs:readonly", + "", PROP_DEFAULT, ZFS_TYPE_POOL, "on | off", "ZPOOL_READONLY"); +#endif + /* readonly number properties */ zprop_register_number(ZPOOL_PROP_SIZE, "size", 0, PROP_READONLY, diff --git a/module/zfs/spa.c b/module/zfs/spa.c index 6eb2e6fd4c31..0e72a4e422e6 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -79,6 +79,7 @@ #include #ifdef _UZFS #include +#include #endif #ifdef _KERNEL #include @@ -635,7 +636,17 @@ spa_prop_validate(spa_t *spa, nvlist_t *props) intval != 0 && intval < ZIO_DEDUPDITTO_MIN) error = SET_ERROR(EINVAL); break; +#ifdef _UZFS + case ZPOOL_PROP_UZFS_READONLY: + if ((error = nvpair_value_string(elem, &strval)) != 0) + break; + if (strcmp(strval, "on") != 0 && + strcmp(strval, "off") != 0) { + error = SET_ERROR(EINVAL); + } + break; +#endif default: break; } @@ -688,6 +699,10 @@ spa_prop_set(spa_t *spa, nvlist_t *nvp) int error; nvpair_t *elem = NULL; boolean_t need_sync = B_FALSE; +#ifdef _UZFS + char *val; + boolean_t update_rdonly = B_FALSE; +#endif if ((error = spa_prop_validate(spa, nvp)) != 0) return (error); @@ -729,6 +744,34 @@ spa_prop_set(spa_t *spa, nvlist_t *nvp) continue; } +#ifdef _UZFS + if (prop == ZPOOL_PROP_UZFS_READONLY) { + VERIFY(nvpair_value_string(elem, &val) == 0); + if (strcmp(val, "on") == 0) { + if (!spa->readonly) { + update_rdonly = B_TRUE; + spa->readonly = B_TRUE; + } + } else if (strcmp(val, "off") == 0) { + if (spa->readonly) { + update_rdonly = B_TRUE; + spa->readonly = B_FALSE; + } + } else { + return (EINVAL); + } + if (update_rdonly) { + fprintf(stderr, "Updating readonly for %s " + "to %s\n", spa->spa_name, val); + dmu_objset_find(spa->spa_name, + uzfs_zpool_rdonly_cb, val, + DS_FIND_CHILDREN); + need_sync = B_TRUE; + } + continue; + } +#endif + need_sync = B_TRUE; break; } @@ -2141,6 +2184,31 @@ spa_load_verify(spa_t *spa) return (verify_ok ? 0 : EIO); } +#ifdef _UZFS +static void +uzfs_get_readonly_prop(spa_t *spa) +{ + char val[4]; // max strlen("off") + int err; + + err = zap_lookup(spa->spa_meta_objset, spa->spa_pool_props_object, + zpool_prop_to_name(ZPOOL_PROP_UZFS_READONLY), 1, + sizeof (val), &val); + if (err) { + // ignore error if value doesn't exist + return; + } + + if (strcmp(val, "on") == 0) { + spa->readonly = B_TRUE; + } else if (strcmp(val, "off") == 0) { + spa->readonly = B_FALSE; + } + fprintf(stderr, "pool %s imported with readonly:%s\n", + spa->spa_name, val); +} +#endif + /* * Find a value in the pool props object. */ @@ -3094,6 +3162,9 @@ spa_load_impl(spa_t *spa, uint64_t pool_guid, nvlist_t *config, spa_prop_find(spa, ZPOOL_PROP_MULTIHOST, &spa->spa_multihost); spa_prop_find(spa, ZPOOL_PROP_DEDUPDITTO, &spa->spa_dedup_ditto); +#ifdef _UZFS + uzfs_get_readonly_prop(spa); +#endif spa->spa_autoreplace = (autoreplace != 0); } diff --git a/module/zfs/zvol.c b/module/zfs/zvol.c index 738410f66dca..4ec9036eb183 100644 --- a/module/zfs/zvol.c +++ b/module/zfs/zvol.c @@ -373,6 +373,8 @@ uzfs_ioc_stats(zfs_cmd_t *zc, nvlist_t *nvl) fnvlist_add_string(innvl, "status", status_to_str(zv)); + fnvlist_add_string(innvl, "readOnly", + IS_ZVOL_READONLY(zv->main_zv) ? "on" : "off"); fnvlist_add_string(innvl, "rebuildStatus", rebuild_status_to_str( diff --git a/tests/cstor/gtest/gtest_utils.cc b/tests/cstor/gtest/gtest_utils.cc index 889849852999..d247c6e55a5e 100644 --- a/tests/cstor/gtest/gtest_utils.cc +++ b/tests/cstor/gtest/gtest_utils.cc @@ -136,6 +136,18 @@ void GtestUtils::TestPool::pExport() { execCmd("zpool", std::string("export ") + m_name); } +bool GtestUtils::TestPool::isReadOnly() { + std::string output; + output = execCmd("zpool", std::string("get -Hp -ovalue io.openebs:readonly ") + m_name); + if (output.compare("on") == 0) + return true; + if (output.compare("off") == 0) + return false; + + throw std::system_error(EINVAL, std::system_category(), + "Invalid readonly value:" + output); +} + void GtestUtils::TestPool::createZvol(std::string name, std::string arg /*= ""*/) { execCmd("zfs", std::string("create -sV ") + std::to_string(ZVOL_SIZE) + diff --git a/tests/cstor/gtest/test_zrepl_prot.cc b/tests/cstor/gtest/test_zrepl_prot.cc index 82a8dd2fa838..bf74ca0a5d6d 100644 --- a/tests/cstor/gtest/test_zrepl_prot.cc +++ b/tests/cstor/gtest/test_zrepl_prot.cc @@ -1127,6 +1127,42 @@ TEST_F(ZreplDataTest, ReadMetaDataFlag) { sleep(5); } +static bool is_zvol_readonly(std::string zvol) { + std::string output; + + output = execCmd("zfs", std::string("get io.openebs:readonly ") + std::string(" -Hpo value ") + zvol); + if (output.compare("on") == 0) + return true; + + output = execCmd("zfs", std::string("stats ") + zvol); + std::size_t found = output.find("\"readOnly\":on"); + if (found != std::string::npos) { + return true; + } + + return false; +} + +static bool isIOAckSenderCreated(std::string zvol) { + std::string output; + + output = execCmd("zfs", std::string("stats ") + zvol); + if (output.find("\"isIOAckSenderCreated\":1") != std::string::npos) + return true; + + return false; +} + +static bool isIOReceiverCreated(std::string zvol) { + std::string output; + + output = execCmd("zfs", std::string("stats ") + zvol); + if (output.find("\"isIOReceiverCreated\":1") != std::string::npos) + return true; + + return false; +} + /* * if zvol is readonly then.. * - write should fail @@ -1143,8 +1179,9 @@ TEST_F(ZreplDataTest, ZVolReadOnly) { std::string output; execCmd("zfs", std::string("set io.openebs:readonly=on ") + m_zvol_name1); - output = execCmd("zfs", std::string("get io.openebs:readonly ") + std::string(" -Hpo value ") + m_zvol_name1); - ASSERT_NE(output.find("on"), std::string::npos); + EXPECT_EQ(is_zvol_readonly(m_zvol_name1), true); + EXPECT_EQ(isIOAckSenderCreated(m_zvol_name1), true); + EXPECT_EQ(isIOReceiverCreated(m_zvol_name1), true); // read should happen read_data_start(m_datasock1.fd(), m_ioseq1, 0, sizeof (buf), &hdr_in, &read_hdr); @@ -1172,10 +1209,11 @@ TEST_F(ZreplDataTest, ZVolReadOnly) { // mgmt connection should not happen m_control_fd1 = target1->accept(10); ASSERT_EQ(m_control_fd1, -1); + EXPECT_EQ(isIOAckSenderCreated(m_zvol_name1), false); + EXPECT_EQ(isIOReceiverCreated(m_zvol_name1), false); execCmd("zfs", std::string("set io.openebs:readonly=off ") + m_zvol_name1); - output = execCmd("zfs", std::string("get io.openebs:readonly ") + std::string(" -Hpo value ") + m_zvol_name1); - ASSERT_NE(output.find("off"), std::string::npos); + EXPECT_EQ(is_zvol_readonly(m_zvol_name1), false); // mgmt connection should happen m_control_fd1 = target1->accept(10); @@ -1185,6 +1223,85 @@ TEST_F(ZreplDataTest, ZVolReadOnly) { ZVOL_OP_STATUS_OK); do_data_connection(m_datasock1.fd(), m_host1, m_port1, m_zvol_name1); + EXPECT_EQ(isIOAckSenderCreated(m_zvol_name1), true); + EXPECT_EQ(isIOReceiverCreated(m_zvol_name1), true); + + // write should happen + write_data(m_datasock1.fd(), m_ioseq1, buf, 0, sizeof (buf), ++m_ioseq1); + rc = read(m_datasock1.fd(), &hdr_in, sizeof (hdr_in)); + ASSERT_ERRNO("read", rc >= 0); + ASSERT_EQ(rc, sizeof (hdr_in)); + EXPECT_EQ(hdr_in.opcode, ZVOL_OPCODE_WRITE); + EXPECT_EQ(hdr_in.status, ZVOL_OP_STATUS_OK); + EXPECT_EQ(hdr_in.io_seq, m_ioseq1); + + m_datasock1.graceful_close(); + m_datasock2.graceful_close(); + sleep(5); +} + +/* + * if zpool is readonly then.. + * - write should fail + * - read should happen + */ +TEST_F(ZreplDataTest, ZpoolReadOnly) { + zvol_io_hdr_t hdr_in; + struct zvol_io_rw_hdr read_hdr; + struct zvol_io_rw_hdr write_hdr; + struct zrepl_status_ack status; + struct mgmt_ack mgmt_ack; + char buf[4096]; + int rc; + std::string output; + + execCmd("zpool", std::string("set io.openebs:readonly=on ") + m_pool1->m_name); + EXPECT_EQ(m_pool1->isReadOnly(), true); + EXPECT_EQ(isIOAckSenderCreated(m_zvol_name1), true); + EXPECT_EQ(isIOReceiverCreated(m_zvol_name1), true); + + // read should happen + read_data_start(m_datasock1.fd(), m_ioseq1, 0, sizeof (buf), &hdr_in, &read_hdr); + ASSERT_EQ(hdr_in.status, ZVOL_OP_STATUS_OK); + ASSERT_EQ(hdr_in.len, sizeof (read_hdr) + sizeof (buf)); + ASSERT_EQ(read_hdr.len, sizeof (buf)); + + rc = read(m_datasock1.fd(), buf, read_hdr.len); + ASSERT_ERRNO("read", rc >= 0); + ASSERT_EQ(rc, read_hdr.len); + + // write should fail + init_buf(buf, sizeof (buf), "cStor-data"); + write_data(m_datasock1.fd(), m_ioseq1, buf, 0, sizeof (buf), ++m_ioseq1); + rc = read(m_datasock1.fd(), &hdr_in, sizeof (hdr_in)); + ASSERT_ERRNO("read", rc >= 0); + ASSERT_EQ(rc, sizeof (hdr_in)); + EXPECT_EQ(hdr_in.opcode, ZVOL_OPCODE_WRITE); + EXPECT_EQ(hdr_in.status, ZVOL_OP_STATUS_FAILED); + EXPECT_EQ(hdr_in.io_seq, m_ioseq1); + + m_pool1->pExport(); + m_pool1->import(); + + // mgmt connection should not happen + m_control_fd1 = target1->accept(10); + ASSERT_EQ(m_control_fd1, -1); + EXPECT_EQ(isIOAckSenderCreated(m_zvol_name1), false); + EXPECT_EQ(isIOReceiverCreated(m_zvol_name1), false); + + execCmd("zpool", std::string("set io.openebs:readonly=off ") + m_pool1->m_name); + EXPECT_EQ(m_pool1->isReadOnly(), false); + + // mgmt connection should happen + m_control_fd1 = target1->accept(10); + ASSERT_GE(m_control_fd1, 0); + + do_handshake(m_zvol_name1, m_host1, m_port1, NULL, NULL, m_control_fd1, + ZVOL_OP_STATUS_OK); + + do_data_connection(m_datasock1.fd(), m_host1, m_port1, m_zvol_name1); + EXPECT_EQ(isIOAckSenderCreated(m_zvol_name1), true); + EXPECT_EQ(isIOReceiverCreated(m_zvol_name1), true); // write should happen write_data(m_datasock1.fd(), m_ioseq1, buf, 0, sizeof (buf), ++m_ioseq1); @@ -2006,23 +2123,20 @@ TEST(Snapshot, ZVolReadOnly) { // make zvol readonly execCmd("zfs", std::string("set io.openebs:readonly=on ") + vol_name); - output = execCmd("zfs", std::string("get io.openebs:readonly ") + std::string(" -Hpo value ") + vol_name); - ASSERT_NE(output.find("on"), std::string::npos); + EXPECT_EQ(is_zvol_readonly(vol_name), true); // snap prepare should fail prep_snap(control_fd, snap_name, hdr_out.io_seq, ZVOL_OP_STATUS_FAILED); // disable zvol readonly execCmd("zfs", std::string("set io.openebs:readonly=off ") + vol_name); - output = execCmd("zfs", std::string("get io.openebs:readonly ") + std::string(" -Hpo value ") + vol_name); - ASSERT_NE(output.find("off"), std::string::npos); + EXPECT_EQ(is_zvol_readonly(vol_name), false); // snap prepare should happen prep_snap(control_fd, snap_name, hdr_out.io_seq, ZVOL_OP_STATUS_OK); // set zvol readonly execCmd("zfs", std::string("set io.openebs:readonly=on ") + vol_name); - output = execCmd("zfs", std::string("get io.openebs:readonly ") + std::string(" -Hpo value ") + vol_name); - ASSERT_NE(output.find("on"), std::string::npos); + EXPECT_EQ(is_zvol_readonly(vol_name), true); hdr_out.version = REPLICA_VERSION; hdr_out.opcode = ZVOL_OPCODE_SNAP_CREATE; @@ -2048,6 +2162,82 @@ TEST(Snapshot, ZVolReadOnly) { sleep(3); } +TEST(Snapshot, ZpoolReadOnly) { + zvol_io_hdr_t hdr_in, hdr_out = {0}; + Zrepl zrepl; + Target target; + int rc, control_fd; + SocketFd datasock; + TestPool pool("snappool"); + char *buf; + std::string vol_name = pool.getZvolName("vol"); + std::string snap_name = pool.getZvolName("vol@snap"); + uint64_t ioseq; + std::string host; + uint16_t port; + struct zvol_snapshot_list *snaplist; + std::string output; + + zrepl.start(); + pool.create(); + pool.createZvol("vol", "-o io.openebs:targetip=127.0.0.1 -o io.openebs:zvol_replica_id=12345"); + + rc = target.listen(); + ASSERT_GE(rc, 0); + control_fd = target.accept(-1); + ASSERT_GE(control_fd, 0); + + do_handshake(vol_name, host, port, NULL, NULL, control_fd, ZVOL_OP_STATUS_OK); + do_data_connection(datasock.fd(), host, port, vol_name, 4096, 2); + + // create the snapshot + transition_zvol_to_online(ioseq, control_fd, vol_name); + sleep(5); + hdr_out.io_seq = 2; + hdr_out.len = snap_name.length() + 1; + + // make zvol readonly + execCmd("zpool", std::string("set io.openebs:readonly=on ") + pool.m_name); + EXPECT_EQ(pool.isReadOnly(), true); + // snap prepare should fail + prep_snap(control_fd, snap_name, hdr_out.io_seq, ZVOL_OP_STATUS_FAILED); + + // disable zvol readonly + execCmd("zpool", std::string("set io.openebs:readonly=off ") + pool.m_name); + EXPECT_EQ(pool.isReadOnly(), false); + // snap prepare should happen + prep_snap(control_fd, snap_name, hdr_out.io_seq, ZVOL_OP_STATUS_OK); + + + // set zvol readonly + execCmd("zpool", std::string("set io.openebs:readonly=on ") + pool.m_name); + EXPECT_EQ(pool.isReadOnly(), true); + + hdr_out.version = REPLICA_VERSION; + hdr_out.opcode = ZVOL_OPCODE_SNAP_CREATE; + hdr_out.status = ZVOL_OP_STATUS_OK; + hdr_out.io_seq = 0; + hdr_out.len = snap_name.length() + 1; + rc = write(control_fd, &hdr_out, sizeof (hdr_out)); + ASSERT_EQ(rc, sizeof (hdr_out)); + rc = write(control_fd, snap_name.c_str(), hdr_out.len); + ASSERT_EQ(rc, hdr_out.len); + + // snap create should fail + rc = read(control_fd, &hdr_in, sizeof (hdr_in)); + ASSERT_EQ(rc, sizeof (hdr_in)); + EXPECT_EQ(hdr_in.version, REPLICA_VERSION); + EXPECT_EQ(hdr_in.opcode, ZVOL_OPCODE_SNAP_CREATE); + EXPECT_EQ(hdr_in.status, ZVOL_OP_STATUS_FAILED); + EXPECT_EQ(hdr_in.io_seq, 0); + ASSERT_EQ(hdr_in.len, 0); + + datasock.graceful_close(); + graceful_close(control_fd); + sleep(3); +} + + /* * resize the cloned volume when while making