// Copyright 2015 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package disk import ( "io/ioutil" "net" "os" "path/filepath" "strings" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend" "runtime" ) const lastIPFilePrefix = "last_reserved_ip." const LineBreak = "\r\n" var defaultDataDir = "/var/lib/cni/networks" // Store is a simple disk-backed store that creates one file per IP // address in a given directory. The contents of the file are the container ID. type Store struct { *FileLock dataDir string } // Store implements the Store interface var _ backend.Store = &Store{} func New(network, dataDir string) (*Store, error) { if dataDir == "" { dataDir = defaultDataDir } dir := filepath.Join(dataDir, network) if err := os.MkdirAll(dir, 0755); err != nil { return nil, err } lk, err := NewFileLock(dir) if err != nil { return nil, err } return &Store{lk, dir}, nil } func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) { fname := GetEscapedPath(s.dataDir, ip.String()) f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644) if os.IsExist(err) { return false, nil } if err != nil { return false, err } if _, err := f.WriteString(strings.TrimSpace(id) + LineBreak + ifname); err != nil { f.Close() os.Remove(f.Name()) return false, err } if err := f.Close(); err != nil { os.Remove(f.Name()) return false, err } // store the reserved ip in lastIPFile ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID) err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644) if err != nil { return false, err } return true, nil } // LastReservedIP returns the last reserved IP if exists func (s *Store) LastReservedIP(rangeID string) (net.IP, error) { ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID) data, err := ioutil.ReadFile(ipfile) if err != nil { return nil, err } return net.ParseIP(string(data)), nil } func (s *Store) Release(ip net.IP) error { return os.Remove(GetEscapedPath(s.dataDir, ip.String())) } func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) { found := false err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } data, err := ioutil.ReadFile(path) if err != nil { return nil } if strings.TrimSpace(string(data)) == match { if err := os.Remove(path); err != nil { return nil } found = true } return nil }) return found, err } // N.B. This function eats errors to be tolerant and // release as much as possible func (s *Store) ReleaseByID(id string, ifname string) error { found := false match := strings.TrimSpace(id) + LineBreak + ifname found, err := s.ReleaseByKey(id, ifname, match) // For backwards compatibility, look for files written by a previous version if !found && err == nil { match := strings.TrimSpace(id) found, err = s.ReleaseByKey(id, ifname, match) } return err } func GetEscapedPath(dataDir string, fname string) string { if runtime.GOOS == "windows" { fname = strings.Replace(fname, ":", "_", -1) } return filepath.Join(dataDir, fname) }