github.com/google/cadvisor@v0.49.1/devicemapper/thin_pool_watcher.go (about) 1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package devicemapper 16 17 import ( 18 "fmt" 19 "strings" 20 "sync" 21 "time" 22 23 "k8s.io/klog/v2" 24 ) 25 26 // ThinPoolWatcher maintains a cache of device name -> usage stats for a 27 // devicemapper thin-pool using thin_ls. 28 type ThinPoolWatcher struct { 29 poolName string 30 metadataDevice string 31 lock *sync.RWMutex 32 cache map[string]uint64 33 period time.Duration 34 stopChan chan struct{} 35 dmsetup DmsetupClient 36 thinLsClient thinLsClient 37 } 38 39 // NewThinPoolWatcher returns a new ThinPoolWatcher for the given devicemapper 40 // thin pool name and metadata device or an error. 41 func NewThinPoolWatcher(poolName, metadataDevice string) (*ThinPoolWatcher, error) { 42 thinLsClient, err := newThinLsClient() 43 if err != nil { 44 return nil, fmt.Errorf("encountered error creating thin_ls client: %v", err) 45 } 46 47 return &ThinPoolWatcher{poolName: poolName, 48 metadataDevice: metadataDevice, 49 lock: &sync.RWMutex{}, 50 cache: make(map[string]uint64), 51 period: 15 * time.Second, 52 stopChan: make(chan struct{}), 53 dmsetup: NewDmsetupClient(), 54 thinLsClient: thinLsClient, 55 }, nil 56 } 57 58 // Start starts the ThinPoolWatcher. 59 func (w *ThinPoolWatcher) Start() { 60 err := w.Refresh() 61 if err != nil { 62 klog.Errorf("encountered error refreshing thin pool watcher: %v", err) 63 } 64 65 for { 66 select { 67 case <-w.stopChan: 68 return 69 case <-time.After(w.period): 70 start := time.Now() 71 err = w.Refresh() 72 if err != nil { 73 klog.Errorf("encountered error refreshing thin pool watcher: %v", err) 74 } 75 76 // print latency for refresh 77 duration := time.Since(start) 78 klog.V(5).Infof("thin_ls(%d) took %s", start.Unix(), duration) 79 } 80 } 81 } 82 83 // Stop stops the ThinPoolWatcher. 84 func (w *ThinPoolWatcher) Stop() { 85 close(w.stopChan) 86 } 87 88 // GetUsage gets the cached usage value of the given device. 89 func (w *ThinPoolWatcher) GetUsage(deviceID string) (uint64, error) { 90 w.lock.RLock() 91 defer w.lock.RUnlock() 92 93 v, ok := w.cache[deviceID] 94 if !ok { 95 return 0, fmt.Errorf("no cached value for usage of device %v", deviceID) 96 } 97 98 return v, nil 99 } 100 101 const ( 102 reserveMetadataMessage = "reserve_metadata_snap" 103 releaseMetadataMessage = "release_metadata_snap" 104 ) 105 106 // Refresh performs a `thin_ls` of the pool being watched and refreshes the 107 // cached data with the result. 108 func (w *ThinPoolWatcher) Refresh() error { 109 w.lock.Lock() 110 defer w.lock.Unlock() 111 112 currentlyReserved, err := w.checkReservation(w.poolName) 113 if err != nil { 114 err = fmt.Errorf("error determining whether snapshot is reserved: %v", err) 115 return err 116 } 117 118 if currentlyReserved { 119 klog.V(5).Infof("metadata for %v is currently reserved; releasing", w.poolName) 120 _, err = w.dmsetup.Message(w.poolName, 0, releaseMetadataMessage) 121 if err != nil { 122 err = fmt.Errorf("error releasing metadata snapshot for %v: %v", w.poolName, err) 123 return err 124 } 125 } 126 127 klog.V(5).Infof("reserving metadata snapshot for thin-pool %v", w.poolName) 128 // NOTE: "0" in the call below is for the 'sector' argument to 'dmsetup 129 // message'. It's not needed for thin pools. 130 if output, err := w.dmsetup.Message(w.poolName, 0, reserveMetadataMessage); err != nil { 131 err = fmt.Errorf("error reserving metadata for thin-pool %v: %v output: %v", w.poolName, err, string(output)) 132 return err 133 } 134 klog.V(5).Infof("reserved metadata snapshot for thin-pool %v", w.poolName) 135 136 defer func() { 137 klog.V(5).Infof("releasing metadata snapshot for thin-pool %v", w.poolName) 138 _, err := w.dmsetup.Message(w.poolName, 0, releaseMetadataMessage) 139 if err != nil { 140 klog.Warningf("Unable to release metadata snapshot for thin-pool %v: %s", w.poolName, err) 141 } 142 }() 143 144 klog.V(5).Infof("running thin_ls on metadata device %v", w.metadataDevice) 145 newCache, err := w.thinLsClient.ThinLs(w.metadataDevice) 146 if err != nil { 147 err = fmt.Errorf("error performing thin_ls on metadata device %v: %v", w.metadataDevice, err) 148 return err 149 } 150 151 w.cache = newCache 152 return nil 153 } 154 155 const ( 156 thinPoolDmsetupStatusHeldMetadataRoot = 6 157 thinPoolDmsetupStatusMinFields = thinPoolDmsetupStatusHeldMetadataRoot + 1 158 ) 159 160 // checkReservation checks to see whether the thin device is currently holding 161 // userspace metadata. 162 func (w *ThinPoolWatcher) checkReservation(poolName string) (bool, error) { 163 klog.V(5).Infof("checking whether the thin-pool is holding a metadata snapshot") 164 output, err := w.dmsetup.Status(poolName) 165 if err != nil { 166 return false, err 167 } 168 169 // we care about the field at fields[thinPoolDmsetupStatusHeldMetadataRoot], 170 // so make sure we get enough fields 171 fields := strings.Fields(string(output)) 172 if len(fields) < thinPoolDmsetupStatusMinFields { 173 return false, fmt.Errorf("unexpected output of dmsetup status command; expected at least %d fields, got %v; output: %v", thinPoolDmsetupStatusMinFields, len(fields), string(output)) 174 } 175 176 heldMetadataRoot := fields[thinPoolDmsetupStatusHeldMetadataRoot] 177 currentlyReserved := heldMetadataRoot != "-" 178 return currentlyReserved, nil 179 }