github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/cmd/lhsm/hsm.go (about) 1 // Copyright (c) 2018 DDN. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "os" 13 "path/filepath" 14 "strings" 15 16 "github.com/dustin/go-humanize" 17 "github.com/pkg/errors" 18 "gopkg.in/urfave/cli.v1" 19 20 "github.com/intel-hpdd/go-lustre" 21 "github.com/intel-hpdd/go-lustre/fs" 22 "github.com/intel-hpdd/go-lustre/hsm" 23 ) 24 25 func init() { 26 hsmStateFlags := strings.Join(hsm.GetStateFlagNames(), ",") 27 28 hsmCommands := []cli.Command{ 29 { 30 Name: "archive", 31 Usage: "Initiate HSM archive of specified paths", 32 ArgsUsage: "[path [path...]]", 33 Action: hsmRequestAction(hsm.RequestArchive), 34 Flags: []cli.Flag{ 35 cli.IntFlag{ 36 Name: "id, i", 37 Usage: "Numeric ID of archive backend", 38 }, 39 cli.BoolFlag{ 40 Name: "null, 0", 41 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 42 }, 43 }, 44 }, 45 { 46 Name: "release", 47 Usage: "Release local data of HSM-archived paths", 48 ArgsUsage: "[path [path...]]", 49 Action: hsmRequestAction(hsm.RequestRelease), 50 Flags: []cli.Flag{ 51 cli.BoolFlag{ 52 Name: "null, 0", 53 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 54 }, 55 }, 56 }, 57 { 58 Name: "restore", 59 Usage: "Explicitly restore local data of HSM-archived paths", 60 ArgsUsage: "[path [path...]]", 61 Action: hsmRequestAction(hsm.RequestRestore), 62 Flags: []cli.Flag{ 63 cli.BoolFlag{ 64 Name: "null, 0", 65 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 66 }, 67 }, 68 }, 69 { 70 Name: "remove", 71 Usage: "Remove HSM-archived data of specified paths (local data is not removed)", 72 ArgsUsage: "[path [path...]]", 73 Action: hsmRequestAction(hsm.RequestRemove), 74 Flags: []cli.Flag{ 75 cli.BoolFlag{ 76 Name: "null, 0", 77 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 78 }, 79 }, 80 }, 81 { 82 Name: "cancel", 83 Usage: "Cancel HSM operations being performed on specified paths", 84 ArgsUsage: "[path [path...]]", 85 Action: hsmRequestAction(hsm.RequestCancel), 86 Flags: []cli.Flag{ 87 cli.BoolFlag{ 88 Name: "null, 0", 89 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 90 }, 91 }, 92 }, 93 { 94 Name: "set", 95 Usage: "Set HSM flags or archive ID for specified paths", 96 ArgsUsage: "[path [path...]]", 97 Action: hsmSetAction, 98 Flags: []cli.Flag{ 99 cli.BoolFlag{ 100 Name: "null, 0", 101 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 102 }, 103 cli.IntFlag{ 104 Name: "id, i", 105 Usage: "Numeric ID of archive backend", 106 }, 107 cli.StringSliceFlag{ 108 Name: "flag, f", 109 Usage: fmt.Sprintf("HSM flag to set (%s)", hsmStateFlags), 110 Value: &cli.StringSlice{}, 111 }, 112 cli.StringSliceFlag{ 113 Name: "clear, F", 114 Usage: fmt.Sprintf("HSM flag to clear (%s)", hsmStateFlags), 115 Value: &cli.StringSlice{}, 116 }, 117 }, 118 }, 119 { 120 Name: "status", 121 Usage: "Display HSM status for specified paths", 122 ArgsUsage: "[path [path...]]", 123 Action: hsmStatusAction, 124 Flags: []cli.Flag{ 125 cli.BoolFlag{ 126 Name: "action, a", 127 Usage: "Include current HSM action", 128 }, 129 cli.BoolFlag{ 130 Name: "hide-path, H", 131 Usage: "Hide pathname in output", 132 }, 133 cli.BoolFlag{ 134 Name: "long, l", 135 Usage: "Show long-form states", 136 }, 137 cli.BoolFlag{ 138 Name: "progress, p", 139 Usage: "Show copy progress for archive/restore actions", 140 }, 141 cli.BoolFlag{ 142 Name: "null, 0", 143 Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)", 144 }, 145 }, 146 }, 147 { 148 Name: "import", 149 Usage: "Import an HSM-backed file.", 150 ArgsUsage: "path", 151 Action: hsmImportAction, 152 Flags: []cli.Flag{ 153 cli.UintFlag{ 154 Name: "id, i", 155 Usage: "Numeric ID of archive backend", 156 }, 157 cli.StringFlag{ 158 Name: "uuid", 159 Usage: "File's UUID", 160 }, 161 cli.StringFlag{ 162 Name: "hash", 163 Usage: "Checksum hash value", 164 }, 165 cli.StringFlag{ 166 Name: "uid", 167 Usage: "Owner uid", 168 }, 169 cli.StringFlag{ 170 Name: "gid", 171 Usage: "Owner gid", 172 }, 173 cli.Int64Flag{ 174 Name: "size", 175 Usage: "Size of file in bytes", 176 }, 177 cli.UintFlag{ 178 Name: "mode", 179 Value: 0644, 180 Usage: "File mode", 181 }, 182 cli.StringFlag{ 183 Name: "timefmt", 184 Value: "2006-01-02 15:04:05.999999999 -0700", 185 Usage: "Format for time stamp see golang time.Parse documentation", 186 }, 187 cli.StringFlag{ 188 Name: "mtime", 189 Usage: "Modification time (default: current time)", 190 }, 191 cli.StringFlag{ 192 Name: "atime", 193 Usage: "Last access time (default: set to mtime)", 194 }, 195 cli.IntFlag{ 196 Name: "stripe_count", 197 Value: 1, 198 Usage: "Set number of stripes in file", 199 }, 200 cli.IntFlag{ 201 Name: "stripe_size", 202 Value: 1 << 20, 203 Usage: "Set stripe size in bytes", 204 }, 205 cli.StringFlag{ 206 Name: "pool", 207 Usage: "Set the start OST Pool name", 208 }, 209 }, 210 }, 211 { 212 Name: "clone", 213 Usage: "Create a relased copy of an HSM-backed file.", 214 ArgsUsage: "source_file target_file", 215 Action: hsmCloneAction, 216 Flags: []cli.Flag{ 217 cli.IntFlag{ 218 Name: "stripe_count", 219 Usage: "Override the number of stripes in the target copy.", 220 }, 221 cli.IntFlag{ 222 Name: "stripe_size", 223 Usage: "Override stripe size (bytes) in target copy.", 224 }, 225 cli.StringFlag{ 226 Name: "pool", 227 Usage: "Set the start OST Pool name", 228 }, 229 }, 230 }, 231 { 232 Name: "restripe", 233 Usage: "Change stripe parameters of a released file.", 234 ArgsUsage: "file", 235 Action: hsmRestripeAction, 236 Flags: []cli.Flag{ 237 cli.IntFlag{ 238 Name: "stripe_count", 239 Usage: "Override the number of stripes in the target copy.", 240 }, 241 cli.IntFlag{ 242 Name: "stripe_size", 243 Usage: "Override stripe size (bytes) in target copy.", 244 }, 245 cli.StringFlag{ 246 Name: "pool", 247 Usage: "Set the start OST Pool name", 248 }, 249 }, 250 }, 251 } 252 commands = append(commands, hsmCommands...) 253 } 254 255 func getFilePaths(c *cli.Context) ([]string, error) { 256 var paths []string 257 258 if c.Bool("null") { 259 reader := bufio.NewReader(os.Stdin) 260 path, err := reader.ReadBytes('\000') 261 for err == nil { 262 paths = append(paths, string(path[:len(path)-1])) 263 path, err = reader.ReadBytes('\000') 264 } 265 if err != io.EOF { 266 return nil, err 267 } 268 } else { 269 paths = c.Args() 270 } 271 272 return paths, nil 273 } 274 275 func getPathStatus(c *cli.Context, filePath string) (string, error) { 276 var buf bytes.Buffer 277 278 s, err := hsm.GetFileStatus(filePath) 279 if err != nil { 280 return "", errors.Wrapf(err, "Failed to get HSM status for %s", filePath) 281 } 282 283 if !c.Bool("hide-path") { 284 fmt.Fprintf(&buf, "%s ", filePath) 285 } 286 fmt.Fprintf(&buf, hsm.FileStatusString(s, !c.Bool("long"))) 287 288 if s.Exists() && c.Bool("action") { 289 a, err := hsm.GetFileAction(filePath) 290 if err != nil { 291 return "", errors.Wrapf(err, "Failed to get current HSM action for %s", filePath) 292 } 293 if a.IsNone() { 294 fmt.Fprintf(&buf, " -") 295 } else { 296 fmt.Fprintf(&buf, " [%s:%s]", a.Action(), a.State()) 297 if c.Bool("progress") && (a.IsArchive() || a.IsRestore()) { 298 st, err := os.Stat(filePath) 299 if err != nil { 300 return "", errors.Wrapf(err, "Failed to stat() %s", filePath) 301 } 302 fmt.Fprintf(&buf, "(%s/%s)", 303 humanize.IBytes(uint64(a.BytesCopied)), 304 humanize.IBytes(uint64(st.Size()))) 305 } 306 } 307 } else { 308 fmt.Fprintf(&buf, " -") 309 } 310 311 // TODO: Display xattrs, once we've standardized them? 312 313 return buf.String(), nil 314 } 315 316 func hsmSetAction(c *cli.Context) error { 317 logContext(c) 318 319 paths, err := getFilePaths(c) 320 if err != nil { 321 return err 322 } 323 324 if len(paths) < 1 { 325 return errors.New("HSM set request must be made with at least 1 path") 326 } 327 328 setFlags, err := hsm.GetStatusMask(c.StringSlice("flag")) 329 if err != nil { 330 return err 331 } 332 clearFlags, err := hsm.GetStatusMask(c.StringSlice("clear")) 333 if err != nil { 334 return err 335 } 336 archiveID := uint32(c.Int("id")) 337 338 if setFlags == 0 && clearFlags == 0 && archiveID == 0 { 339 return errors.New("HSM set request made with no flags to set or clear, and no new archive ID supplied") 340 } 341 342 // TODO: Parallelize this? 343 for _, path := range paths { 344 if err := hsm.SetFileStatus(path, setFlags, clearFlags, archiveID); err != nil { 345 return err 346 } 347 } 348 349 return nil 350 } 351 352 func hsmStatusAction(c *cli.Context) error { 353 logContext(c) 354 355 paths, err := getFilePaths(c) 356 if err != nil { 357 return err 358 } 359 360 if len(paths) < 1 { 361 return errors.New("HSM status request must be made with at least 1 path") 362 } 363 364 for _, path := range paths { 365 status, err := getPathStatus(c, path) 366 if err != nil { 367 return errors.Errorf("%s: %v", path, err) 368 } 369 fmt.Println(status) 370 } 371 372 return nil 373 } 374 375 type hsmRequestFn func(fs.RootDir, uint, []*lustre.Fid) error 376 377 func hsmRequestAction(requestFn func(fs.RootDir, uint, []*lustre.Fid) error) cli.ActionFunc { 378 return func(c *cli.Context) error { 379 logContext(c) 380 381 paths, err := getFilePaths(c) 382 if err != nil { 383 return err 384 } 385 386 return submitHsmRequest(c.Command.Name, hsmRequestFn(requestFn), uint(c.Int("id")), paths...) 387 } 388 } 389 390 func submitHsmRequest(actionName string, requestFn hsmRequestFn, archiveID uint, paths ...string) error { 391 var fids []*lustre.Fid 392 393 if len(paths) < 1 { 394 return fmt.Errorf("HSM %s request must be made with at least 1 path", actionName) 395 } 396 397 fsRoot, err := fs.MountRoot(paths[0]) 398 if err != nil { 399 return fmt.Errorf("Error getting fs root from %s: %s", paths[0], err) 400 } 401 402 // TODO: Occurs to me that it might be better to break up a large 403 // batch into multiple batches, each serviced by its own goroutine. 404 for _, path := range paths { 405 absPath, err2 := filepath.Abs(path) 406 if err2 != nil { 407 return fmt.Errorf("Cannot resolve absolute path for %s: %s", path, err) 408 } 409 if !strings.HasPrefix(absPath, fsRoot.Path()) { 410 return fmt.Errorf("All files in HSM request must be in the same filesystem (%s is not in %s)", 411 path, fsRoot) 412 } 413 414 fid, err2 := fs.LookupFid(path) 415 if err2 != nil { 416 return fmt.Errorf("Cannot resolve Fid for %s: %s", path, err) 417 } 418 fids = append(fids, fid) 419 } 420 421 if requestFn != nil { 422 err = requestFn(fsRoot, archiveID, fids) 423 } else { 424 err = fmt.Errorf("Unhandled HSM action: %s", actionName) 425 } 426 427 return err 428 }