github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/configstore/disk_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package configstore_test 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strings" 13 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils/fslock" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/environs/configstore" 21 "github.com/juju/juju/testing" 22 ) 23 24 var _ = gc.Suite(&diskInterfaceSuite{}) 25 26 type diskInterfaceSuite struct { 27 interfaceSuite 28 dir string 29 } 30 31 func (s *diskInterfaceSuite) SetUpTest(c *gc.C) { 32 s.interfaceSuite.SetUpTest(c) 33 s.dir = c.MkDir() 34 s.NewStore = func(c *gc.C) configstore.Storage { 35 store, err := configstore.NewDisk(s.dir) 36 c.Assert(err, jc.ErrorIsNil) 37 return store 38 } 39 } 40 41 // storePath returns the path to the environment info 42 // for the named environment in the given directory. 43 // If envName is empty, it returns the path 44 // to the info files' containing directory. 45 func storePath(dir string, envName string) string { 46 path := filepath.Join(dir, "environments") 47 if envName != "" { 48 path = filepath.Join(path, envName+".jenv") 49 } 50 return path 51 } 52 53 func (s *diskInterfaceSuite) TearDownTest(c *gc.C) { 54 s.NewStore = nil 55 // Check that no stray temp files have been left behind 56 entries, err := ioutil.ReadDir(storePath(s.dir, "")) 57 c.Assert(err, jc.ErrorIsNil) 58 for _, entry := range entries { 59 if !strings.HasSuffix(entry.Name(), ".jenv") && entry.Name() != "cache.yaml" { 60 c.Errorf("found possible stray temp file %s, %q", s.dir, entry.Name()) 61 } 62 } 63 s.interfaceSuite.TearDownTest(c) 64 } 65 66 var _ = gc.Suite(&diskStoreSuite{}) 67 68 type diskStoreSuite struct { 69 testing.BaseSuite 70 } 71 72 func (*diskStoreSuite) TestNewDisk(c *gc.C) { 73 dir := c.MkDir() 74 store, err := configstore.NewDisk(filepath.Join(dir, "foo")) 75 c.Assert(err, jc.Satisfies, os.IsNotExist) 76 c.Assert(store, gc.IsNil) 77 78 store, err = configstore.NewDisk(filepath.Join(dir)) 79 c.Assert(err, jc.ErrorIsNil) 80 c.Assert(store, gc.NotNil) 81 } 82 83 var sampleInfo = ` 84 user: rog 85 password: guessit 86 state-servers: 87 - 10.0.0.1 88 - 127.0.0.1 89 server-hostnames: 90 - example.com 91 - kremvax.ru 92 ca-cert: 'first line 93 94 second line' 95 bootstrap-config: 96 secret: blah 97 arble: bletch 98 `[1:] 99 100 func (*diskStoreSuite) TestRead(c *gc.C) { 101 dir := c.MkDir() 102 err := os.Mkdir(storePath(dir, ""), 0700) 103 c.Assert(err, jc.ErrorIsNil) 104 err = ioutil.WriteFile(storePath(dir, "someenv"), []byte(sampleInfo), 0666) 105 c.Assert(err, jc.ErrorIsNil) 106 store, err := configstore.NewDisk(dir) 107 c.Assert(err, jc.ErrorIsNil) 108 info, err := store.ReadInfo("someenv") 109 c.Assert(err, jc.ErrorIsNil) 110 c.Assert(info.Initialized(), jc.IsTrue) 111 c.Assert(info.APICredentials(), gc.DeepEquals, configstore.APICredentials{ 112 User: "rog", 113 Password: "guessit", 114 }) 115 c.Assert(info.APIEndpoint(), gc.DeepEquals, configstore.APIEndpoint{ 116 Addresses: []string{"10.0.0.1", "127.0.0.1"}, 117 Hostnames: []string{"example.com", "kremvax.ru"}, 118 CACert: "first line\nsecond line", 119 }) 120 c.Assert(info.Location(), gc.Equals, fmt.Sprintf("file %q", filepath.Join(dir, "environments", "someenv.jenv"))) 121 c.Assert(info.BootstrapConfig(), gc.DeepEquals, map[string]interface{}{ 122 "secret": "blah", 123 "arble": "bletch", 124 }) 125 } 126 127 func (*diskStoreSuite) TestReadNotFound(c *gc.C) { 128 dir := c.MkDir() 129 store, err := configstore.NewDisk(dir) 130 c.Assert(err, jc.ErrorIsNil) 131 info, err := store.ReadInfo("someenv") 132 c.Assert(err, jc.Satisfies, errors.IsNotFound) 133 c.Assert(info, gc.IsNil) 134 } 135 136 func (*diskStoreSuite) TestWriteFails(c *gc.C) { 137 dir := c.MkDir() 138 store, err := configstore.NewDisk(dir) 139 c.Assert(err, jc.ErrorIsNil) 140 141 info := store.CreateInfo("someenv") 142 143 // Make the directory non-writable 144 err = os.Chmod(storePath(dir, ""), 0555) 145 c.Assert(err, jc.ErrorIsNil) 146 147 // Cannot use permissions properly on windows for now 148 if runtime.GOOS != "windows" { 149 err = info.Write() 150 c.Assert(err, gc.ErrorMatches, ".* permission denied") 151 } 152 153 // Make the directory writable again so that gocheck can clean it up. 154 err = os.Chmod(storePath(dir, ""), 0777) 155 c.Assert(err, jc.ErrorIsNil) 156 } 157 158 func (*diskStoreSuite) TestRenameFails(c *gc.C) { 159 if runtime.GOOS == "windows" { 160 c.Skip("issue 1403084: the way the error is checked doesn't work on windows") 161 } 162 dir := c.MkDir() 163 store, err := configstore.NewDisk(dir) 164 c.Assert(err, jc.ErrorIsNil) 165 166 // Replace the file by an directory which can't be renamed over. 167 path := storePath(dir, "someenv") 168 err = os.Mkdir(path, 0777) 169 c.Assert(err, jc.ErrorIsNil) 170 171 info := store.CreateInfo("someenv") 172 err = info.Write() 173 c.Assert(err, gc.ErrorMatches, "environment info already exists") 174 } 175 176 func (*diskStoreSuite) TestDestroyRemovesFiles(c *gc.C) { 177 dir := c.MkDir() 178 store, err := configstore.NewDisk(dir) 179 c.Assert(err, jc.ErrorIsNil) 180 181 info := store.CreateInfo("someenv") 182 err = info.Write() 183 c.Assert(err, jc.ErrorIsNil) 184 185 _, err = os.Stat(storePath(dir, "someenv")) 186 c.Assert(err, jc.ErrorIsNil) 187 188 err = info.Destroy() 189 c.Assert(err, jc.ErrorIsNil) 190 191 _, err = os.Stat(storePath(dir, "someenv")) 192 c.Assert(err, jc.Satisfies, os.IsNotExist) 193 194 err = info.Destroy() 195 c.Assert(err, gc.ErrorMatches, "environment info has already been removed") 196 } 197 198 func (*diskStoreSuite) TestWriteSmallerFile(c *gc.C) { 199 dir := c.MkDir() 200 store, err := configstore.NewDisk(dir) 201 c.Assert(err, jc.ErrorIsNil) 202 info := store.CreateInfo("someenv") 203 endpoint := configstore.APIEndpoint{ 204 Addresses: []string{"this", "is", "never", "validated", "here"}, 205 Hostnames: []string{"neither", "is", "this"}, 206 EnvironUUID: testing.EnvironmentTag.Id(), 207 } 208 info.SetAPIEndpoint(endpoint) 209 err = info.Write() 210 c.Assert(err, jc.ErrorIsNil) 211 212 newInfo, err := store.ReadInfo("someenv") 213 c.Assert(err, jc.ErrorIsNil) 214 // Now change the number of addresses to be shorter. 215 endpoint.Addresses = []string{"just one"} 216 endpoint.Hostnames = []string{"just this"} 217 newInfo.SetAPIEndpoint(endpoint) 218 err = newInfo.Write() 219 c.Assert(err, jc.ErrorIsNil) 220 221 // We should be able to read in in fine. 222 yaInfo, err := store.ReadInfo("someenv") 223 c.Assert(err, jc.ErrorIsNil) 224 c.Assert(yaInfo.APIEndpoint().Addresses, gc.DeepEquals, []string{"just one"}) 225 c.Assert(yaInfo.APIEndpoint().Hostnames, gc.DeepEquals, []string{"just this"}) 226 } 227 228 func (*diskStoreSuite) TestConcurrentAccess(c *gc.C) { 229 var tw loggo.TestWriter 230 c.Assert(loggo.RegisterWriter("test-log", &tw, loggo.DEBUG), gc.IsNil) 231 232 dir := c.MkDir() 233 store, err := configstore.NewDisk(dir) 234 c.Assert(err, jc.ErrorIsNil) 235 236 envDir := storePath(dir, "") 237 lock, err := configstore.AcquireEnvironmentLock(envDir, "blocking-op") 238 c.Assert(err, jc.ErrorIsNil) 239 defer lock.Unlock() 240 241 _, err = store.ReadInfo("someenv") 242 c.Assert(errors.Cause(err), gc.Equals, fslock.ErrTimeout) 243 244 // Using . between environments and env.lock so we don't have to care 245 // about forward vs. backwards slash separator. 246 messages := []jc.SimpleMessage{ 247 {loggo.WARNING, `configstore lock held, lock dir: .*environments.env\.lock`}, 248 {loggo.WARNING, `lock holder message: pid: \d+, operation: blocking-op`}, 249 } 250 251 c.Check(tw.Log(), jc.LogMatches, messages) 252 }