github.com/google/cadvisor@v0.49.1/container/raw/watcher.go (about) 1 // Copyright 2014 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 container defines types for sub-container events and also 16 // defines an interface for container operation handlers. 17 package raw 18 19 import ( 20 "fmt" 21 "os" 22 "path" 23 "strings" 24 25 inotify "k8s.io/utils/inotify" 26 27 "github.com/google/cadvisor/container" 28 "github.com/google/cadvisor/container/common" 29 "github.com/google/cadvisor/container/libcontainer" 30 "github.com/google/cadvisor/watcher" 31 32 "k8s.io/klog/v2" 33 ) 34 35 type rawContainerWatcher struct { 36 // Absolute path to the root of the cgroup hierarchies 37 cgroupPaths map[string]string 38 39 // Inotify event watcher. 40 watcher *common.InotifyWatcher 41 42 // Signal for watcher thread to stop. 43 stopWatcher chan error 44 } 45 46 func NewRawContainerWatcher(includedMetrics container.MetricSet) (watcher.ContainerWatcher, error) { 47 cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics) 48 if err != nil { 49 return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err) 50 } 51 if len(cgroupSubsystems) == 0 { 52 return nil, fmt.Errorf("failed to find supported cgroup mounts for the raw factory") 53 } 54 55 watcher, err := common.NewInotifyWatcher() 56 if err != nil { 57 return nil, err 58 } 59 60 rawWatcher := &rawContainerWatcher{ 61 cgroupPaths: cgroupSubsystems, 62 watcher: watcher, 63 stopWatcher: make(chan error), 64 } 65 66 return rawWatcher, nil 67 } 68 69 func (w *rawContainerWatcher) Start(events chan watcher.ContainerEvent) error { 70 // Watch this container (all its cgroups) and all subdirectories. 71 watched := make([]string, 0) 72 for _, cgroupPath := range w.cgroupPaths { 73 _, err := w.watchDirectory(events, cgroupPath, "/") 74 if err != nil { 75 for _, watchedCgroupPath := range watched { 76 _, removeErr := w.watcher.RemoveWatch("/", watchedCgroupPath) 77 if removeErr != nil { 78 klog.Warningf("Failed to remove inotify watch for %q with error: %v", watchedCgroupPath, removeErr) 79 } 80 } 81 return err 82 } 83 watched = append(watched, cgroupPath) 84 } 85 86 // Process the events received from the kernel. 87 go func() { 88 for { 89 select { 90 case event := <-w.watcher.Event(): 91 err := w.processEvent(event, events) 92 if err != nil { 93 klog.Warningf("Error while processing event (%+v): %v", event, err) 94 } 95 case err := <-w.watcher.Error(): 96 klog.Warningf("Error while watching %q: %v", "/", err) 97 case <-w.stopWatcher: 98 err := w.watcher.Close() 99 if err == nil { 100 w.stopWatcher <- err 101 return 102 } 103 } 104 } 105 }() 106 107 return nil 108 } 109 110 func (w *rawContainerWatcher) Stop() error { 111 // Rendezvous with the watcher thread. 112 w.stopWatcher <- nil 113 return <-w.stopWatcher 114 } 115 116 // Watches the specified directory and all subdirectories. Returns whether the path was 117 // already being watched and an error (if any). 118 func (w *rawContainerWatcher) watchDirectory(events chan watcher.ContainerEvent, dir string, containerName string) (bool, error) { 119 // Don't watch .mount cgroups because they never have containers as sub-cgroups. A single container 120 // can have many .mount cgroups associated with it which can quickly exhaust the inotify watches on a node. 121 if strings.HasSuffix(containerName, ".mount") { 122 return false, nil 123 } 124 alreadyWatching, err := w.watcher.AddWatch(containerName, dir) 125 if err != nil { 126 return alreadyWatching, err 127 } 128 129 // Remove the watch if further operations failed. 130 cleanup := true 131 defer func() { 132 if cleanup { 133 _, err := w.watcher.RemoveWatch(containerName, dir) 134 if err != nil { 135 klog.Warningf("Failed to remove inotify watch for %q: %v", dir, err) 136 } 137 } 138 }() 139 140 // TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime. 141 // Watch subdirectories as well. 142 entries, err := os.ReadDir(dir) 143 if err != nil { 144 return alreadyWatching, err 145 } 146 for _, entry := range entries { 147 if entry.IsDir() { 148 entryPath := path.Join(dir, entry.Name()) 149 subcontainerName := path.Join(containerName, entry.Name()) 150 alreadyWatchingSubDir, err := w.watchDirectory(events, entryPath, subcontainerName) 151 if err != nil { 152 klog.Errorf("Failed to watch directory %q: %v", entryPath, err) 153 if os.IsNotExist(err) { 154 // The directory may have been removed before watching. Try to watch the other 155 // subdirectories. (https://github.com/kubernetes/kubernetes/issues/28997) 156 continue 157 } 158 return alreadyWatching, err 159 } 160 // since we already missed the creation event for this directory, publish an event here. 161 if !alreadyWatchingSubDir { 162 go func() { 163 events <- watcher.ContainerEvent{ 164 EventType: watcher.ContainerAdd, 165 Name: subcontainerName, 166 WatchSource: watcher.Raw, 167 } 168 }() 169 } 170 } 171 } 172 173 cleanup = false 174 return alreadyWatching, nil 175 } 176 177 func (w *rawContainerWatcher) processEvent(event *inotify.Event, events chan watcher.ContainerEvent) error { 178 // Convert the inotify event type to a container create or delete. 179 var eventType watcher.ContainerEventType 180 switch { 181 case (event.Mask & inotify.InCreate) > 0: 182 eventType = watcher.ContainerAdd 183 case (event.Mask & inotify.InDelete) > 0: 184 eventType = watcher.ContainerDelete 185 case (event.Mask & inotify.InMovedFrom) > 0: 186 eventType = watcher.ContainerDelete 187 case (event.Mask & inotify.InMovedTo) > 0: 188 eventType = watcher.ContainerAdd 189 default: 190 // Ignore other events. 191 return nil 192 } 193 194 // Derive the container name from the path name. 195 var containerName string 196 for _, mount := range w.cgroupPaths { 197 mountLocation := path.Clean(mount) + "/" 198 if strings.HasPrefix(event.Name, mountLocation) { 199 containerName = event.Name[len(mountLocation)-1:] 200 break 201 } 202 } 203 if containerName == "" { 204 return fmt.Errorf("unable to detect container from watch event on directory %q", event.Name) 205 } 206 207 // Maintain the watch for the new or deleted container. 208 switch eventType { 209 case watcher.ContainerAdd: 210 // New container was created, watch it. 211 alreadyWatched, err := w.watchDirectory(events, event.Name, containerName) 212 if err != nil { 213 return err 214 } 215 216 // Only report container creation once. 217 if alreadyWatched { 218 return nil 219 } 220 case watcher.ContainerDelete: 221 // Container was deleted, stop watching for it. 222 lastWatched, err := w.watcher.RemoveWatch(containerName, event.Name) 223 if err != nil { 224 return err 225 } 226 227 // Only report container deletion once. 228 if !lastWatched { 229 return nil 230 } 231 default: 232 return fmt.Errorf("unknown event type %v", eventType) 233 } 234 235 // Deliver the event. 236 events <- watcher.ContainerEvent{ 237 EventType: eventType, 238 Name: containerName, 239 WatchSource: watcher.Raw, 240 } 241 242 return nil 243 }