Skip to content

Commit cbac83c

Browse files
authored
Merge pull request #77 from klihub/transient-specs
pkg/cdi: add functions for generating Spec names and removing Spec files.
2 parents 5609688 + a5fbc2f commit cbac83c

File tree

6 files changed

+485
-33
lines changed

6 files changed

+485
-33
lines changed

pkg/cdi/cache.go

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@
1717
package cdi
1818

1919
import (
20+
"io/fs"
21+
"os"
2022
"path/filepath"
2123
"sort"
2224
"strings"
2325
"sync"
2426

27+
stderr "errors"
28+
2529
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
2630
"github.com/fsnotify/fsnotify"
2731
"github.com/hashicorp/go-multierror"
@@ -255,11 +259,22 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e
255259
return nil, nil
256260
}
257261

258-
// WriteSpec writes a Spec file with the given content. Priority is used
259-
// as an index into the list of Spec directories to pick a directory for
260-
// the file, adjusting for any under- or overflows. If name has a "json"
261-
// or "yaml" extension it choses the encoding. Otherwise JSON encoding
262-
// is used with a "json" extension.
262+
// highestPrioritySpecDir returns the Spec directory with highest priority
263+
// and its priority.
264+
func (c *Cache) highestPrioritySpecDir() (string, int) {
265+
if len(c.specDirs) == 0 {
266+
return "", -1
267+
}
268+
269+
prio := len(c.specDirs) - 1
270+
dir := c.specDirs[prio]
271+
272+
return dir, prio
273+
}
274+
275+
// WriteSpec writes a Spec file with the given content into the highest
276+
// priority Spec directory. If name has a "json" or "yaml" extension it
277+
// choses the encoding. Otherwise the default YAML encoding is used.
263278
func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error {
264279
var (
265280
specDir string
@@ -269,23 +284,51 @@ func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error {
269284
err error
270285
)
271286

272-
if len(c.specDirs) == 0 {
287+
specDir, prio = c.highestPrioritySpecDir()
288+
if specDir == "" {
273289
return errors.New("no Spec directories to write to")
274290
}
275291

276-
prio = len(c.specDirs) - 1
277-
specDir = c.specDirs[prio]
278292
path = filepath.Join(specDir, name)
279293
if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
280-
path += ".json"
294+
path += defaultSpecExt
281295
}
282296

283-
spec, err = NewSpec(raw, path, prio)
297+
spec, err = newSpec(raw, path, prio)
284298
if err != nil {
285299
return err
286300
}
287301

288-
return spec.Write(true)
302+
return spec.write(true)
303+
}
304+
305+
// RemoveSpec removes a Spec with the given name from the highest
306+
// priority Spec directory. This function can be used to remove a
307+
// Spec previously written by WriteSpec(). If the file exists and
308+
// its removal fails RemoveSpec returns an error.
309+
func (c *Cache) RemoveSpec(name string) error {
310+
var (
311+
specDir string
312+
path string
313+
err error
314+
)
315+
316+
specDir, _ = c.highestPrioritySpecDir()
317+
if specDir == "" {
318+
return errors.New("no Spec directories to remove from")
319+
}
320+
321+
path = filepath.Join(specDir, name)
322+
if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
323+
path += defaultSpecExt
324+
}
325+
326+
err = os.Remove(path)
327+
if err != nil && stderr.Is(err, fs.ErrNotExist) {
328+
err = nil
329+
}
330+
331+
return err
289332
}
290333

291334
// GetDevice returns the cached device for the given qualified name.

pkg/cdi/cache_test.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package cdi
1818

1919
import (
20+
"fmt"
2021
"os"
2122
"path/filepath"
23+
"strconv"
2224
"strings"
2325
"sync"
2426
"testing"
@@ -29,6 +31,7 @@ import (
2931
oci "github.com/opencontainers/runtime-spec/specs-go"
3032
"github.com/pkg/errors"
3133
"github.com/stretchr/testify/require"
34+
"sigs.k8s.io/yaml"
3235
)
3336

3437
func TestNewCache(t *testing.T) {
@@ -1633,6 +1636,215 @@ containerEdits:
16331636
}
16341637
}
16351638

