github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/environs/sync/sync_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package sync_test
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime"
    16  	"sort"
    17  	"testing"
    18  
    19  	"github.com/juju/errors"
    20  	gitjujutesting "github.com/juju/testing"
    21  	jc "github.com/juju/testing/checkers"
    22  	"github.com/juju/utils"
    23  	"github.com/juju/utils/series"
    24  	gc "gopkg.in/check.v1"
    25  
    26  	"github.com/juju/juju/environs"
    27  	"github.com/juju/juju/environs/filestorage"
    28  	"github.com/juju/juju/environs/simplestreams"
    29  	"github.com/juju/juju/environs/storage"
    30  	"github.com/juju/juju/environs/sync"
    31  	envtesting "github.com/juju/juju/environs/testing"
    32  	envtools "github.com/juju/juju/environs/tools"
    33  	toolstesting "github.com/juju/juju/environs/tools/testing"
    34  	coretesting "github.com/juju/juju/testing"
    35  	coretools "github.com/juju/juju/tools"
    36  	"github.com/juju/juju/version"
    37  )
    38  
    39  func TestPackage(t *testing.T) {
    40  	gc.TestingT(t)
    41  }
    42  
    43  type syncSuite struct {
    44  	coretesting.FakeJujuHomeSuite
    45  	envtesting.ToolsFixture
    46  	storage      storage.Storage
    47  	localStorage string
    48  }
    49  
    50  var _ = gc.Suite(&syncSuite{})
    51  var _ = gc.Suite(&uploadSuite{})
    52  var _ = gc.Suite(&badBuildSuite{})
    53  
    54  func (s *syncSuite) setUpTest(c *gc.C) {
    55  	if runtime.GOOS == "windows" {
    56  		c.Skip("issue 1403084: Currently does not work because of jujud problems")
    57  	}
    58  	s.FakeJujuHomeSuite.SetUpTest(c)
    59  	s.ToolsFixture.SetUpTest(c)
    60  
    61  	// It's important that this be v1.8.x to match the test data.
    62  	s.PatchValue(&version.Current.Number, version.MustParse("1.8.3"))
    63  
    64  	// Create a source storage.
    65  	baseDir := c.MkDir()
    66  	stor, err := filestorage.NewFileStorageWriter(baseDir)
    67  	c.Assert(err, jc.ErrorIsNil)
    68  	s.storage = stor
    69  
    70  	// Create a local tools directory.
    71  	s.localStorage = c.MkDir()
    72  
    73  	// Populate both local and default tools locations with the public tools.
    74  	versionStrings := make([]string, len(vAll))
    75  	for i, vers := range vAll {
    76  		versionStrings[i] = vers.String()
    77  	}
    78  	toolstesting.MakeTools(c, baseDir, "released", versionStrings)
    79  	toolstesting.MakeTools(c, s.localStorage, "released", versionStrings)
    80  
    81  	// Switch the default tools location.
    82  	baseURL, err := s.storage.URL(storage.BaseToolsPath)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	s.PatchValue(&envtools.DefaultBaseURL, baseURL)
    85  }
    86  
    87  func (s *syncSuite) tearDownTest(c *gc.C) {
    88  	s.ToolsFixture.TearDownTest(c)
    89  	s.FakeJujuHomeSuite.TearDownTest(c)
    90  }
    91  
    92  var tests = []struct {
    93  	description string
    94  	ctx         *sync.SyncContext
    95  	source      bool
    96  	tools       []version.Binary
    97  	version     version.Number
    98  	major       int
    99  	minor       int
   100  }{
   101  	{
   102  		description: "copy newest from the filesystem",
   103  		ctx:         &sync.SyncContext{},
   104  		source:      true,
   105  		tools:       v180all,
   106  	},
   107  	{
   108  		description: "copy newest from the dummy environment",
   109  		ctx:         &sync.SyncContext{},
   110  		tools:       v180all,
   111  	},
   112  	{
   113  		description: "copy matching dev from the dummy environment",
   114  		ctx:         &sync.SyncContext{},
   115  		version:     version.MustParse("1.9.3"),
   116  		tools:       v190all,
   117  	},
   118  	{
   119  		description: "copy matching major, minor from the dummy environment",
   120  		ctx:         &sync.SyncContext{},
   121  		major:       3,
   122  		minor:       2,
   123  		tools:       []version.Binary{v320p64},
   124  	},
   125  	{
   126  		description: "copy matching major, minor dev from the dummy environment",
   127  		ctx:         &sync.SyncContext{},
   128  		major:       3,
   129  		minor:       1,
   130  		tools:       []version.Binary{v310p64},
   131  	},
   132  	{
   133  		description: "copy all from the dummy environment",
   134  		ctx: &sync.SyncContext{
   135  			AllVersions: true,
   136  		},
   137  		tools: v1all,
   138  	},
   139  }
   140  
   141  func (s *syncSuite) TestSyncing(c *gc.C) {
   142  	for i, test := range tests {
   143  		// Perform all tests in a "clean" environment.
   144  		func() {
   145  			s.setUpTest(c)
   146  			defer s.tearDownTest(c)
   147  
   148  			c.Logf("test %d: %s", i, test.description)
   149  
   150  			if test.source {
   151  				test.ctx.Source = s.localStorage
   152  			}
   153  			if test.version != version.Zero {
   154  				version.Current.Number = test.version
   155  			}
   156  			if test.major > 0 {
   157  				test.ctx.MajorVersion = test.major
   158  				test.ctx.MinorVersion = test.minor
   159  			}
   160  			uploader := fakeToolsUploader{
   161  				uploaded: make(map[version.Binary]bool),
   162  			}
   163  			test.ctx.TargetToolsFinder = mockToolsFinder{}
   164  			test.ctx.TargetToolsUploader = &uploader
   165  
   166  			err := sync.SyncTools(test.ctx)
   167  			c.Assert(err, jc.ErrorIsNil)
   168  
   169  			var uploaded []version.Binary
   170  			for v := range uploader.uploaded {
   171  				uploaded = append(uploaded, v)
   172  			}
   173  			c.Assert(uploaded, jc.SameContents, test.tools)
   174  		}()
   175  	}
   176  }
   177  
   178  type fakeToolsUploader struct {
   179  	uploaded map[version.Binary]bool
   180  }
   181  
   182  func (u *fakeToolsUploader) UploadTools(toolsDir, stream string, tools *coretools.Tools, data []byte) error {
   183  	u.uploaded[tools.Version] = true
   184  	return nil
   185  }
   186  
   187  var (
   188  	v100p64 = version.MustParseBinary("1.0.0-precise-amd64")
   189  	v100q64 = version.MustParseBinary("1.0.0-quantal-amd64")
   190  	v100q32 = version.MustParseBinary("1.0.0-quantal-i386")
   191  	v100all = []version.Binary{v100p64, v100q64, v100q32}
   192  	v180q64 = version.MustParseBinary("1.8.0-quantal-amd64")
   193  	v180p32 = version.MustParseBinary("1.8.0-precise-i386")
   194  	v180all = []version.Binary{v180q64, v180p32}
   195  	v190q64 = version.MustParseBinary("1.9.0-quantal-amd64")
   196  	v190p32 = version.MustParseBinary("1.9.0-precise-i386")
   197  	v190all = []version.Binary{v190q64, v190p32}
   198  	v1all   = append(append(v100all, v180all...), v190all...)
   199  	v200p64 = version.MustParseBinary("2.0.0-precise-amd64")
   200  	v310p64 = version.MustParseBinary("3.1.0-precise-amd64")
   201  	v320p64 = version.MustParseBinary("3.2.0-precise-amd64")
   202  	vAll    = append(append(v1all, v200p64), v310p64, v320p64)
   203  )
   204  
   205  type uploadSuite struct {
   206  	env environs.Environ
   207  	coretesting.FakeJujuHomeSuite
   208  	envtesting.ToolsFixture
   209  	targetStorage storage.Storage
   210  }
   211  
   212  func (s *uploadSuite) SetUpTest(c *gc.C) {
   213  	if runtime.GOOS == "windows" {
   214  		c.Skip("issue 1403084: Currently does not work because of jujud problems")
   215  	}
   216  	s.FakeJujuHomeSuite.SetUpTest(c)
   217  	s.ToolsFixture.SetUpTest(c)
   218  
   219  	// Create a target storage.
   220  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	s.targetStorage = stor
   223  
   224  	// Mock out building of tools. Sync should not care about the contents
   225  	// of tools archives, other than that they hash correctly.
   226  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
   227  }
   228  
   229  func (s *uploadSuite) TearDownTest(c *gc.C) {
   230  	s.ToolsFixture.TearDownTest(c)
   231  	s.FakeJujuHomeSuite.TearDownTest(c)
   232  }
   233  
   234  func (s *uploadSuite) TestUpload(c *gc.C) {
   235  	t, err := sync.Upload(s.targetStorage, "released", nil)
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	c.Assert(t.Version, gc.Equals, version.Current)
   238  	c.Assert(t.URL, gc.Not(gc.Equals), "")
   239  	s.assertUploadedTools(c, t, []string{series.HostSeries()}, "released")
   240  }
   241  
   242  func (s *uploadSuite) TestUploadFakeSeries(c *gc.C) {
   243  	seriesToUpload := "precise"
   244  	if seriesToUpload == series.HostSeries() {
   245  		seriesToUpload = "raring"
   246  	}
   247  	t, err := sync.Upload(s.targetStorage, "released", nil, "quantal", seriesToUpload)
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "released")
   250  }
   251  
   252  func (s *uploadSuite) TestUploadAndForceVersion(c *gc.C) {
   253  	// This test actually tests three things:
   254  	//   the writing of the FORCE-VERSION file;
   255  	//   the reading of the FORCE-VERSION file by the version package;
   256  	//   and the reading of the version from jujud.
   257  	vers := version.Current
   258  	vers.Patch++
   259  	t, err := sync.Upload(s.targetStorage, "released", &vers.Number)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	c.Assert(t.Version, gc.Equals, vers)
   262  }
   263  
   264  func (s *uploadSuite) TestSyncTools(c *gc.C) {
   265  	builtTools, err := sync.BuildToolsTarball(nil, "released")
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools)
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	c.Assert(t.Version, gc.Equals, version.Current)
   270  	c.Assert(t.URL, gc.Not(gc.Equals), "")
   271  }
   272  
   273  func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) {
   274  	seriesToUpload := "precise"
   275  	if seriesToUpload == series.HostSeries() {
   276  		seriesToUpload = "raring"
   277  	}
   278  	builtTools, err := sync.BuildToolsTarball(nil, "testing")
   279  	c.Assert(err, jc.ErrorIsNil)
   280  
   281  	t, err := sync.SyncBuiltTools(s.targetStorage, "testing", builtTools, "quantal", seriesToUpload)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "testing")
   284  }
   285  
   286  func (s *uploadSuite) TestSyncAndForceVersion(c *gc.C) {
   287  	// This test actually tests three things:
   288  	//   the writing of the FORCE-VERSION file;
   289  	//   the reading of the FORCE-VERSION file by the version package;
   290  	//   and the reading of the version from jujud.
   291  	vers := version.Current
   292  	vers.Patch++
   293  	builtTools, err := sync.BuildToolsTarball(&vers.Number, "released")
   294  	c.Assert(err, jc.ErrorIsNil)
   295  	t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	c.Assert(t.Version, gc.Equals, vers)
   298  }
   299  
   300  func (s *uploadSuite) assertUploadedTools(c *gc.C, t *coretools.Tools, expectSeries []string, stream string) {
   301  	c.Assert(t.Version, gc.Equals, version.Current)
   302  	expectRaw := downloadToolsRaw(c, t)
   303  
   304  	list, err := envtools.ReadList(s.targetStorage, stream, version.Current.Major, version.Current.Minor)
   305  	c.Assert(err, jc.ErrorIsNil)
   306  	c.Assert(list.AllSeries(), jc.SameContents, expectSeries)
   307  	sort.Strings(expectSeries)
   308  	c.Assert(list.AllSeries(), gc.DeepEquals, expectSeries)
   309  	for _, t := range list {
   310  		c.Logf("checking %s", t.URL)
   311  		c.Assert(t.Version.Number, gc.Equals, version.Current.Number)
   312  		actualRaw := downloadToolsRaw(c, t)
   313  		c.Assert(string(actualRaw), gc.Equals, string(expectRaw))
   314  	}
   315  	metadata, err := envtools.ReadMetadata(s.targetStorage, stream)
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	c.Assert(metadata, gc.HasLen, 0)
   318  }
   319  
   320  // downloadToolsRaw downloads the supplied tools and returns the raw bytes.
   321  func downloadToolsRaw(c *gc.C, t *coretools.Tools) []byte {
   322  	resp, err := utils.GetValidatingHTTPClient().Get(t.URL)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	defer resp.Body.Close()
   325  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   326  	var buf bytes.Buffer
   327  	_, err = io.Copy(&buf, resp.Body)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	return buf.Bytes()
   330  }
   331  
   332  func bundleTools(c *gc.C) (version.Binary, string, error) {
   333  	f, err := ioutil.TempFile("", "juju-tgz")
   334  	c.Assert(err, jc.ErrorIsNil)
   335  	defer f.Close()
   336  	defer os.Remove(f.Name())
   337  
   338  	return envtools.BundleTools(f, &version.Current.Number)
   339  }
   340  
   341  type badBuildSuite struct {
   342  	env environs.Environ
   343  	gitjujutesting.LoggingSuite
   344  	gitjujutesting.CleanupSuite
   345  	envtesting.ToolsFixture
   346  }
   347  
   348  var badGo = `
   349  #!/bin/bash --norc
   350  exit 1
   351  `[1:]
   352  
   353  func (s *badBuildSuite) SetUpSuite(c *gc.C) {
   354  	if runtime.GOOS == "windows" {
   355  		c.Skip("issue 1403084: Currently does not work because of jujud problems")
   356  	}
   357  	s.CleanupSuite.SetUpSuite(c)
   358  	s.LoggingSuite.SetUpSuite(c)
   359  }
   360  
   361  func (s *badBuildSuite) TearDownSuite(c *gc.C) {
   362  	s.LoggingSuite.TearDownSuite(c)
   363  	s.CleanupSuite.TearDownSuite(c)
   364  }
   365  
   366  func (s *badBuildSuite) SetUpTest(c *gc.C) {
   367  	s.CleanupSuite.SetUpTest(c)
   368  	s.LoggingSuite.SetUpTest(c)
   369  	s.ToolsFixture.SetUpTest(c)
   370  
   371  	// Mock go cmd
   372  	testPath := c.MkDir()
   373  	s.PatchEnvPathPrepend(testPath)
   374  	path := filepath.Join(testPath, "go")
   375  	err := ioutil.WriteFile(path, []byte(badGo), 0755)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  
   378  	// Check mocked go cmd errors
   379  	out, err := exec.Command("go").CombinedOutput()
   380  	c.Assert(err, gc.ErrorMatches, "exit status 1")
   381  	c.Assert(string(out), gc.Equals, "")
   382  }
   383  
   384  func (s *badBuildSuite) TearDownTest(c *gc.C) {
   385  	s.ToolsFixture.TearDownTest(c)
   386  	s.LoggingSuite.TearDownTest(c)
   387  	s.CleanupSuite.TearDownTest(c)
   388  }
   389  
   390  func (s *badBuildSuite) TestBundleToolsBadBuild(c *gc.C) {
   391  	// Test that original bundleTools Func fails as expected
   392  	vers, sha256Hash, err := bundleTools(c)
   393  	c.Assert(vers, gc.DeepEquals, version.Binary{})
   394  	c.Assert(sha256Hash, gc.Equals, "")
   395  	c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `)
   396  
   397  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
   398  
   399  	// Test that BundleTools func passes after it is
   400  	// mocked out
   401  	vers, sha256Hash, err = bundleTools(c)
   402  	c.Assert(err, jc.ErrorIsNil)
   403  	c.Assert(vers.Number, gc.Equals, version.Current.Number)
   404  	c.Assert(sha256Hash, gc.Equals, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
   405  }
   406  
   407  func (s *badBuildSuite) TestUploadToolsBadBuild(c *gc.C) {
   408  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   409  	c.Assert(err, jc.ErrorIsNil)
   410  
   411  	// Test that original Upload Func fails as expected
   412  	t, err := sync.Upload(stor, "released", nil)
   413  	c.Assert(t, gc.IsNil)
   414  	c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `)
   415  
   416  	// Test that Upload func passes after BundleTools func is mocked out
   417  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
   418  	t, err = sync.Upload(stor, "released", nil)
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	c.Assert(t.Version, gc.Equals, version.Current)
   421  	c.Assert(t.URL, gc.Not(gc.Equals), "")
   422  }
   423  
   424  func (s *badBuildSuite) TestBuildToolsBadBuild(c *gc.C) {
   425  	// Test that original BuildToolsTarball fails
   426  	builtTools, err := sync.BuildToolsTarball(nil, "released")
   427  	c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `)
   428  	c.Assert(builtTools, gc.IsNil)
   429  
   430  	// Test that BuildToolsTarball func passes after BundleTools func is
   431  	// mocked out
   432  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
   433  	builtTools, err = sync.BuildToolsTarball(nil, "released")
   434  	c.Assert(builtTools.Version, gc.Equals, version.Current)
   435  	c.Assert(err, jc.ErrorIsNil)
   436  }
   437  
   438  func (s *uploadSuite) TestMockBundleTools(c *gc.C) {
   439  	var (
   440  		writer       io.Writer
   441  		forceVersion *version.Number
   442  		n            int
   443  		p            bytes.Buffer
   444  	)
   445  	p.WriteString("Hello World")
   446  
   447  	s.PatchValue(&envtools.BundleTools, func(writerArg io.Writer, forceVersionArg *version.Number) (vers version.Binary, sha256Hash string, err error) {
   448  		writer = writerArg
   449  		n, err = writer.Write(p.Bytes())
   450  		c.Assert(err, jc.ErrorIsNil)
   451  		forceVersion = forceVersionArg
   452  		return
   453  	})
   454  
   455  	_, err := sync.BuildToolsTarball(&version.Current.Number, "released")
   456  	c.Assert(err, jc.ErrorIsNil)
   457  	c.Assert(*forceVersion, gc.Equals, version.Current.Number)
   458  	c.Assert(writer, gc.NotNil)
   459  	c.Assert(n, gc.Equals, len(p.Bytes()))
   460  }
   461  
   462  func (s *uploadSuite) TestMockBuildTools(c *gc.C) {
   463  	s.PatchValue(&version.Current, version.MustParseBinary("1.9.1-trusty-amd64"))
   464  	buildToolsFunc := toolstesting.GetMockBuildTools(c)
   465  	builtTools, err := buildToolsFunc(nil, "released")
   466  	c.Assert(err, jc.ErrorIsNil)
   467  
   468  	builtTools.Dir = ""
   469  
   470  	expectedBuiltTools := &sync.BuiltTools{
   471  		StorageName: "name",
   472  		Version:     version.Current,
   473  		Size:        127,
   474  		Sha256Hash:  "6a19d08ca4913382ca86508aa38eb8ee5b9ae2d74333fe8d862c0f9e29b82c39",
   475  	}
   476  	c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools)
   477  
   478  	vers := version.MustParseBinary("1.5.3-trusty-amd64")
   479  	builtTools, err = buildToolsFunc(&vers.Number, "released")
   480  	c.Assert(err, jc.ErrorIsNil)
   481  	builtTools.Dir = ""
   482  	expectedBuiltTools = &sync.BuiltTools{
   483  		StorageName: "name",
   484  		Version:     vers,
   485  		Size:        127,
   486  		Sha256Hash:  "cad8ccedab8f26807ff379ddc2f2f78d9a7cac1276e001154cee5e39b9ddcc38",
   487  	}
   488  	c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools)
   489  }
   490  
   491  func (s *uploadSuite) TestStorageToolsUploaderWriteMirrors(c *gc.C) {
   492  	s.testStorageToolsUploaderWriteMirrors(c, envtools.WriteMirrors)
   493  }
   494  
   495  func (s *uploadSuite) TestStorageToolsUploaderDontWriteMirrors(c *gc.C) {
   496  	s.testStorageToolsUploaderWriteMirrors(c, envtools.DoNotWriteMirrors)
   497  }
   498  
   499  func (s *uploadSuite) testStorageToolsUploaderWriteMirrors(c *gc.C, writeMirrors envtools.ShouldWriteMirrors) {
   500  	storageDir := c.MkDir()
   501  	stor, err := filestorage.NewFileStorageWriter(storageDir)
   502  	c.Assert(err, jc.ErrorIsNil)
   503  
   504  	uploader := &sync.StorageToolsUploader{
   505  		Storage:       stor,
   506  		WriteMetadata: true,
   507  		WriteMirrors:  writeMirrors,
   508  	}
   509  
   510  	err = uploader.UploadTools(
   511  		"released",
   512  		"released",
   513  		&coretools.Tools{
   514  			Version: version.Current,
   515  			Size:    7,
   516  			SHA256:  "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73",
   517  		}, []byte("content"))
   518  	c.Assert(err, jc.ErrorIsNil)
   519  
   520  	mirrorsPath := simplestreams.MirrorsPath(envtools.StreamsVersionV1) + simplestreams.UnsignedSuffix
   521  	r, err := stor.Get(path.Join(storage.BaseToolsPath, mirrorsPath))
   522  	if writeMirrors == envtools.WriteMirrors {
   523  		c.Assert(err, jc.ErrorIsNil)
   524  		data, err := ioutil.ReadAll(r)
   525  		r.Close()
   526  		c.Assert(err, jc.ErrorIsNil)
   527  		c.Assert(string(data), jc.Contains, `"mirrors":`)
   528  	} else {
   529  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
   530  	}
   531  }
   532  
   533  type mockToolsFinder struct{}
   534  
   535  func (mockToolsFinder) FindTools(major int, stream string) (coretools.List, error) {
   536  	return nil, coretools.ErrNoMatches
   537  }