github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/application/deploy_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application_test
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/errors"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/charm.v6"
    14  	"gopkg.in/juju/charmrepo.v3"
    15  	"gopkg.in/juju/environschema.v1"
    16  
    17  	"github.com/juju/juju/apiserver/facades/client/application"
    18  	coreapplication "github.com/juju/juju/core/application"
    19  	"github.com/juju/juju/core/constraints"
    20  	"github.com/juju/juju/core/instance"
    21  	"github.com/juju/juju/juju/testing"
    22  	"github.com/juju/juju/state"
    23  	"github.com/juju/juju/testcharms"
    24  )
    25  
    26  // DeployLocalSuite uses a fresh copy of the same local dummy charm for each
    27  // test, because DeployApplication demands that a charm already exists in state,
    28  // and that's is the simplest way to get one in there.
    29  type DeployLocalSuite struct {
    30  	testing.JujuConnSuite
    31  	repo  charmrepo.Interface
    32  	charm *state.Charm
    33  }
    34  
    35  var _ = gc.Suite(&DeployLocalSuite{})
    36  
    37  func (s *DeployLocalSuite) SetUpSuite(c *gc.C) {
    38  	s.JujuConnSuite.SetUpSuite(c)
    39  	s.repo = &charmrepo.LocalRepository{Path: testcharms.Repo.Path()}
    40  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
    41  }
    42  
    43  func (s *DeployLocalSuite) SetUpTest(c *gc.C) {
    44  	s.JujuConnSuite.SetUpTest(c)
    45  	curl := charm.MustParseURL("local:quantal/dummy")
    46  	charm, err := testing.PutCharm(s.State, curl, s.repo, false, false)
    47  	c.Assert(err, jc.ErrorIsNil)
    48  	s.charm = charm
    49  }
    50  
    51  func (s *DeployLocalSuite) TestDeployMinimal(c *gc.C) {
    52  	app, err := application.DeployApplication(stateDeployer{s.State},
    53  		application.DeployApplicationParams{
    54  			ApplicationName: "bob",
    55  			Charm:           s.charm,
    56  		})
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.assertCharm(c, app, s.charm.URL())
    59  	s.assertSettings(c, app, charm.Settings{})
    60  	s.assertApplicationConfig(c, app, coreapplication.ConfigAttributes(nil))
    61  	s.assertConstraints(c, app, constraints.Value{})
    62  	s.assertMachines(c, app, constraints.Value{})
    63  }
    64  
    65  func (s *DeployLocalSuite) TestDeploySeries(c *gc.C) {
    66  	var f fakeDeployer
    67  
    68  	_, err := application.DeployApplication(&f,
    69  		application.DeployApplicationParams{
    70  			ApplicationName: "bob",
    71  			Charm:           s.charm,
    72  			Series:          "aseries",
    73  		})
    74  	c.Assert(err, jc.ErrorIsNil)
    75  
    76  	c.Assert(f.args.Name, gc.Equals, "bob")
    77  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
    78  	c.Assert(f.args.Series, gc.Equals, "aseries")
    79  }
    80  
    81  func (s *DeployLocalSuite) TestDeployWithImplicitBindings(c *gc.C) {
    82  	wordpressCharm := s.addWordpressCharmWithExtraBindings(c)
    83  
    84  	app, err := application.DeployApplication(stateDeployer{s.State},
    85  		application.DeployApplicationParams{
    86  			ApplicationName:  "bob",
    87  			Charm:            wordpressCharm,
    88  			EndpointBindings: nil,
    89  		})
    90  	c.Assert(err, jc.ErrorIsNil)
    91  
    92  	s.assertBindings(c, app, map[string]string{
    93  		// relation names
    94  		"url":             "",
    95  		"logging-dir":     "",
    96  		"monitoring-port": "",
    97  		"db":              "",
    98  		"cache":           "",
    99  		"cluster":         "",
   100  		// extra-bindings names
   101  		"db-client": "",
   102  		"admin-api": "",
   103  		"foo-bar":   "",
   104  	})
   105  }
   106  
   107  func (s *DeployLocalSuite) addWordpressCharm(c *gc.C) *state.Charm {
   108  	wordpressCharmURL := charm.MustParseURL("local:quantal/wordpress")
   109  	return s.addWordpressCharmFromURL(c, wordpressCharmURL)
   110  }
   111  
   112  func (s *DeployLocalSuite) addWordpressCharmWithExtraBindings(c *gc.C) *state.Charm {
   113  	wordpressCharmURL := charm.MustParseURL("local:quantal/wordpress-extra-bindings")
   114  	return s.addWordpressCharmFromURL(c, wordpressCharmURL)
   115  }
   116  
   117  func (s *DeployLocalSuite) addWordpressCharmFromURL(c *gc.C, charmURL *charm.URL) *state.Charm {
   118  	wordpressCharm, err := testing.PutCharm(s.State, charmURL, s.repo, false, false)
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	return wordpressCharm
   121  }
   122  
   123  func (s *DeployLocalSuite) assertBindings(c *gc.C, app application.Application, expected map[string]string) {
   124  	type withEndpointBindings interface {
   125  		EndpointBindings() (map[string]string, error)
   126  	}
   127  	bindings, err := app.(withEndpointBindings).EndpointBindings()
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	c.Assert(bindings, jc.DeepEquals, expected)
   130  }
   131  
   132  func (s *DeployLocalSuite) TestDeployWithSomeSpecifiedBindings(c *gc.C) {
   133  	wordpressCharm := s.addWordpressCharm(c)
   134  	_, err := s.State.AddSpace("db", "", nil, false)
   135  	c.Assert(err, jc.ErrorIsNil)
   136  	_, err = s.State.AddSpace("public", "", nil, false)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  
   139  	app, err := application.DeployApplication(stateDeployer{s.State},
   140  		application.DeployApplicationParams{
   141  			ApplicationName: "bob",
   142  			Charm:           wordpressCharm,
   143  			EndpointBindings: map[string]string{
   144  				"":   "public",
   145  				"db": "db",
   146  			},
   147  		})
   148  	c.Assert(err, jc.ErrorIsNil)
   149  
   150  	s.assertBindings(c, app, map[string]string{
   151  		// default binding
   152  		"": "public",
   153  		// relation names
   154  		"url":             "public",
   155  		"logging-dir":     "public",
   156  		"monitoring-port": "public",
   157  		"db":              "db",
   158  		"cache":           "public",
   159  		// extra-bindings names
   160  		"db-client": "public",
   161  		"admin-api": "public",
   162  		"foo-bar":   "public",
   163  	})
   164  }
   165  
   166  func (s *DeployLocalSuite) TestDeployWithBoundRelationNamesAndExtraBindingsNames(c *gc.C) {
   167  	wordpressCharm := s.addWordpressCharmWithExtraBindings(c)
   168  	_, err := s.State.AddSpace("db", "", nil, false)
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	_, err = s.State.AddSpace("public", "", nil, false)
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	_, err = s.State.AddSpace("internal", "", nil, false)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  
   175  	app, err := application.DeployApplication(stateDeployer{s.State},
   176  		application.DeployApplicationParams{
   177  			ApplicationName: "bob",
   178  			Charm:           wordpressCharm,
   179  			EndpointBindings: map[string]string{
   180  				"":          "public",
   181  				"db":        "db",
   182  				"db-client": "db",
   183  				"admin-api": "internal",
   184  			},
   185  		})
   186  	c.Assert(err, jc.ErrorIsNil)
   187  
   188  	s.assertBindings(c, app, map[string]string{
   189  		"":                "public",
   190  		"url":             "public",
   191  		"logging-dir":     "public",
   192  		"monitoring-port": "public",
   193  		"db":              "db",
   194  		"cache":           "public",
   195  		"db-client":       "db",
   196  		"admin-api":       "internal",
   197  		"cluster":         "public",
   198  		"foo-bar":         "public", // like for relations, uses the application-default.
   199  	})
   200  
   201  }
   202  
   203  func (s *DeployLocalSuite) TestDeployWithInvalidSpace(c *gc.C) {
   204  	wordpressCharm := s.addWordpressCharm(c)
   205  	_, err := s.State.AddSpace("db", "", nil, false)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	_, err = s.State.AddSpace("public", "", nil, false)
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	_, err = s.State.AddSpace("internal", "", nil, false)
   210  	c.Assert(err, jc.ErrorIsNil)
   211  
   212  	app, err := application.DeployApplication(stateDeployer{s.State},
   213  		application.DeployApplicationParams{
   214  			ApplicationName: "bob",
   215  			Charm:           wordpressCharm,
   216  			EndpointBindings: map[string]string{
   217  				"":   "public",
   218  				"db": "intranel", //typo 'intranel'
   219  			},
   220  		})
   221  	c.Assert(err, gc.ErrorMatches, `cannot add application "bob": unknown space "intranel" not valid`)
   222  	c.Check(app, gc.IsNil)
   223  	// The application should not have been added
   224  	_, err = s.State.Application("bob")
   225  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   226  
   227  }
   228  
   229  func (s *DeployLocalSuite) TestDeployWithInvalidBinding(c *gc.C) {
   230  	wordpressCharm := s.addWordpressCharm(c)
   231  	_, err := s.State.AddSpace("db", "", nil, false)
   232  	c.Assert(err, jc.ErrorIsNil)
   233  	_, err = s.State.AddSpace("public", "", nil, false)
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	_, err = s.State.AddSpace("internal", "", nil, false)
   236  	c.Assert(err, jc.ErrorIsNil)
   237  
   238  	app, err := application.DeployApplication(stateDeployer{s.State},
   239  		application.DeployApplicationParams{
   240  			ApplicationName: "bob",
   241  			Charm:           wordpressCharm,
   242  			EndpointBindings: map[string]string{
   243  				"":      "public",
   244  				"bd":    "internal", // typo 'bd'
   245  				"pubic": "public",   // typo 'pubic'
   246  			},
   247  		})
   248  	c.Assert(err, gc.ErrorMatches,
   249  		`invalid binding\(s\) supplied "bd", "pubic", valid binding names are "admin-api", .* "url"`)
   250  	c.Check(app, gc.IsNil)
   251  	// The application should not have been added
   252  	_, err = s.State.Application("bob")
   253  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   254  
   255  }
   256  
   257  func (s *DeployLocalSuite) TestDeployResources(c *gc.C) {
   258  	var f fakeDeployer
   259  
   260  	_, err := application.DeployApplication(&f,
   261  		application.DeployApplicationParams{
   262  			ApplicationName: "bob",
   263  			Charm:           s.charm,
   264  			EndpointBindings: map[string]string{
   265  				"": "public",
   266  			},
   267  			Resources: map[string]string{"foo": "bar"},
   268  		})
   269  	c.Assert(err, jc.ErrorIsNil)
   270  
   271  	c.Assert(f.args.Name, gc.Equals, "bob")
   272  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
   273  	c.Assert(f.args.Resources, gc.DeepEquals, map[string]string{"foo": "bar"})
   274  }
   275  
   276  func (s *DeployLocalSuite) TestDeploySettings(c *gc.C) {
   277  	app, err := application.DeployApplication(stateDeployer{s.State},
   278  		application.DeployApplicationParams{
   279  			ApplicationName: "bob",
   280  			Charm:           s.charm,
   281  			CharmConfig: charm.Settings{
   282  				"title":       "banana cupcakes",
   283  				"skill-level": 9901,
   284  			},
   285  		})
   286  	c.Assert(err, jc.ErrorIsNil)
   287  	s.assertSettings(c, app, charm.Settings{
   288  		"title":       "banana cupcakes",
   289  		"skill-level": int64(9901),
   290  	})
   291  }
   292  
   293  func (s *DeployLocalSuite) TestDeploySettingsError(c *gc.C) {
   294  	_, err := application.DeployApplication(stateDeployer{s.State},
   295  		application.DeployApplicationParams{
   296  			ApplicationName: "bob",
   297  			Charm:           s.charm,
   298  			CharmConfig: charm.Settings{
   299  				"skill-level": 99.01,
   300  			},
   301  		})
   302  	c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got 99.01`)
   303  	_, err = s.State.Application("bob")
   304  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   305  }
   306  
   307  func sampleApplicationConfigSchema() environschema.Fields {
   308  	schema := environschema.Fields{
   309  		"title":       environschema.Attr{Type: environschema.Tstring},
   310  		"outlook":     environschema.Attr{Type: environschema.Tstring},
   311  		"username":    environschema.Attr{Type: environschema.Tstring},
   312  		"skill-level": environschema.Attr{Type: environschema.Tint},
   313  	}
   314  	return schema
   315  }
   316  
   317  func (s *DeployLocalSuite) TestDeployWithApplicationConfig(c *gc.C) {
   318  	cfg, err := coreapplication.NewConfig(map[string]interface{}{
   319  		"outlook":     "good",
   320  		"skill-level": 1,
   321  	}, sampleApplicationConfigSchema(), nil)
   322  	app, err := application.DeployApplication(stateDeployer{s.State},
   323  		application.DeployApplicationParams{
   324  			ApplicationName:   "bob",
   325  			Charm:             s.charm,
   326  			ApplicationConfig: cfg,
   327  		})
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	s.assertApplicationConfig(c, app, coreapplication.ConfigAttributes{
   330  		"outlook":     "good",
   331  		"skill-level": 1,
   332  	})
   333  }
   334  
   335  func (s *DeployLocalSuite) TestDeployConstraints(c *gc.C) {
   336  	err := s.State.SetModelConstraints(constraints.MustParse("mem=2G"))
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	applicationCons := constraints.MustParse("cores=2")
   339  	app, err := application.DeployApplication(stateDeployer{s.State},
   340  		application.DeployApplicationParams{
   341  			ApplicationName: "bob",
   342  			Charm:           s.charm,
   343  			Constraints:     applicationCons,
   344  		})
   345  	c.Assert(err, jc.ErrorIsNil)
   346  	s.assertConstraints(c, app, applicationCons)
   347  }
   348  
   349  func (s *DeployLocalSuite) TestDeployNumUnits(c *gc.C) {
   350  	var f fakeDeployer
   351  
   352  	applicationCons := constraints.MustParse("cores=2")
   353  	_, err := application.DeployApplication(&f,
   354  		application.DeployApplicationParams{
   355  			ApplicationName: "bob",
   356  			Charm:           s.charm,
   357  			Constraints:     applicationCons,
   358  			NumUnits:        2,
   359  		})
   360  	c.Assert(err, jc.ErrorIsNil)
   361  
   362  	c.Assert(f.args.Name, gc.Equals, "bob")
   363  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
   364  	c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons)
   365  	c.Assert(f.args.NumUnits, gc.Equals, 2)
   366  }
   367  
   368  func (s *DeployLocalSuite) TestDeployForceMachineId(c *gc.C) {
   369  	var f fakeDeployer
   370  
   371  	applicationCons := constraints.MustParse("cores=2")
   372  	_, err := application.DeployApplication(&f,
   373  		application.DeployApplicationParams{
   374  			ApplicationName: "bob",
   375  			Charm:           s.charm,
   376  			Constraints:     applicationCons,
   377  			NumUnits:        1,
   378  			Placement:       []*instance.Placement{instance.MustParsePlacement("0")},
   379  		})
   380  	c.Assert(err, jc.ErrorIsNil)
   381  
   382  	c.Assert(f.args.Name, gc.Equals, "bob")
   383  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
   384  	c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons)
   385  	c.Assert(f.args.NumUnits, gc.Equals, 1)
   386  	c.Assert(f.args.Placement, gc.HasLen, 1)
   387  	c.Assert(*f.args.Placement[0], gc.Equals, instance.Placement{Scope: instance.MachineScope, Directive: "0"})
   388  }
   389  
   390  func (s *DeployLocalSuite) TestDeployForceMachineIdWithContainer(c *gc.C) {
   391  	var f fakeDeployer
   392  
   393  	applicationCons := constraints.MustParse("cores=2")
   394  	_, err := application.DeployApplication(&f,
   395  		application.DeployApplicationParams{
   396  			ApplicationName: "bob",
   397  			Charm:           s.charm,
   398  			Constraints:     applicationCons,
   399  			NumUnits:        1,
   400  			Placement:       []*instance.Placement{instance.MustParsePlacement(fmt.Sprintf("%s:0", instance.LXD))},
   401  		})
   402  	c.Assert(err, jc.ErrorIsNil)
   403  	c.Assert(f.args.Name, gc.Equals, "bob")
   404  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
   405  	c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons)
   406  	c.Assert(f.args.NumUnits, gc.Equals, 1)
   407  	c.Assert(f.args.Placement, gc.HasLen, 1)
   408  	c.Assert(*f.args.Placement[0], gc.Equals, instance.Placement{Scope: string(instance.LXD), Directive: "0"})
   409  }
   410  
   411  func (s *DeployLocalSuite) TestDeploy(c *gc.C) {
   412  	var f fakeDeployer
   413  
   414  	applicationCons := constraints.MustParse("cores=2")
   415  	placement := []*instance.Placement{
   416  		{Scope: s.State.ModelUUID(), Directive: "valid"},
   417  		{Scope: "#", Directive: "0"},
   418  		{Scope: "lxd", Directive: "1"},
   419  		{Scope: "lxd", Directive: ""},
   420  	}
   421  	_, err := application.DeployApplication(&f,
   422  		application.DeployApplicationParams{
   423  			ApplicationName: "bob",
   424  			Charm:           s.charm,
   425  			Constraints:     applicationCons,
   426  			NumUnits:        4,
   427  			Placement:       placement,
   428  		})
   429  	c.Assert(err, jc.ErrorIsNil)
   430  
   431  	c.Assert(f.args.Name, gc.Equals, "bob")
   432  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
   433  	c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons)
   434  	c.Assert(f.args.NumUnits, gc.Equals, 4)
   435  	c.Assert(f.args.Placement, gc.DeepEquals, placement)
   436  }
   437  
   438  func (s *DeployLocalSuite) TestDeployWithFewerPlacement(c *gc.C) {
   439  	var f fakeDeployer
   440  	applicationCons := constraints.MustParse("cores=2")
   441  	placement := []*instance.Placement{{Scope: s.State.ModelUUID(), Directive: "valid"}}
   442  	_, err := application.DeployApplication(&f,
   443  		application.DeployApplicationParams{
   444  			ApplicationName: "bob",
   445  			Charm:           s.charm,
   446  			Constraints:     applicationCons,
   447  			NumUnits:        3,
   448  			Placement:       placement,
   449  		})
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	c.Assert(f.args.Name, gc.Equals, "bob")
   452  	c.Assert(f.args.Charm, gc.DeepEquals, s.charm)
   453  	c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons)
   454  	c.Assert(f.args.NumUnits, gc.Equals, 3)
   455  	c.Assert(f.args.Placement, gc.DeepEquals, placement)
   456  }
   457  
   458  func (s *DeployLocalSuite) assertCharm(c *gc.C, app application.Application, expect *charm.URL) {
   459  	curl, force := app.CharmURL()
   460  	c.Assert(curl, gc.DeepEquals, expect)
   461  	c.Assert(force, jc.IsFalse)
   462  }
   463  
   464  func (s *DeployLocalSuite) assertSettings(c *gc.C, app application.Application, settings charm.Settings) {
   465  	settings, err := app.CharmConfig()
   466  	c.Assert(err, jc.ErrorIsNil)
   467  	expected := s.charm.Config().DefaultSettings()
   468  	for name, value := range settings {
   469  		expected[name] = value
   470  	}
   471  	c.Assert(settings, gc.DeepEquals, expected)
   472  }
   473  
   474  func (s *DeployLocalSuite) assertApplicationConfig(c *gc.C, app application.Application, wantCfg coreapplication.ConfigAttributes) {
   475  	cfg, err := app.ApplicationConfig()
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	c.Assert(cfg, gc.DeepEquals, wantCfg)
   478  }
   479  
   480  func (s *DeployLocalSuite) assertConstraints(c *gc.C, app application.Application, expect constraints.Value) {
   481  	cons, err := app.Constraints()
   482  	c.Assert(err, jc.ErrorIsNil)
   483  	c.Assert(cons, gc.DeepEquals, expect)
   484  }
   485  
   486  func (s *DeployLocalSuite) assertMachines(c *gc.C, app application.Application, expectCons constraints.Value, expectIds ...string) {
   487  	type withAssignedMachineId interface {
   488  		AssignedMachineId() (string, error)
   489  	}
   490  
   491  	units, err := app.AllUnits()
   492  	c.Assert(err, jc.ErrorIsNil)
   493  	c.Assert(units, gc.HasLen, len(expectIds))
   494  	// first manually tell state to assign all the units
   495  	for _, unit := range units {
   496  		id := unit.UnitTag().Id()
   497  		res, err := s.State.AssignStagedUnits([]string{id})
   498  		c.Assert(err, jc.ErrorIsNil)
   499  		c.Assert(res[0].Error, jc.ErrorIsNil)
   500  		c.Assert(res[0].Unit, gc.Equals, id)
   501  	}
   502  
   503  	// refresh the list of units from state
   504  	units, err = app.AllUnits()
   505  	c.Assert(err, jc.ErrorIsNil)
   506  	c.Assert(units, gc.HasLen, len(expectIds))
   507  	unseenIds := set.NewStrings(expectIds...)
   508  	for _, unit := range units {
   509  		id, err := unit.(withAssignedMachineId).AssignedMachineId()
   510  		c.Assert(err, jc.ErrorIsNil)
   511  		unseenIds.Remove(id)
   512  		machine, err := s.State.Machine(id)
   513  		c.Assert(err, jc.ErrorIsNil)
   514  		cons, err := machine.Constraints()
   515  		c.Assert(err, jc.ErrorIsNil)
   516  		c.Assert(cons, gc.DeepEquals, expectCons)
   517  	}
   518  	c.Assert(unseenIds, gc.DeepEquals, set.NewStrings())
   519  }
   520  
   521  type stateDeployer struct {
   522  	*state.State
   523  }
   524  
   525  func (d stateDeployer) AddApplication(args state.AddApplicationArgs) (application.Application, error) {
   526  	app, err := d.State.AddApplication(args)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  	return application.NewStateApplication(d.State, app), nil
   531  }
   532  
   533  type fakeDeployer struct {
   534  	args state.AddApplicationArgs
   535  }
   536  
   537  func (f *fakeDeployer) AddApplication(args state.AddApplicationArgs) (application.Application, error) {
   538  	f.args = args
   539  	return nil, nil
   540  }