1 /*-
2 * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions, and the following disclaimer,
10 * without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 * substantially similar to the "NO WARRANTY" disclaimer below
13 * ("Disclaimer") and any redistribution must be conditioned upon
14 * including a substantially similar Disclaimer requirement for further
15 * binary redistribution.
16 *
17 * NO WARRANTY
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGES.
29 *
30 * Authors: Justin T. Gibbs (Spectra Logic Corporation)
31 */
32
33 /**
34 * \file case_file.h
35 *
36 * CaseFile objects aggregate vdev faults that may require ZFSD action
37 * in order to maintain the health of a ZFS pool.
38 *
39 * Header requirements:
40 *
41 * #include <list>
42 *
43 * #include "callout.h"
44 * #include "zfsd_event.h"
45 */
46 #ifndef _CASE_FILE_H_
47 #define _CASE_FILE_H_
48
49 /*=========================== Forward Declarations ===========================*/
50 class CaseFile;
51 class Vdev;
52
53 /*============================= Class Definitions ============================*/
54 /*------------------------------- CaseFileList -------------------------------*/
55 /**
56 * CaseFileList is a specialization of the standard list STL container.
57 */
58 typedef std::list< CaseFile *> CaseFileList;
59
60 /*--------------------------------- CaseFile ---------------------------------*/
61 /**
62 * A CaseFile object is instantiated anytime a vdev for an active pool
63 * experiences an I/O error, is faulted by ZFS, or is determined to be
64 * missing/removed.
65 *
66 * A vdev may have at most one CaseFile.
67 *
68 * CaseFiles are retired when a vdev leaves an active pool configuration
69 * or an action is taken to resolve the issues recorded in the CaseFile.
70 *
71 * Logging a case against a vdev does not imply that an immediate action
72 * to resolve a fault is required or even desired. For example, a CaseFile
73 * must accumulate a number of I/O errors in order to flag a device as
74 * degraded.
75 *
76 * Vdev I/O errors are not recorded in ZFS label inforamation. For this
77 * reasons, CaseFile%%s with accumulated I/O error events are serialized
78 * to the file system so that they survive across boots. Currently all
79 * other fault types can be reconstructed from ZFS label information, so
80 * CaseFile%%s for missing, faulted, or degradded members are just recreated
81 * at ZFSD startup instead of being deserialized from the file system.
82 */
83 class CaseFile
84 {
85 public:
86 /**
87 * \brief Find a CaseFile object by a vdev's pool/vdev GUID tuple.
88 *
89 * \param poolGUID Pool GUID for the vdev of the CaseFile to find.
90 * If InvalidGuid, then only match the vdev GUID
91 * instead of both pool and vdev GUIDs.
92 * \param vdevGUID Vdev GUID for the vdev of the CaseFile to find.
93 *
94 * \return If found, a pointer to a valid CaseFile object.
95 * Otherwise NULL.
96 */
97 static CaseFile *Find(DevdCtl::Guid poolGUID, DevdCtl::Guid vdevGUID);
98
99 /**
100 * \brief Find multiple CaseFile objects by a vdev's pool/vdev
101 * GUID tuple (special case for spare vdevs)
102 *
103 * \param poolGUID Pool GUID for the vdev of the CaseFile to find.
104 * If InvalidGuid, then only match the vdev GUID
105 * instead of both pool and vdev GUIDs.
106 * \param vdevGUID Vdev GUID for the vdev of the CaseFile to find.
107 * \param caseList List of cases associated with the vdev.
108 */
109 static void Find(DevdCtl::Guid poolGUID, DevdCtl::Guid vdevGUID,
110 CaseFileList &caseList);
111
112 /**
113 * \brief Find a CaseFile object by a vdev's current/last known
114 * physical path.
115 *
116 * \param physPath Physical path of the vdev of the CaseFile to find.
117 *
118 * \return If found, a pointer to a valid CaseFile object.
119 * Otherwise NULL.
120 */
121 static CaseFile *Find(const string &physPath);
122
123 /**
124 * \brief ReEvaluate all open cases whose pool guid matches the argument
125 *
126 * \param poolGUID Only reevaluate cases for this pool
127 * \param event Try to consume this event with the casefile
128 */
129 static void ReEvaluateByGuid(DevdCtl::Guid poolGUID,
130 const ZfsEvent &event);
131
132 /**
133 * \brief Create or return an existing active CaseFile for the
134 * specified vdev.
135 *
136 * \param vdev The vdev object for which to find/create a CaseFile.
137 *
138 * \return A reference to a valid CaseFile object.
139 */
140 static CaseFile &Create(Vdev &vdev);
141
142 /**
143 * \brief Deserialize all serialized CaseFile objects found in
144 * the file system.
145 */
146 static void DeSerialize();
147
148 /**
149 * \brief returns true if there are no CaseFiles
150 */
151 static bool Empty();
152
153 /**
154 * \brief Emit syslog data on all active CaseFile%%s in the system.
155 */
156 static void LogAll();
157
158 /**
159 * \brief Destroy the in-core cache of CaseFile data.
160 *
161 * This routine does not disturb the on disk, serialized, CaseFile
162 * data.
163 */
164 static void PurgeAll();
165
166 DevdCtl::Guid PoolGUID() const;
167 DevdCtl::Guid VdevGUID() const;
168 vdev_state VdevState() const;
169 const string &PoolGUIDString() const;
170 const string &VdevGUIDString() const;
171 const string &PhysicalPath() const;
172
173 /**
174 * \brief Attempt to resolve this CaseFile using the disk
175 * resource at the given device/physical path/vdev object
176 * tuple.
177 *
178 * \param devPath The devfs path for the disk resource.
179 * \param physPath The physical path information reported by
180 * the disk resource.
181 * \param vdev If the disk contains ZFS label information,
182 * a pointer to the disk label's vdev object
183 * data. Otherwise NULL.
184 *
185 * \return True if this event was consumed by this CaseFile.
186 */
187 bool ReEvaluate(const string &devPath, const string &physPath,
188 Vdev *vdev);
189
190 /**
191 * \brief Update this CaseFile in light of the provided ZfsEvent.
192 *
193 * Must be virtual so it can be overridden in the unit tests
194 *
195 * \param event The ZfsEvent to evaluate.
196 *
197 * \return True if this event was consumed by this CaseFile.
198 */
199 virtual bool ReEvaluate(const ZfsEvent &event);
200
201 /**
202 * \brief Register an itimer callout for the given event, if necessary
203 */
204 virtual void RegisterCallout(const DevdCtl::Event &event);
205
206 /**
207 * \brief Close a case if it is no longer relevant.
208 *
209 * This method deals with cases tracking soft errors. Soft errors
210 * will be discarded should a remove event occur within a short period
211 * of the soft errors being reported. We also discard the events
212 * if the vdev is marked degraded or failed.
213 *
214 * \return True if the case is closed. False otherwise.
215 */
216 bool CloseIfSolved();
217
218 /**
219 * \brief Emit data about this CaseFile via syslog(3).
220 */
221 void Log();
222
223 /**
224 * \brief Whether we should degrade this vdev
225 */
226 bool ShouldDegrade() const;
227
228 /**
229 * \brief Whether we should fault this vdev
230 */
231 bool ShouldFault() const;
232
233 /**
234 * \brief If this vdev is spare
235 */
236 int IsSpare();
237
238 /**
239 * \brief Get case vdev's specified property
240 */
241 int GetVdevProp(vdev_prop_t) const;
242
243 protected:
244 enum {
245 /*
246 * Use these defaults if we can't get the corresponding vdev
247 * prop or if the prop is not set
248 */
249 /**
250 * The number of soft errors on a vdev required
251 * to transition a vdev from healthy to degraded
252 * status
253 */
254 DEFAULT_ZFS_DEGRADE_IO_COUNT = 50,
255 /**
256 * The number of delay errors on a vdev required to fault it
257 */
258 DEFAULT_ZFS_FAULT_SLOW_IO_COUNT = 8,
259 };
260
261 static CalloutFunc_t OnGracePeriodEnded;
262
263 /**
264 * \brief scandir(3) filter function used to find files containing
265 * serialized CaseFile data.
266 *
267 * \param dirEntry Directory entry for the file to filter.
268 *
269 * \return Non-zero for a file to include in the selection,
270 * otherwise 0.
271 */
272 static int DeSerializeSelector(const struct dirent *dirEntry);
273
274 /**
275 * \brief Given the name of a file containing serialized events from a
276 * CaseFile object, create/update an in-core CaseFile object
277 * representing the serialized data.
278 *
279 * \param fileName The name of a file containing serialized events
280 * from a CaseFile object.
281 */
282 static void DeSerializeFile(const char *fileName);
283
284 /** Constructor. */
285 CaseFile(const Vdev &vdev);
286
287 /**
288 * Destructor.
289 * Must be virtual so it can be subclassed in the unit tests
290 */
291 virtual ~CaseFile();
292
293 /**
294 * \brief Reload state for the vdev associated with this CaseFile.
295 *
296 * \return True if the refresh was successful. False if the system
297 * has no record of the pool or vdev for this CaseFile.
298 */
299 virtual bool RefreshVdevState();
300
301 /**
302 * \brief Free all events in the m_events list.
303 */
304 void PurgeEvents();
305
306 /**
307 * \brief Free all events in the m_tentativeEvents list.
308 */
309 void PurgeTentativeEvents();
310
311 /**
312 * \brief Commit to file system storage.
313 */
314 void Serialize();
315
316 /**
317 * \brief Retrieve event data from a serialization stream.
318 *
319 * \param caseStream The serializtion stream to parse.
320 */
321 void DeSerialize(std::ifstream &caseStream);
322
323 /**
324 * \brief Serializes the supplied event list and writes it to fd
325 *
326 * \param prefix If not NULL, this prefix will be prepended to
327 * every event in the file.
328 */
329 void SerializeEvList(const DevdCtl::EventList events, int fd,
330 const char* prefix=NULL) const;
331
332 /**
333 * \brief Unconditionally close a CaseFile.
334 */
335 virtual void Close();
336
337 /**
338 * \brief Callout callback invoked when the remove timer grace
339 * period expires.
340 *
341 * If no remove events are received prior to the grace period
342 * firing, then any tentative events are promoted and counted
343 * against the health of the vdev.
344 */
345 void OnGracePeriodEnded();
346
347 /**
348 * \brief Attempt to activate a spare on this case's pool.
349 *
350 * Call this whenever a pool becomes degraded. It will look for any
351 * spare devices and activate one to replace the casefile's vdev. It
352 * will _not_ close the casefile; that should only happen when the
353 * missing drive is replaced or the user promotes the spare.
354 *
355 * \return True if a spare was activated
356 */
357 bool ActivateSpare();
358
359 /**
360 * \brief replace a pool's vdev with another
361 *
362 * \param vdev_type The type of the new vdev. Usually either
363 * VDEV_TYPE_DISK or VDEV_TYPE_FILE
364 * \param path The file system path to the new vdev
365 * \param isspare Whether the new vdev is a spare
366 *
367 * \return true iff the replacement was successful
368 */
369 bool Replace(const char* vdev_type, const char* path, bool isspare);
370
371 /**
372 * \brief Which vdev, if any, is replacing ours.
373 *
374 * \param zhp Pool handle state from the caller context
375 *
376 * \return the vdev that is currently replacing ours,
377 * or NonexistentVdev if there isn't one.
378 */
379 Vdev BeingReplacedBy(zpool_handle_t *zhp);
380
381 /**
382 * \brief All CaseFiles being tracked by ZFSD.
383 */
384 static CaseFileList s_activeCases;
385
386 /**
387 * \brief The file system path to serialized CaseFile data.
388 */
389 static const string s_caseFilePath;
390
391 /**
392 * \brief A list of soft error events counted against the health of
393 * a vdev.
394 */
395 DevdCtl::EventList m_events;
396
397 /**
398 * \brief A list of soft error events waiting for a grace period
399 * expiration before being counted against the health of
400 * a vdev.
401 */
402 DevdCtl::EventList m_tentativeEvents;
403
404 DevdCtl::Guid m_poolGUID;
405 DevdCtl::Guid m_vdevGUID;
406 vdev_state m_vdevState;
407 string m_poolGUIDString;
408 string m_vdevGUIDString;
409 string m_vdevPhysPath;
410 string m_vdevName;
411 int m_is_spare;
412
413 /**
414 * \brief Callout activated when a grace period
415 */
416 Callout m_tentativeTimer;
417
418 private:
419 nvlist_t *CaseVdev(zpool_handle_t *zhp) const;
420 };
421
422 inline DevdCtl::Guid
PoolGUID()423 CaseFile::PoolGUID() const
424 {
425 return (m_poolGUID);
426 }
427
428 inline DevdCtl::Guid
VdevGUID()429 CaseFile::VdevGUID() const
430 {
431 return (m_vdevGUID);
432 }
433
434 inline vdev_state
VdevState()435 CaseFile::VdevState() const
436 {
437 return (m_vdevState);
438 }
439
440 inline const string &
PoolGUIDString()441 CaseFile::PoolGUIDString() const
442 {
443 return (m_poolGUIDString);
444 }
445
446 inline const string &
VdevGUIDString()447 CaseFile::VdevGUIDString() const
448 {
449 return (m_vdevGUIDString);
450 }
451
452 inline const string &
PhysicalPath()453 CaseFile::PhysicalPath() const
454 {
455 return (m_vdevPhysPath);
456 }
457
458 #endif /* _CASE_FILE_H_ */
459