Skip to content

Commit

Permalink
Add Ping method to providers
Browse files Browse the repository at this point in the history
    In order to know if an endpoint is correct when adding a cloud,
    we need to be able to ping that provider to get an idea if it
    even exists.  This is long before bootstrap or even credentials,
    so we need a way to reasonably determine if a cloud of the
    correct type exists at the hostname/ip.  This is different per
    cloud, thus a per-provider endpoint.
  • Loading branch information
natefinch committed Dec 2, 2016
1 parent 98cc6c7 commit a185eca
Show file tree
Hide file tree
Showing 33 changed files with 468 additions and 113 deletions.
38 changes: 29 additions & 9 deletions cmd/juju/cloud/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ Examples:
See also:
clouds`

type addCloudCommand struct {
// AddCloudCommand is the command that allows you to add a cloud configuration
// for use with juju bootstrap.
type AddCloudCommand struct {
cmd.CommandBase

// Replace, if true, existing cloud information is overwritten.
Expand All @@ -66,17 +68,27 @@ type addCloudCommand struct {
CloudFile string

cloudMetadataStore CloudMetadataStore

// Ping contains the logic for pinging a cloud endpoint to know whether or
// not it really has a valid cloud of the same type as the provider. By
// default it just calls the correct provider's Ping method.
Ping func(p environs.EnvironProvider, endpoint string) error
}

// NewAddCloudCommand returns a command to add cloud information.
func NewAddCloudCommand(cloudMetadataStore CloudMetadataStore) cmd.Command {
return &addCloudCommand{
func NewAddCloudCommand(cloudMetadataStore CloudMetadataStore) *AddCloudCommand {
// Ping is provider.Ping except in tests where we don't actually want to
// require a valid cloud.
return &AddCloudCommand{
cloudMetadataStore: cloudMetadataStore,
Ping: func(p environs.EnvironProvider, endpoint string) error {
return p.Ping(endpoint)
},
}
}

// Info returns help information about the command.
func (c *addCloudCommand) Info() *cmd.Info {
func (c *AddCloudCommand) Info() *cmd.Info {
return &cmd.Info{
Name: "add-cloud",
Args: "<cloud name> <cloud definition file>",
Expand All @@ -86,13 +98,13 @@ func (c *addCloudCommand) Info() *cmd.Info {
}

// SetFlags initializes the flags supported by the command.
func (c *addCloudCommand) SetFlags(f *gnuflag.FlagSet) {
func (c *AddCloudCommand) SetFlags(f *gnuflag.FlagSet) {
c.CommandBase.SetFlags(f)
f.BoolVar(&c.Replace, "replace", false, "Overwrite any existing cloud information")
}

// Init populates the command with the args from the command line.
func (c *addCloudCommand) Init(args []string) (err error) {
func (c *AddCloudCommand) Init(args []string) (err error) {
if len(args) > 0 {
c.Cloud = args[0]
}
Expand All @@ -107,7 +119,7 @@ func (c *addCloudCommand) Init(args []string) (err error) {

// Run executes the add cloud command, adding a cloud based on a passed-in yaml
// file or interactive queries.
func (c *addCloudCommand) Run(ctxt *cmd.Context) error {
func (c *AddCloudCommand) Run(ctxt *cmd.Context) error {
if c.CloudFile == "" {
return c.runInteractive(ctxt)
}
Expand All @@ -130,7 +142,7 @@ func (c *addCloudCommand) Run(ctxt *cmd.Context) error {
return addCloud(c.cloudMetadataStore, c.Cloud, newCloud)
}

func (c *addCloudCommand) runInteractive(ctxt *cmd.Context) error {
func (c *AddCloudCommand) runInteractive(ctxt *cmd.Context) error {
errout := interact.NewErrWriter(ctxt.Stdout)
pollster := interact.New(ctxt.Stdin, ctxt.Stdout, errout)

Expand All @@ -149,6 +161,14 @@ func (c *addCloudCommand) runInteractive(ctxt *cmd.Context) error {
return errors.Trace(err)
}

pollster.VerifyURLs = func(s string) (ok bool, msg string, err error) {
err = c.Ping(provider, s)
if err != nil {
return false, "Can't validate endpoint: " + err.Error(), nil
}
return true, "", nil
}

v, err := pollster.QuerySchema(provider.CloudSchema())
if err != nil {
return errors.Trace(err)
Expand Down Expand Up @@ -262,7 +282,7 @@ func queryCloudType(pollster *interact.Pollster) (string, error) {
}, cloudVerify)
}

func (c *addCloudCommand) verifyName(name string) error {
func (c *AddCloudCommand) verifyName(name string) error {
if c.Replace {
return nil
}
Expand Down
19 changes: 19 additions & 0 deletions cmd/juju/cloud/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

cloudfile "github.com/juju/juju/cloud"
"github.com/juju/juju/cmd/juju/cloud"
"github.com/juju/juju/environs"
"github.com/juju/juju/testing"
)

Expand Down Expand Up @@ -259,6 +260,9 @@ func (*addSuite) TestInteractiveOpenstack(c *gc.C) {
numCallsToWrite := fake.Call("WritePersonalCloudMetadata", m1Metadata).Returns(nil)

command := cloud.NewAddCloudCommand(fake)
command.Ping = func(environs.EnvironProvider, string) error {
return nil
}
err := testing.InitCommand(command, nil)
c.Assert(err, jc.ErrorIsNil)

Expand Down Expand Up @@ -295,6 +299,9 @@ func (*addSuite) TestInteractiveMaas(c *gc.C) {
numCallsToWrite := fake.Call("WritePersonalCloudMetadata", m1Metadata).Returns(nil)

command := cloud.NewAddCloudCommand(fake)
command.Ping = func(environs.EnvironProvider, string) error {
return nil
}
err := testing.InitCommand(command, nil)
c.Assert(err, jc.ErrorIsNil)

Expand Down Expand Up @@ -323,6 +330,9 @@ func (*addSuite) TestInteractiveManual(c *gc.C) {
numCallsToWrite := fake.Call("WritePersonalCloudMetadata", manMetadata).Returns(nil)

command := cloud.NewAddCloudCommand(fake)
command.Ping = func(environs.EnvironProvider, string) error {
return nil
}
err := testing.InitCommand(command, nil)
c.Assert(err, jc.ErrorIsNil)

Expand Down Expand Up @@ -373,6 +383,9 @@ func (*addSuite) TestInteractiveVSphere(c *gc.C) {
numCallsToWrite := fake.Call("WritePersonalCloudMetadata", vsphereMetadata).Returns(nil)

command := cloud.NewAddCloudCommand(fake)
command.Ping = func(environs.EnvironProvider, string) error {
return nil
}
err := testing.InitCommand(command, nil)
c.Assert(err, jc.ErrorIsNil)

Expand Down Expand Up @@ -405,6 +418,9 @@ func (*addSuite) TestInteractiveExistingNameOverride(c *gc.C) {
numCallsToWrite := fake.Call("WritePersonalCloudMetadata", manMetadata).Returns(nil)

command := cloud.NewAddCloudCommand(fake)
command.Ping = func(environs.EnvironProvider, string) error {
return nil
}
err := testing.InitCommand(command, nil)
c.Assert(err, jc.ErrorIsNil)

Expand Down Expand Up @@ -441,6 +457,9 @@ func (*addSuite) TestInteractiveExistingNameNoOverride(c *gc.C) {
numCallsToWrite := fake.Call("WritePersonalCloudMetadata", compoundCloudMetadata).Returns(nil)

command := cloud.NewAddCloudCommand(fake)
command.Ping = func(environs.EnvironProvider, string) error {
return nil
}
err := testing.InitCommand(command, nil)
c.Assert(err, jc.ErrorIsNil)

Expand Down
13 changes: 9 additions & 4 deletions cmd/juju/interact/pollster.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import (
// Pollster is used to ask multiple questions of the user using a standard
// formatting.
type Pollster struct {
scanner *bufio.Scanner
out io.Writer
errOut io.Writer
VerifyURLs VerifyFunc
scanner *bufio.Scanner
out io.Writer
errOut io.Writer
}

// New returns a Pollster that wraps the given reader and writer.
Expand Down Expand Up @@ -393,7 +394,11 @@ func (p *Pollster) queryOneSchema(schema *jsonschema.Schema) (interface{}, error
// anything
a, err = p.Enter(schema.Singular)
case jsonschema.FormatURI:
a, err = p.EnterVerify(schema.Singular, uriVerify)
if p.VerifyURLs == nil {
a, err = p.EnterVerify(schema.Singular, uriVerify)
} else {
a, err = p.EnterVerify(schema.Singular, p.VerifyURLs)
}
default:
// TODO(natefinch): support more formats
return nil, errors.Errorf("unsupported format type: %q", schema.Format)
Expand Down
3 changes: 3 additions & 0 deletions environs/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type EnvironProvider interface {
// nil.
CloudSchema() *jsonschema.Schema

// Ping tests the connection to the cloud, to verify the endpoint is valid.
Ping(endpoint string) error

// PrepareConfig prepares the configuration for a new model, based on
// the provided arguments. PrepareConfig is expected to produce a
// deterministic output. Any unique values should be based on the
Expand Down
5 changes: 5 additions & 0 deletions provider/azure/environprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ func (p azureEnvironProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p azureEnvironProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig is part of the EnvironProvider interface.
func (prov *azureEnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if err := validateCloudSpec(args.Cloud); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions provider/cloudsigma/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ func (p environProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p environProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig is defined by EnvironProvider.
func (environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if err := validateCloudSpec(args.Cloud); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions provider/dummy/environs.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,11 @@ func (p environProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p environProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig is specified in the EnvironProvider interface.
func (p *environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if _, err := dummy.newConfig(args.Config); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions provider/ec2/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ func (p environProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p environProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig is specified in the EnvironProvider interface.
func (p environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if err := validateCloudSpec(args.Cloud); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions provider/gce/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ func (p environProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p environProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig implements environs.EnvironProvider.
func (p environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if err := validateCloudSpec(args.Cloud); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions provider/joyent/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ func (p joyentProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p joyentProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig is part of the EnvironProvider interface.
func (p joyentProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if err := validateCloudSpec(args.Cloud); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions provider/lxd/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func (p environProvider) CloudSchema() *jsonschema.Schema {
return nil
}

// Ping tests the connection to the cloud, to verify the endpoint is valid.
func (p environProvider) Ping(endpoint string) error {
return errors.NotImplementedf("Ping")
}

// PrepareConfig implements environs.EnvironProvider.
func (p environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
if err := validateCloudSpec(args.Cloud); err != nil {
Expand Down
10 changes: 5 additions & 5 deletions provider/maas/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type maasModelConfig struct {
attrs map[string]interface{}
}

func (prov maasEnvironProvider) newConfig(cfg *config.Config) (*maasModelConfig, error) {
func (prov MaasEnvironProvider) newConfig(cfg *config.Config) (*maasModelConfig, error) {
validCfg, err := prov.Validate(cfg, nil)
if err != nil {
return nil, err
Expand All @@ -39,7 +39,7 @@ func (prov maasEnvironProvider) newConfig(cfg *config.Config) (*maasModelConfig,
}

// Schema returns the configuration schema for an environment.
func (maasEnvironProvider) Schema() environschema.Fields {
func (MaasEnvironProvider) Schema() environschema.Fields {
fields, err := config.Schema(configSchema)
if err != nil {
panic(err)
Expand All @@ -49,17 +49,17 @@ func (maasEnvironProvider) Schema() environschema.Fields {

// ConfigSchema returns extra config attributes specific
// to this provider only.
func (p maasEnvironProvider) ConfigSchema() schema.Fields {
func (p MaasEnvironProvider) ConfigSchema() schema.Fields {
return configFields
}

// ConfigDefaults returns the default values for the
// provider specific config attributes.
func (p maasEnvironProvider) ConfigDefaults() schema.Defaults {
func (p MaasEnvironProvider) ConfigDefaults() schema.Defaults {
return configDefaults
}

func (prov maasEnvironProvider) Validate(cfg, oldCfg *config.Config) (*config.Config, error) {
func (prov MaasEnvironProvider) Validate(cfg, oldCfg *config.Config) (*config.Config, error) {
// Validate base configuration change before validating MAAS specifics.
err := config.Validate(cfg, oldCfg)
if err != nil {
Expand Down
9 changes: 2 additions & 7 deletions provider/maas/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package maas
import (
"github.com/juju/gomaasapi"
jc "github.com/juju/testing/checkers"
"github.com/juju/utils/set"
gc "gopkg.in/check.v1"

"github.com/juju/juju/environs/config"
Expand All @@ -15,7 +14,7 @@ import (

// Ensure MAAS provider supports the expected interfaces.
var (
_ config.ConfigSchemaSource = (*maasEnvironProvider)(nil)
_ config.ConfigSchemaSource = (*MaasEnvironProvider)(nil)
)

type configSuite struct {
Expand Down Expand Up @@ -48,10 +47,6 @@ func newConfig(values map[string]interface{}) (*maasModelConfig, error) {

func (s *configSuite) SetUpTest(c *gc.C) {
s.BaseSuite.SetUpTest(c)
mockCapabilities := func(*gomaasapi.MAASObject, string) (set.Strings, error) {
return set.NewStrings("network-deployment-ubuntu"), nil
}
s.PatchValue(&GetCapabilities, mockCapabilities)
mockGetController := func(string, string) (gomaasapi.Controller, error) {
return nil, gomaasapi.NewUnsupportedVersionError("oops")
}
Expand All @@ -68,7 +63,7 @@ func (*configSuite) TestValidateUpcallsEnvironsConfigValidate(c *gc.C) {
newCfg, err := oldCfg.Apply(map[string]interface{}{"name": newName})
c.Assert(err, jc.ErrorIsNil)

_, err = maasEnvironProvider{}.Validate(newCfg, oldCfg.Config)
_, err = MaasEnvironProvider{}.Validate(newCfg, oldCfg.Config)

c.Assert(err, gc.NotNil)
c.Check(err, gc.ErrorMatches, ".*cannot change name.*")
Expand Down
Loading

0 comments on commit a185eca

Please sign in to comment.