Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose UUID in API URLs #26

Merged
merged 7 commits into from Jun 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Expand Up @@ -3,8 +3,8 @@ cmd/jujud/jujud
cmd/builddb/builddb
cmd/charmd/charmd
cmd/charmload/charmload
./tags
./TAGS
tags
TAGS
.emacs.desktop
.emacs.desktop.lock
*.test
Expand Down
1 change: 1 addition & 0 deletions dependencies.tsv
Expand Up @@ -3,6 +3,7 @@ code.google.com/p/go.net hg c17ad62118ea511e1051721b429779fa40bddc74 116
github.com/binary132/gojsonpointer git 57ab5e9c764219a3e0c4d7759797fefdcab22e9c
github.com/binary132/gojsonreference git 75785fb7b21f9bf2051dca600da83ff57bc6582a
github.com/binary132/gojsonschema git 640782bf48d45ba2b22fd1e8a80842667c52af00
github.com/bmizerany/pat git 48be7df2c27e1cec821a3284a683ce6ef90d9052
github.com/joyent/gocommon git 40c7818502f7c1ebbb13dab185a26e77b746ff40
github.com/joyent/gomanta git cabd97b029d931836571f00b7e48c331809a30b7
github.com/joyent/gosdc git 2f11feadd2d9891e92296a1077c3e2e56939547d
Expand Down
7 changes: 5 additions & 2 deletions environs/configstore/disk.go
Expand Up @@ -32,6 +32,7 @@ type diskStore struct {
type EnvironInfoData struct {
User string
Password string
EnvironUUID string `json:"environ-uuid,omitempty" yaml:"environ-uuid,omitempty"`
StateServers []string `json:"state-servers" yaml:"state-servers"`
CACert string `json:"ca-cert" yaml:"ca-cert"`
Config map[string]interface{} `json:"bootstrap-config,omitempty" yaml:"bootstrap-config,omitempty"`
Expand Down Expand Up @@ -138,8 +139,9 @@ func (info *environInfo) APICredentials() APICredentials {
// APIEndpoint implements EnvironInfo.APIEndpoint.
func (info *environInfo) APIEndpoint() APIEndpoint {
return APIEndpoint{
Addresses: info.EnvInfo.StateServers,
CACert: info.EnvInfo.CACert,
Addresses: info.EnvInfo.StateServers,
CACert: info.EnvInfo.CACert,
EnvironUUID: info.EnvInfo.EnvironUUID,
}
}

Expand All @@ -155,6 +157,7 @@ func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) {
func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) {
info.EnvInfo.StateServers = endpoint.Addresses
info.EnvInfo.CACert = endpoint.CACert
info.EnvInfo.EnvironUUID = endpoint.EnvironUUID
}

// SetAPICredentials implements EnvironInfo.SetAPICredentials.
Expand Down
4 changes: 4 additions & 0 deletions environs/configstore/interface.go
Expand Up @@ -19,6 +19,10 @@ type APIEndpoint struct {
// CACert holds the CA certificate that
// signed the API server's key.
CACert string

// EnvironUUID holds the UUID for the environment we are connecting to.
// This may be empty if the environment has not been bootstrapped.
EnvironUUID string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not an environ tag, to match the tag in api.Info ?

}

// APICredentials hold credentials for connecting to an API endpoint.
Expand Down
10 changes: 6 additions & 4 deletions environs/configstore/interface_test.go
Expand Up @@ -46,8 +46,9 @@ func (s *interfaceSuite) TestSetAPIEndpointAndCredentials(c *gc.C) {
c.Assert(err, gc.IsNil)

expectEndpoint := configstore.APIEndpoint{
Addresses: []string{"example.com"},
CACert: "a cert",
Addresses: []string{"example.com"},
CACert: "a cert",
EnvironUUID: "dead-beef",
}
info.SetAPIEndpoint(expectEndpoint)
c.Assert(info.APIEndpoint(), gc.DeepEquals, expectEndpoint)
Expand Down Expand Up @@ -75,8 +76,9 @@ func (s *interfaceSuite) TestWrite(c *gc.C) {
info.SetAPICredentials(expectCreds)

expectEndpoint := configstore.APIEndpoint{
Addresses: []string{"example.com"},
CACert: "a cert",
Addresses: []string{"example.invalid"},
CACert: "a cert",
EnvironUUID: "dead-beef",
}
info.SetAPIEndpoint(expectEndpoint)

Expand Down
63 changes: 47 additions & 16 deletions juju/api.go
Expand Up @@ -32,6 +32,7 @@ var (
type apiState interface {
Close() error
APIHostPorts() [][]instance.HostPort
EnvironTag() string
}

type apiOpenFunc func(*api.Info, api.DialOpts) (apiState, error)
Expand Down Expand Up @@ -212,7 +213,7 @@ func newAPIFromStore(envName string, store configstore.Storage, apiOpen apiOpenF

st := val0.(apiState)
// Even though we are about to update API addresses based on
// APIHostPorts in cacheChangedAPIAddresses, we first cache the
// APIHostPorts in cacheChangedAPIInfo, we first cache the
// addresses based on the provider lookup. This is because older API
// servers didn't return their HostPort information on Login, and we
// still want to cache our connection information to them.
Expand All @@ -231,7 +232,7 @@ func newAPIFromStore(envName string, store configstore.Storage, apiOpen apiOpenF
}
}
// Update API addresses if they've changed. Error is non-fatal.
if localerr := cacheChangedAPIAddresses(info, st); localerr != nil {
if localerr := cacheChangedAPIInfo(info, st); localerr != nil {
logger.Warningf("cannot failed to cache API addresses: %v", localerr)
}
return st, nil
Expand Down Expand Up @@ -267,11 +268,18 @@ func apiInfoConnect(store configstore.Storage, info configstore.EnvironInfo, api
return nil, &infoConnectError{fmt.Errorf("no cached addresses")}
}
logger.Infof("connecting to API addresses: %v", endpoint.Addresses)
environTag := ""
if endpoint.EnvironUUID != "" {
// Note: we should be validating that EnvironUUID contains a
// valid UUID.
environTag = names.EnvironTag(endpoint.EnvironUUID)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check EnvironUUID contains a valid UUID, there is LP bug http://pad.lv/1257587 for fixing names.IsEnvironment() to be more strict. It's not something to do now, only perhaps add a TODO comment here referencing the same bug and some notice we need to improve this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment, though since the next steps are about comparing it with the concrete UUID someone else has, it is being validated, just not early.

}
apiInfo := &api.Info{
Addrs: endpoint.Addresses,
CACert: endpoint.CACert,
Tag: names.UserTag(info.APICredentials().User),
Password: info.APICredentials().Password,
Addrs: endpoint.Addresses,
CACert: endpoint.CACert,
Tag: names.UserTag(info.APICredentials().User),
Password: info.APICredentials().Password,
EnvironTag: environTag,
}
st, err := apiOpen(apiInfo, api.DefaultDialOpts())
if err != nil {
Expand Down Expand Up @@ -343,14 +351,24 @@ func environAPIInfo(environ environs.Environ) (*api.Info, error) {
// cacheAPIInfo updates the local environment settings (.jenv file)
// with the provided apiInfo, assuming we've just successfully
// connected to the API server.
func cacheAPIInfo(info configstore.EnvironInfo, apiInfo *api.Info) error {
func cacheAPIInfo(info configstore.EnvironInfo, apiInfo *api.Info) (err error) {
defer errors.Contextf(&err, "failed to cache API credentials")
environUUID := ""
if apiInfo.EnvironTag != "" {
var err error
_, environUUID, err = names.ParseTag(apiInfo.Tag, names.EnvironTagKind)
if err != nil {
return err
}
}
info.SetAPIEndpoint(configstore.APIEndpoint{
Addresses: apiInfo.Addrs,
CACert: string(apiInfo.CACert),
Addresses: apiInfo.Addrs,
CACert: string(apiInfo.CACert),
EnvironUUID: environUUID,
})
_, username, err := names.ParseTag(apiInfo.Tag, names.UserTagKind)
if err != nil {
return fmt.Errorf("invalid API user tag: %v", err)
return err
}
info.SetAPICredentials(configstore.APICredentials{
User: username,
Expand All @@ -359,9 +377,10 @@ func cacheAPIInfo(info configstore.EnvironInfo, apiInfo *api.Info) error {
return info.Write()
}

// cacheChangedAPIAddresses updates the local environment settings (.jenv file)
// with the provided API server addresses if they have changed.
func cacheChangedAPIAddresses(info configstore.EnvironInfo, st apiState) error {
// cacheChangedAPIInfo updates the local environment settings (.jenv file)
// with the provided API server addresses if they have changed. It will also
// save the environment tag if it is available.
func cacheChangedAPIInfo(info configstore.EnvironInfo, st apiState) error {
var addrs []string
for _, serverHostPorts := range st.APIHostPorts() {
for _, hostPort := range serverHostPorts {
Expand All @@ -373,11 +392,23 @@ func cacheChangedAPIAddresses(info configstore.EnvironInfo, st apiState) error {
}
}
endpoint := info.APIEndpoint()
if len(addrs) == 0 || !addrsChanged(endpoint.Addresses, addrs) {
newEnvironTag := st.EnvironTag()
changed := false
if newEnvironTag != "" {
_, environUUID, err := names.ParseTag(newEnvironTag, names.EnvironTagKind)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should environ UUID change at all like this? It feels like we should log it at least when it's known to have changed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it definitely can change from empty to containing something.
It can also change from the fallback code (if I force a bad environ-uuid in ~/.juju/local.jenv, and then connect to the environment, the direct connection failure is treated as a api.Open failed, just like if it couldn't actually connect, rather than a more critical something is actually wrong failure. Which is certainly a bug).
And then it falls back to "use the apiConfigConnect code, which doesn't know about environ UUID, and overwrites it with the 'correct' value).
We can make this code more pessimistic, and only allow it to be set from the empty string, but we do at least need that ability.

if err == nil && endpoint.EnvironUUID != environUUID {
changed = true
endpoint.EnvironUUID = environUUID
}
}
if len(addrs) != 0 && addrsChanged(endpoint.Addresses, addrs) {
logger.Debugf("API addresses changed from %q to %q", endpoint.Addresses, addrs)
changed = true
endpoint.Addresses = addrs
}
if !changed {
return nil
}
logger.Debugf("API addresses changed from %q to %q", endpoint.Addresses, addrs)
endpoint.Addresses = addrs
info.SetAPIEndpoint(endpoint)
if err := info.Write(); err != nil {
return err
Expand Down