diff --git a/cddl/usr.sbin/zfsd/tests/zfsd_unittest.cc b/cddl/usr.sbin/zfsd/tests/zfsd_unittest.cc index f3fea2ca83f4..caeb077a3de8 100644 --- a/cddl/usr.sbin/zfsd/tests/zfsd_unittest.cc +++ b/cddl/usr.sbin/zfsd/tests/zfsd_unittest.cc @@ -1,768 +1,805 @@ /*- * Copyright (c) 2012, 2013, 2014 Spectra Logic Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * Authors: Alan Somers (Spectra Logic Corporation) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libmocks.h" /*================================== Macros ==================================*/ #define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x)) /*============================ Namespace Control =============================*/ using std::string; using std::stringstream; using DevdCtl::Event; using DevdCtl::EventFactory; using DevdCtl::EventList; using DevdCtl::Guid; using DevdCtl::NVPairMap; /* redefine zpool_handle here because libzfs_impl.h is not includable */ struct zpool_handle { libzfs_handle_t *zpool_hdl; zpool_handle_t *zpool_next; char zpool_name[ZFS_MAX_DATASET_NAME_LEN]; int zpool_state; size_t zpool_config_size; nvlist_t *zpool_config; nvlist_t *zpool_old_config; nvlist_t *zpool_props; diskaddr_t zpool_start_block; }; class MockZfsEvent : public ZfsEvent { public: MockZfsEvent(Event::Type, NVPairMap&, const string&); virtual ~MockZfsEvent() {} static BuildMethod MockZfsEventBuilder; MOCK_CONST_METHOD0(ProcessPoolEvent, void()); static EventFactory::Record s_buildRecords[]; }; EventFactory::Record MockZfsEvent::s_buildRecords[] = { { Event::NOTIFY, "ZFS", &MockZfsEvent::MockZfsEventBuilder } }; MockZfsEvent::MockZfsEvent(Event::Type type, NVPairMap& map, const string& str) : ZfsEvent(type, map, str) { } Event * MockZfsEvent::MockZfsEventBuilder(Event::Type type, NVPairMap &nvpairs, const string &eventString) { return (new MockZfsEvent(type, nvpairs, eventString)); } /* * A dummy Vdev class used for testing other classes */ class MockVdev : public Vdev { public: MockVdev(nvlist_t *vdevConfig); virtual ~MockVdev() {} MOCK_CONST_METHOD0(GUID, Guid()); MOCK_CONST_METHOD0(PoolGUID, Guid()); MOCK_CONST_METHOD0(State, vdev_state()); MOCK_CONST_METHOD0(PhysicalPath, string()); }; MockVdev::MockVdev(nvlist_t *vdevConfig) : Vdev(vdevConfig) { } /* * A CaseFile class with side effects removed, for testing */ class TestableCaseFile : public CaseFile { public: static TestableCaseFile &Create(Vdev &vdev); TestableCaseFile(Vdev &vdev); virtual ~TestableCaseFile() {} MOCK_METHOD0(Close, void()); MOCK_METHOD1(RegisterCallout, void(const Event &event)); MOCK_METHOD0(RefreshVdevState, bool()); MOCK_METHOD1(ReEvaluate, bool(const ZfsEvent &event)); bool RealReEvaluate(const ZfsEvent &event) { return (CaseFile::ReEvaluate(event)); } /* * This splices the event lists, a procedure that would normally be done * by OnGracePeriodEnded, but we don't necessarily call that in the * unit tests */ void SpliceEvents(); /* * Used by some of our expectations. CaseFile does not publicize this */ static int getActiveCases() { return (s_activeCases.size()); } }; TestableCaseFile::TestableCaseFile(Vdev &vdev) : CaseFile(vdev) { } TestableCaseFile & TestableCaseFile::Create(Vdev &vdev) { TestableCaseFile *newCase; newCase = new TestableCaseFile(vdev); return (*newCase); } void TestableCaseFile::SpliceEvents() { m_events.splice(m_events.begin(), m_tentativeEvents); } /* * Test class ZfsdException */ class ZfsdExceptionTest : public ::testing::Test { protected: virtual void SetUp() { ASSERT_EQ(0, nvlist_alloc(&poolConfig, NV_UNIQUE_NAME, 0)); ASSERT_EQ(0, nvlist_add_string(poolConfig, ZPOOL_CONFIG_POOL_NAME, "unit_test_pool")); ASSERT_EQ(0, nvlist_add_uint64(poolConfig, ZPOOL_CONFIG_POOL_GUID, 0x1234)); ASSERT_EQ(0, nvlist_alloc(&vdevConfig, NV_UNIQUE_NAME, 0)); ASSERT_EQ(0, nvlist_add_uint64(vdevConfig, ZPOOL_CONFIG_GUID, 0x5678)); bzero(&poolHandle, sizeof(poolHandle)); poolHandle.zpool_config = poolConfig; } virtual void TearDown() { nvlist_free(poolConfig); nvlist_free(vdevConfig); } nvlist_t *poolConfig; nvlist_t *vdevConfig; zpool_handle_t poolHandle; }; TEST_F(ZfsdExceptionTest, StringConstructorNull) { ZfsdException ze(""); EXPECT_STREQ("", ze.GetString().c_str()); } TEST_F(ZfsdExceptionTest, StringConstructorFormatted) { ZfsdException ze(" %d %s", 55, "hello world"); EXPECT_STREQ(" 55 hello world", ze.GetString().c_str()); } TEST_F(ZfsdExceptionTest, LogSimple) { ZfsdException ze("unit test w/o vdev or pool"); ze.Log(); EXPECT_EQ(LOG_ERR, syslog_last_priority); EXPECT_STREQ("unit test w/o vdev or pool\n", syslog_last_message); } TEST_F(ZfsdExceptionTest, Pool) { const char msg[] = "Exception with pool name"; char expected[4096]; sprintf(expected, "Pool unit_test_pool: %s\n", msg); ZfsdException ze(poolConfig, msg); ze.Log(); EXPECT_STREQ(expected, syslog_last_message); } TEST_F(ZfsdExceptionTest, PoolHandle) { const char msg[] = "Exception with pool handle"; char expected[4096]; sprintf(expected, "Pool unit_test_pool: %s\n", msg); ZfsdException ze(&poolHandle, msg); ze.Log(); EXPECT_STREQ(expected, syslog_last_message); } /* * Test class Vdev */ class VdevTest : public ::testing::Test { protected: virtual void SetUp() { ASSERT_EQ(0, nvlist_alloc(&m_poolConfig, NV_UNIQUE_NAME, 0)); ASSERT_EQ(0, nvlist_add_uint64(m_poolConfig, ZPOOL_CONFIG_POOL_GUID, 0x1234)); ASSERT_EQ(0, nvlist_alloc(&m_vdevConfig, NV_UNIQUE_NAME, 0)); ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_GUID, 0x5678)); } virtual void TearDown() { nvlist_free(m_poolConfig); nvlist_free(m_vdevConfig); } nvlist_t *m_poolConfig; nvlist_t *m_vdevConfig; }; TEST_F(VdevTest, StateFromConfig) { vdev_stat_t vs; vs.vs_state = VDEV_STATE_OFFLINE; ASSERT_EQ(0, nvlist_add_uint64_array(m_vdevConfig, ZPOOL_CONFIG_VDEV_STATS, (uint64_t*)&vs, sizeof(vs) / sizeof(uint64_t))); Vdev vdev(m_poolConfig, m_vdevConfig); EXPECT_EQ(VDEV_STATE_OFFLINE, vdev.State()); } TEST_F(VdevTest, StateFaulted) { ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_FAULTED, 1)); Vdev vdev(m_poolConfig, m_vdevConfig); EXPECT_EQ(VDEV_STATE_FAULTED, vdev.State()); } /* * Test that we can construct a Vdev from the label information that is stored * on an available spare drive */ TEST_F(VdevTest, ConstructAvailSpare) { nvlist_t *labelConfig; ASSERT_EQ(0, nvlist_alloc(&labelConfig, NV_UNIQUE_NAME, 0)); ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_GUID, 1948339428197961030)); ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_POOL_STATE, POOL_STATE_SPARE)); EXPECT_NO_THROW(Vdev vdev(labelConfig)); nvlist_free(labelConfig); } /* Available spares will always show the HEALTHY state */ TEST_F(VdevTest, AvailSpareState) { nvlist_t *labelConfig; ASSERT_EQ(0, nvlist_alloc(&labelConfig, NV_UNIQUE_NAME, 0)); ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_GUID, 1948339428197961030)); ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_POOL_STATE, POOL_STATE_SPARE)); Vdev vdev(labelConfig); EXPECT_EQ(VDEV_STATE_HEALTHY, vdev.State()); nvlist_free(labelConfig); } /* Test the Vdev::IsSpare method */ TEST_F(VdevTest, IsSpare) { Vdev notSpare(m_poolConfig, m_vdevConfig); EXPECT_EQ(false, notSpare.IsSpare()); ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_IS_SPARE, 1)); Vdev isSpare(m_poolConfig, m_vdevConfig); EXPECT_EQ(true, isSpare.IsSpare()); } /* * Test class ZFSEvent */ class ZfsEventTest : public ::testing::Test { protected: virtual void SetUp() { m_eventFactory = new EventFactory(); m_eventFactory->UpdateRegistry(MockZfsEvent::s_buildRecords, NUM_ELEMENTS(MockZfsEvent::s_buildRecords)); m_event = NULL; } virtual void TearDown() { delete m_eventFactory; delete m_event; } EventFactory *m_eventFactory; Event *m_event; }; TEST_F(ZfsEventTest, ProcessPoolEventGetsCalled) { string evString("!system=ZFS " "subsystem=ZFS " "type=sysevent.fs.zfs.vdev_remove " "pool_name=foo " "pool_guid=9756779504028057996 " "vdev_guid=1631193447431603339 " "vdev_path=/dev/da1 " "timestamp=1348871594"); m_event = Event::CreateEvent(*m_eventFactory, evString); MockZfsEvent *mock_event = static_cast(m_event); EXPECT_CALL(*mock_event, ProcessPoolEvent()).Times(1); mock_event->Process(); } /* * Test class CaseFile */ class CaseFileTest : public ::testing::Test { protected: virtual void SetUp() { m_eventFactory = new EventFactory(); m_eventFactory->UpdateRegistry(MockZfsEvent::s_buildRecords, NUM_ELEMENTS(MockZfsEvent::s_buildRecords)); m_event = NULL; nvlist_alloc(&m_vdevConfig, NV_UNIQUE_NAME, 0); ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_GUID, 0xbeef)); m_vdev = new MockVdev(m_vdevConfig); ON_CALL(*m_vdev, GUID()) .WillByDefault(::testing::Return(Guid(123))); ON_CALL(*m_vdev, PoolGUID()) .WillByDefault(::testing::Return(Guid(456))); ON_CALL(*m_vdev, State()) .WillByDefault(::testing::Return(VDEV_STATE_HEALTHY)); m_caseFile = &TestableCaseFile::Create(*m_vdev); ON_CALL(*m_caseFile, ReEvaluate(::testing::_)) .WillByDefault(::testing::Invoke(m_caseFile, &TestableCaseFile::RealReEvaluate)); return; } virtual void TearDown() { delete m_caseFile; nvlist_free(m_vdevConfig); delete m_vdev; delete m_event; delete m_eventFactory; } nvlist_t *m_vdevConfig; MockVdev *m_vdev; TestableCaseFile *m_caseFile; Event *m_event; EventFactory *m_eventFactory; }; /* * A Vdev with no events should not be degraded or faulted */ TEST_F(CaseFileTest, HealthyVdev) { EXPECT_FALSE(m_caseFile->ShouldDegrade()); EXPECT_FALSE(m_caseFile->ShouldFault()); } /* * A Vdev with only one event should not be degraded or faulted * For performance reasons, RefreshVdevState should not be called. */ TEST_F(CaseFileTest, HealthyishVdev) { string evString("!system=ZFS " "class=ereport.fs.zfs.io " "ena=12091638756982918145 " "parent_guid=13237004955564865395 " "parent_type=raidz " "pool=testpool.4415 " "pool_context=0 " "pool_failmode=wait " "pool_guid=456 " "subsystem=ZFS " "timestamp=1348867914 " "type=ereport.fs.zfs.io " "vdev_guid=123 " "vdev_path=/dev/da400 " "vdev_type=disk " "zio_blkid=622 " "zio_err=1 " "zio_level=-2 " "zio_object=0 " "zio_objset=37 " "zio_offset=25598976 " "zio_size=1024"); m_event = Event::CreateEvent(*m_eventFactory, evString); ZfsEvent *zfs_event = static_cast(m_event); EXPECT_CALL(*m_caseFile, RefreshVdevState()) .Times(::testing::Exactly(0)); EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event)); EXPECT_FALSE(m_caseFile->ShouldDegrade()); EXPECT_FALSE(m_caseFile->ShouldFault()); } /* The case file should be closed when its pool is destroyed */ TEST_F(CaseFileTest, PoolDestroy) { string evString("!system=ZFS " "pool_name=testpool.4415 " "pool_guid=456 " "subsystem=ZFS " "timestamp=1348867914 " "type=sysevent.fs.zfs.pool_destroy "); m_event = Event::CreateEvent(*m_eventFactory, evString); ZfsEvent *zfs_event = static_cast(m_event); EXPECT_CALL(*m_caseFile, Close()); EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event)); } /* * A Vdev with a very large number of IO errors should fault * For performance reasons, RefreshVdevState should be called at most once */ TEST_F(CaseFileTest, VeryManyIOErrors) { EXPECT_CALL(*m_caseFile, RefreshVdevState()) .Times(::testing::AtMost(1)) .WillRepeatedly(::testing::Return(true)); for(int i=0; i<100; i++) { stringstream evStringStream; evStringStream << "!system=ZFS " "class=ereport.fs.zfs.io " "ena=12091638756982918145 " "parent_guid=13237004955564865395 " "parent_type=raidz " "pool=testpool.4415 " "pool_context=0 " "pool_failmode=wait " "pool_guid=456 " "subsystem=ZFS " "timestamp="; evStringStream << i << " "; evStringStream << "type=ereport.fs.zfs.io " "vdev_guid=123 " "vdev_path=/dev/da400 " "vdev_type=disk " "zio_blkid=622 " "zio_err=1 " "zio_level=-2 " "zio_object=0 " "zio_objset=37 " "zio_offset=25598976 " "zio_size=1024"; Event *event(Event::CreateEvent(*m_eventFactory, evStringStream.str())); ZfsEvent *zfs_event = static_cast(event); EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event)); delete event; } m_caseFile->SpliceEvents(); EXPECT_FALSE(m_caseFile->ShouldDegrade()); EXPECT_TRUE(m_caseFile->ShouldFault()); } /* * A Vdev with a very large number of checksum errors should degrade * For performance reasons, RefreshVdevState should be called at most once */ TEST_F(CaseFileTest, VeryManyChecksumErrors) { EXPECT_CALL(*m_caseFile, RefreshVdevState()) .Times(::testing::AtMost(1)) .WillRepeatedly(::testing::Return(true)); for(int i=0; i<100; i++) { stringstream evStringStream; evStringStream << "!system=ZFS " "bad_cleared_bits=03000000000000803f50b00000000000 " "bad_range_clears=0000000e " "bad_range_sets=00000000 " "bad_ranges=0000000000000010 " "bad_ranges_min_gap=8 " "bad_set_bits=00000000000000000000000000000000 " "class=ereport.fs.zfs.checksum " "ena=12272856582652437505 " "parent_guid=5838204195352909894 " "parent_type=raidz pool=testpool.7640 " "pool_context=0 " "pool_failmode=wait " "pool_guid=456 " "subsystem=ZFS timestamp="; evStringStream << i << " "; evStringStream << "type=ereport.fs.zfs.checksum " "vdev_guid=123 " "vdev_path=/mnt/tmp/file1.7702 " "vdev_type=file " "zio_blkid=0 " "zio_err=0 " "zio_level=0 " "zio_object=3 " "zio_objset=0 " "zio_offset=16896 " "zio_size=512"; Event *event(Event::CreateEvent(*m_eventFactory, evStringStream.str())); ZfsEvent *zfs_event = static_cast(event); EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event)); delete event; } m_caseFile->SpliceEvents(); EXPECT_TRUE(m_caseFile->ShouldDegrade()); EXPECT_FALSE(m_caseFile->ShouldFault()); } /* * Test CaseFile::ReEvaluateByGuid */ class ReEvaluateByGuidTest : public ::testing::Test { protected: virtual void SetUp() { m_eventFactory = new EventFactory(); m_eventFactory->UpdateRegistry(MockZfsEvent::s_buildRecords, NUM_ELEMENTS(MockZfsEvent::s_buildRecords)); m_event = Event::CreateEvent(*m_eventFactory, s_evString); nvlist_alloc(&m_vdevConfig, NV_UNIQUE_NAME, 0); ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_GUID, 0xbeef)); m_vdev456 = new ::testing::NiceMock(m_vdevConfig); m_vdev789 = new ::testing::NiceMock(m_vdevConfig); ON_CALL(*m_vdev456, GUID()) .WillByDefault(::testing::Return(Guid(123))); ON_CALL(*m_vdev456, PoolGUID()) .WillByDefault(::testing::Return(Guid(456))); ON_CALL(*m_vdev456, State()) .WillByDefault(::testing::Return(VDEV_STATE_HEALTHY)); ON_CALL(*m_vdev789, GUID()) .WillByDefault(::testing::Return(Guid(123))); ON_CALL(*m_vdev789, PoolGUID()) .WillByDefault(::testing::Return(Guid(789))); ON_CALL(*m_vdev789, State()) .WillByDefault(::testing::Return(VDEV_STATE_HEALTHY)); m_caseFile456 = NULL; m_caseFile789 = NULL; return; } virtual void TearDown() { delete m_caseFile456; delete m_caseFile789; nvlist_free(m_vdevConfig); delete m_vdev456; delete m_vdev789; delete m_event; delete m_eventFactory; } static string s_evString; nvlist_t *m_vdevConfig; ::testing::NiceMock *m_vdev456; ::testing::NiceMock *m_vdev789; TestableCaseFile *m_caseFile456; TestableCaseFile *m_caseFile789; Event *m_event; EventFactory *m_eventFactory; }; string ReEvaluateByGuidTest::s_evString( "!system=ZFS " "pool_guid=16271873792808333580 " "pool_name=foo " "subsystem=ZFS " "timestamp=1360620391 " "type=sysevent.fs.zfs.config_sync"); /* * Test the ReEvaluateByGuid method on an empty list of casefiles. * We must create one event, even though it never gets used, because it will * be passed by reference to ReEvaluateByGuid */ TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_empty) { ZfsEvent *zfs_event = static_cast(m_event); EXPECT_EQ(0, TestableCaseFile::getActiveCases()); CaseFile::ReEvaluateByGuid(Guid(456), *zfs_event); EXPECT_EQ(0, TestableCaseFile::getActiveCases()); } /* * Test the ReEvaluateByGuid method on a list of CaseFiles that contains only * one CaseFile, which doesn't match the criteria */ TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_oneFalse) { m_caseFile456 = &TestableCaseFile::Create(*m_vdev456); ZfsEvent *zfs_event = static_cast(m_event); EXPECT_EQ(1, TestableCaseFile::getActiveCases()); EXPECT_CALL(*m_caseFile456, ReEvaluate(::testing::_)) .Times(::testing::Exactly(0)); CaseFile::ReEvaluateByGuid(Guid(789), *zfs_event); EXPECT_EQ(1, TestableCaseFile::getActiveCases()); } /* * Test the ReEvaluateByGuid method on a list of CaseFiles that contains only * one CaseFile, which does match the criteria */ TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_oneTrue) { m_caseFile456 = &TestableCaseFile::Create(*m_vdev456); ZfsEvent *zfs_event = static_cast(m_event); EXPECT_EQ(1, TestableCaseFile::getActiveCases()); EXPECT_CALL(*m_caseFile456, ReEvaluate(::testing::_)) .Times(::testing::Exactly(1)) .WillRepeatedly(::testing::Return(false)); CaseFile::ReEvaluateByGuid(Guid(456), *zfs_event); EXPECT_EQ(1, TestableCaseFile::getActiveCases()); } /* * Test the ReEvaluateByGuid method on a long list of CaseFiles that contains a * few cases which meet the criteria */ TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_five) { TestableCaseFile *CaseFile1 = &TestableCaseFile::Create(*m_vdev456); TestableCaseFile *CaseFile2 = &TestableCaseFile::Create(*m_vdev789); TestableCaseFile *CaseFile3 = &TestableCaseFile::Create(*m_vdev456); TestableCaseFile *CaseFile4 = &TestableCaseFile::Create(*m_vdev789); TestableCaseFile *CaseFile5 = &TestableCaseFile::Create(*m_vdev789); ZfsEvent *zfs_event = static_cast(m_event); EXPECT_EQ(5, TestableCaseFile::getActiveCases()); EXPECT_CALL(*CaseFile1, ReEvaluate(::testing::_)) .Times(::testing::Exactly(1)) .WillRepeatedly(::testing::Return(false)); EXPECT_CALL(*CaseFile3, ReEvaluate(::testing::_)) .Times(::testing::Exactly(1)) .WillRepeatedly(::testing::Return(false)); EXPECT_CALL(*CaseFile2, ReEvaluate(::testing::_)) .Times(::testing::Exactly(0)); EXPECT_CALL(*CaseFile4, ReEvaluate(::testing::_)) .Times(::testing::Exactly(0)); EXPECT_CALL(*CaseFile5, ReEvaluate(::testing::_)) .Times(::testing::Exactly(0)); CaseFile::ReEvaluateByGuid(Guid(456), *zfs_event); EXPECT_EQ(5, TestableCaseFile::getActiveCases()); delete CaseFile1; delete CaseFile2; delete CaseFile3; delete CaseFile4; delete CaseFile5; } + +/* + * Test VdevIterator + */ +class VdevIteratorTest : public ::testing::Test +{ +}; + +bool VdevIteratorTestCB(Vdev &vdev, void *cbArg) { + return (false); +} + +/* + * VdevIterator::Next should not crash when run on a pool that has a previously + * removed vdev. Regression for + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273663 + */ +TEST_F(VdevIteratorTest, VdevRemoval) +{ + nvlist_t* poolConfig, *rootVdev; + + ASSERT_EQ(0, nvlist_alloc(&rootVdev, NV_UNIQUE_NAME, 0)); + ASSERT_EQ(0, nvlist_add_uint64(rootVdev, ZPOOL_CONFIG_GUID, 0x5678)); + /* + * Note: pools with previously-removed top-level VDEVs will contain a + * TLV in their labels that has 0 children. + */ + ASSERT_EQ(0, nvlist_add_nvlist_array(rootVdev, ZPOOL_CONFIG_CHILDREN, + NULL, 0)); + ASSERT_EQ(0, nvlist_alloc(&poolConfig, NV_UNIQUE_NAME, 0)); + ASSERT_EQ(0, nvlist_add_uint64(poolConfig, + ZPOOL_CONFIG_POOL_GUID, 0x1234)); + ASSERT_EQ(0, nvlist_add_nvlist(poolConfig, ZPOOL_CONFIG_VDEV_TREE, + rootVdev)); + + VdevIterator(poolConfig).Each(VdevIteratorTestCB, NULL); +} diff --git a/cddl/usr.sbin/zfsd/vdev_iterator.cc b/cddl/usr.sbin/zfsd/vdev_iterator.cc index f64b0d98440d..e9283108ed3c 100644 --- a/cddl/usr.sbin/zfsd/vdev_iterator.cc +++ b/cddl/usr.sbin/zfsd/vdev_iterator.cc @@ -1,170 +1,167 @@ /*- * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * Authors: Justin T. Gibbs (Spectra Logic Corporation) */ /** * \file vdev_iterator.cc * * Implementation of the VdevIterator class. */ #include #include #include #include #include #include #include #include #include #include #include "vdev.h" #include "vdev_iterator.h" #include "zfsd_exception.h" /*============================ Namespace Control =============================*/ using DevdCtl::Guid; /*=========================== Class Implementations ==========================*/ /*------------------------------- VdevIterator -------------------------------*/ VdevIterator::VdevIterator(zpool_handle_t *pool) : m_poolConfig(zpool_get_config(pool, NULL)) { Reset(); } VdevIterator::VdevIterator(nvlist_t *poolConfig) : m_poolConfig(poolConfig) { Reset(); } void VdevIterator::Reset() { nvlist_t *rootVdev; nvlist **cache_child; nvlist **spare_child; int result; uint_t cache_children; uint_t spare_children; result = nvlist_lookup_nvlist(m_poolConfig, ZPOOL_CONFIG_VDEV_TREE, &rootVdev); if (result != 0) throw ZfsdException(m_poolConfig, "Unable to extract " "ZPOOL_CONFIG_VDEV_TREE from pool."); m_vdevQueue.assign(1, rootVdev); result = nvlist_lookup_nvlist_array(rootVdev, ZPOOL_CONFIG_L2CACHE, &cache_child, &cache_children); if (result == 0) for (uint_t c = 0; c < cache_children; c++) m_vdevQueue.push_back(cache_child[c]); result = nvlist_lookup_nvlist_array(rootVdev, ZPOOL_CONFIG_SPARES, &spare_child, &spare_children); if (result == 0) for (uint_t c = 0; c < spare_children; c++) m_vdevQueue.push_back(spare_child[c]); } nvlist_t * VdevIterator::Next() { nvlist_t *vdevConfig; - if (m_vdevQueue.empty()) - return (NULL); - - for (;;) { + for (vdevConfig = NULL; !m_vdevQueue.empty();) { nvlist_t **vdevChildren; int result; u_int numChildren; vdevConfig = m_vdevQueue.front(); m_vdevQueue.pop_front(); /* Expand non-leaf vdevs. */ result = nvlist_lookup_nvlist_array(vdevConfig, ZPOOL_CONFIG_CHILDREN, &vdevChildren, &numChildren); if (result != 0) { /* leaf vdev */ break; } /* * Insert children at the head of the queue to effect a * depth first traversal of the tree. */ m_vdevQueue.insert(m_vdevQueue.begin(), vdevChildren, vdevChildren + numChildren); } return (vdevConfig); } void VdevIterator::Each(VdevCallback_t *callBack, void *callBackArg) { nvlist_t *vdevConfig; Reset(); while ((vdevConfig = Next()) != NULL) { Vdev vdev(m_poolConfig, vdevConfig); if (callBack(vdev, callBackArg)) break; } } nvlist_t * VdevIterator::Find(Guid vdevGUID) { nvlist_t *vdevConfig; Reset(); while ((vdevConfig = Next()) != NULL) { Vdev vdev(m_poolConfig, vdevConfig); if (vdev.GUID() == vdevGUID) return (vdevConfig); } return (NULL); }