1639+
func TestCacheTransientSpecs(t *testing.T) {
1640+
type testCase struct {
1641+
name string
1642+
specs []string
1643+
invalid map[int]bool
1644+
expected [][]string
1645+
numSpecFiles []int
1646+
}
1647+
for _, tc := range []*testCase{
1648+
{
1649+
name: "invalid spec",
1650+
specs: []string{
1651+
`
1652+
cdiVersion: "` + cdi.CurrentVersion + `"
1653+
kind: "vendor.comdevice"
1654+
devices:
1655+
- name: "dev1"
1656+
containerEdits:
1657+
deviceNodes:
1658+
- path: "/dev/vendor1-dev1"
1659+
type: b
1660+
major: 10
1661+
minor: 1`,
1662+
},
1663+
invalid: map[int]bool{
1664+
0: true,
1665+
},
1666+
},
1667+
{
1668+
name: "add/remove one valid spec",
1669+
specs: []string{
1670+
`
1671+
cdiVersion: "` + cdi.CurrentVersion + `"
1672+
kind: "vendor.com/device"
1673+
devices:
1674+
- name: "dev1"
1675+
containerEdits:
1676+
deviceNodes:
1677+
- path: "/dev/vendor-dev1"
1678+
type: b
1679+
major: 10
1680+
minor: 1
1681+
`,
1682+
"-0",
1683+
},
1684+
expected: [][]string{
1685+
[]string{
1686+
"vendor.com/device=dev1",
1687+
},
1688+
nil,
1689+
},
1690+
numSpecFiles: []int{
1691+
1,
1692+
0,
1693+
},
1694+
},
1695+
{
1696+
name: "add/remove multiple valid specs",
1697+
specs: []string{
1698+
`
1699+
cdiVersion: "` + cdi.CurrentVersion + `"
1700+
kind: "vendor.com/device"
1701+
devices:
1702+
- name: "dev1"
1703+
containerEdits:
1704+
deviceNodes:
1705+
- path: "/dev/vendor-dev1"
1706+
type: b
1707+
major: 10
1708+
minor: 1
1709+
`,
1710+
`
1711+
cdiVersion: "` + cdi.CurrentVersion + `"
1712+
kind: "vendor.com/device"
1713+
devices:
1714+
- name: "dev2"
1715+
containerEdits:
1716+
deviceNodes:
1717+
- path: "/dev/vendor-dev2"
1718+
type: b
1719+
major: 10
1720+
minor: 2
1721+
`,
1722+
`
1723+
cdiVersion: "` + cdi.CurrentVersion + `"
1724+
kind: "vendor.com/device"
1725+
devices:
1726+
- name: "dev3"
1727+
containerEdits:
1728+
deviceNodes:
1729+
- path: "/dev/vendor-dev3"
1730+
type: b
1731+
major: 10
1732+
minor: 3
1733+
- name: "dev4"
1734+
containerEdits:
1735+
deviceNodes:
1736+
- path: "/dev/vendor-dev4"
1737+
type: b
1738+
major: 10
1739+
minor: 4
1740+
`,
1741+
"-0",
1742+
"-1",
1743+
"-2",
1744+
},
1745+
expected: [][]string{
1746+
[]string{
1747+
"vendor.com/device=dev1",
1748+
},
1749+
[]string{
1750+
"vendor.com/device=dev1",
1751+
"vendor.com/device=dev2",
1752+
},
1753+
[]string{
1754+
"vendor.com/device=dev1",
1755+
"vendor.com/device=dev2",
1756+
"vendor.com/device=dev3",
1757+
"vendor.com/device=dev4",
1758+
},
1759+
[]string{
1760+
"vendor.com/device=dev2",
1761+
"vendor.com/device=dev3",
1762+
"vendor.com/device=dev4",
1763+
},
1764+
[]string{
1765+
"vendor.com/device=dev3",
1766+
"vendor.com/device=dev4",
1767+
},
1768+
nil,
1769+
},
1770+
numSpecFiles: []int{
1771+
1,
1772+
2,
1773+
3,
1774+
2,
1775+
1,
1776+
0,
1777+
},
1778+
},
1779+
} {
1780+
t.Run(tc.name, func(t *testing.T) {
1781+
var (
1782+
dir string
1783+
err error
1784+
cache *Cache
1785+
specFiles []os.DirEntry
1786+
specs = map[int]string{}
1787+
)
1788+
1789+
dir, err = createSpecDirs(t, nil, nil)
1790+
require.NoError(t, err)
1791+
cache, err = NewCache(
1792+
WithSpecDirs(
1793+
filepath.Join(dir, "etc"),
1794+
filepath.Join(dir, "run"),
1795+
),
1796+
WithAutoRefresh(false),
1797+
)
1798+
1799+
require.NoError(t, err)
1800+
require.NotNil(t, cache)
1801+
1802+
for idx, data := range tc.specs {
1803+
var (
1804+
transientID string
1805+
raw *cdi.Spec
1806+
delIdx int
1807+
err error
1808+
)
1809+
1810+
if data[0] == '-' {
1811+
delIdx, err = strconv.Atoi(string(data[1:]))
1812+
require.NoError(t, err)
1813+
1814+
err = cache.RemoveSpec(specs[delIdx])
1815+
require.NoError(t, err)
1816+
} else {
1817+
err = yaml.Unmarshal([]byte(data), &raw)
1818+
require.NoError(t, err)
1819+
1820+
transientID = fmt.Sprintf("id%d", idx)
1821+
specs[idx], err = GenerateNameForTransientSpec(raw, transientID)
1822+
if tc.invalid[idx] {
1823+
require.NotNil(t, err)
1824+
continue
1825+
}
1826+
require.NoError(t, err)
1827+
1828+
err = cache.WriteSpec(raw, specs[idx])
1829+
require.NoError(t, err)
1830+
}
1831+
1832+
err = cache.Refresh()
1833+
require.NoError(t, err)
1834+
1835+
devices := cache.ListDevices()
1836+
require.Equal(t, tc.expected[idx], devices)
1837+
1838+
specFiles, err = os.ReadDir(
1839+
filepath.Join(dir, "run"),
1840+
)
1841+
require.NoError(t, err)
1842+
require.Equal(t, tc.numSpecFiles[idx], len(specFiles))
1843+
}
1844+
})
1845+
}
1846+
}
1847+
16361848
// Create and populate automatically cleaned up spec directories.
16371849
func createSpecDirs(t *testing.T, etc, run map[string]string) (string, error) {
16381850
return mkTestDir(t, map[string]map[string]string{

0 commit comments

Comments
 (0)