-
Notifications
You must be signed in to change notification settings - Fork 0
/
devman.go
170 lines (143 loc) · 4.96 KB
/
devman.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package main
import (
"database/sql"
"fmt"
"sync"
"github.com/ammesonb/dispersed-backup/device"
"github.com/ammesonb/dispersed-backup/mydb"
)
var getDevices = mydb.GetDevices
var makeDevice = device.MakeDevice
var addDBDevice = mydb.AddDevice
// DevCommandAddDevice instructs the manager to add a new device by mount
const DevCommandAddDevice int = 1
// DevCommandReserveSpace instructs the manager to reserve an amount of space, optionally on a specific mount
const DevCommandReserveSpace int = 2
// DevCommandFreeSpace instructs the manager to free an amount of space on a given mount
const DevCommandFreeSpace int = 3
// DeviceCommand contains information needed to execute a command
type DeviceCommand struct {
// Command integer, see variables above
command int
// Mountpoint and serial, for adding a new device
// Mountpoint may also be used to request storing a file on a specific mountpoint
mountPoint string
serial string
// Space to allocate or free
space int64
}
// DeviceResult contains details about the executed action
type DeviceResult struct {
success bool
message string
err error
}
// DevMan contains the necessary components for interacting with the device manager goroutine
type DevMan struct {
commands chan DeviceCommand
results chan DeviceResult
lock sync.Mutex
}
// RunManager should be used in a goroutine, and is responsible for managing available device space for file backups
// A MutEx should be used to maintain one-to-one command -> result behavior
func RunManager(db *sql.DB, commands <-chan DeviceCommand, results chan<- DeviceResult) {
devices := getDevices(db)
go func() {
process(&devices, db, commands, results)
}()
}
func process(devices *[]*device.Device, db *sql.DB, commands <-chan DeviceCommand, results chan<- DeviceResult) {
for command := range commands {
handle(command, devices, db, commands, results)
}
}
var handle = func(command DeviceCommand, devices *[]*device.Device, db *sql.DB, commands <-chan DeviceCommand, results chan<- DeviceResult) {
defer func() {
if r := recover(); r != nil {
// Ignore errors, since need to keep processing requests
fmt.Println("Recovered. Error:\n", r)
// Since only called when command received, ensure we inform the caller there was an error
results <- DeviceResult{false, "", fmt.Errorf("Panic during execution")}
}
}()
switch command.command {
case DevCommandAddDevice:
if len(command.mountPoint) == 0 {
results <- DeviceResult{false, "", fmt.Errorf("Mountpoint required")}
break
}
device, err := addDevice(command, db)
if err == nil {
*devices = append(*devices, &device)
results <- DeviceResult{true, "Device added successfully", nil}
} else {
results <- DeviceResult{false, "", err}
}
case DevCommandReserveSpace:
mount, err := reserveSpace(command, devices)
if err != nil {
results <- DeviceResult{false, "", err}
} else {
results <- DeviceResult{true, mount, nil}
}
case DevCommandFreeSpace:
err := freeSpace(command, devices)
if err != nil {
results <- DeviceResult{false, "", err}
} else {
results <- DeviceResult{true, "Space freed", nil}
}
default:
results <- DeviceResult{false, "", fmt.Errorf("%d at path %s is not a recognized command", command.command, command.mountPoint)}
}
}
// addDevice wraps functionality to add a new device, returning the result
var addDevice = func(command DeviceCommand, db *sql.DB) (device.Device, error) {
toAdd, err := makeDevice(0, command.mountPoint, command.serial)
if err != nil {
return device.Device{}, err
}
addedDev, err := addDBDevice(db, toAdd)
if err != nil {
return device.Device{}, err
}
return addedDev, nil
}
// reserveSpace attempts to allocate space on
var reserveSpace = func(command DeviceCommand, devices *[]*device.Device) (string, error) {
if len(*devices) == 0 {
return "", fmt.Errorf("No devices available -- add one first")
}
for _, dev := range *devices {
// If requested size is negative, then would be less than an int64 representation of remaining space anyways
if len(command.mountPoint) > 0 && command.mountPoint == dev.MountPoint {
if dev.RemainingSpace() > uint64(command.space) {
dev.ReserveSpace(command.space)
return dev.MountPoint, nil
}
return "", fmt.Errorf("Insufficient space on requested device")
// Check device space
} else if len(command.mountPoint) == 0 && dev.RemainingSpace() > uint64(command.space) {
dev.ReserveSpace(command.space)
return dev.MountPoint, nil
}
}
return "", fmt.Errorf("No device with sufficient space -- add another or make space")
}
var freeSpace = func(command DeviceCommand, devices *[]*device.Device) error {
if len(command.mountPoint) == 0 {
return fmt.Errorf("Mountpoint required")
}
var selected *device.Device = &device.Device{}
for _, dev := range *devices {
if dev.MountPoint == command.mountPoint {
selected = dev
break
}
}
if selected.DeviceID == 0 {
return fmt.Errorf("No such mountpoint")
}
selected.ReserveSpace(-1 * command.space)
return nil
}