github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/apicaller/open.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apicaller
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/utils"
    11  
    12  	"github.com/juju/juju/agent"
    13  	"github.com/juju/juju/api"
    14  	apiagent "github.com/juju/juju/api/agent"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/worker"
    17  )
    18  
    19  var (
    20  	apiOpen = openAPIForAgent
    21  
    22  	checkProvisionedStrategy = utils.AttemptStrategy{
    23  		Total: 1 * time.Minute,
    24  		Delay: 5 * time.Second,
    25  	}
    26  )
    27  
    28  // openAPIForAgent exists to handle the edge case that exists
    29  // when an environment is jumping several versions and doesn't
    30  // yet have the environment UUID cached in the agent config.
    31  // This happens only the first time an agent tries to connect
    32  // after an upgrade.  If there is no environment UUID set, then
    33  // use login version 1.
    34  func openAPIForAgent(info *api.Info, opts api.DialOpts) (api.Connection, error) {
    35  	if info.EnvironTag.Id() == "" {
    36  		return api.OpenWithVersion(info, opts, 1)
    37  	}
    38  	return api.Open(info, opts)
    39  }
    40  
    41  // OpenAPIState opens the API using the given information. The agent's
    42  // password is changed if the fallback password was used to connect to
    43  // the API.
    44  func OpenAPIState(a agent.Agent) (_ api.Connection, _ *apiagent.Entity, err error) {
    45  	agentConfig := a.CurrentConfig()
    46  	info := agentConfig.APIInfo()
    47  	st, usedOldPassword, err := openAPIStateUsingInfo(info, agentConfig.OldPassword())
    48  	if err != nil {
    49  		return nil, nil, err
    50  	}
    51  	defer func() {
    52  		// NOTE(fwereade): we may close and overwrite st below,
    53  		// so we need to double-check what we need to do here.
    54  		if err != nil && st != nil {
    55  			if err := st.Close(); err != nil {
    56  				logger.Errorf("while closing API connection: %v", err)
    57  			}
    58  		}
    59  	}()
    60  
    61  	tag := agentConfig.Tag()
    62  	entity, err := st.Agent().Entity(tag)
    63  	if err == nil && entity.Life() == params.Dead {
    64  		logger.Errorf("agent terminating - entity %q is dead", tag)
    65  		return nil, nil, worker.ErrTerminateAgent
    66  	}
    67  	if params.IsCodeUnauthorized(err) {
    68  		logger.Errorf("agent terminating due to error returned during entity lookup: %v", err)
    69  		return nil, nil, worker.ErrTerminateAgent
    70  	}
    71  	if err != nil {
    72  		return nil, nil, err
    73  	}
    74  
    75  	if !usedOldPassword {
    76  		// Call set password with the current password.  If we've recently
    77  		// become a state server, this will fix up our credentials in mongo.
    78  		if err := entity.SetPassword(info.Password); err != nil {
    79  			return nil, nil, errors.Annotate(err, "can't reset agent password")
    80  		}
    81  	} else {
    82  		// We succeeded in connecting with the fallback
    83  		// password, so we need to create a new password
    84  		// for the future.
    85  		logger.Debugf("replacing insecure password")
    86  		newPassword, err := utils.RandomPassword()
    87  		if err != nil {
    88  			return nil, nil, err
    89  		}
    90  		err = setAgentPassword(newPassword, info.Password, a, entity)
    91  		if err != nil {
    92  			return nil, nil, err
    93  		}
    94  
    95  		// Reconnect to the API with the new password.
    96  		if err := st.Close(); err != nil {
    97  			logger.Errorf("while closing API connection with old password: %v", err)
    98  		}
    99  		info.Password = newPassword
   100  
   101  		// NOTE(fwereade): this is where we rebind st. If you accidentally make
   102  		// it a local variable you will break this func in a subtle and currently-
   103  		// untested way.
   104  		st, err = apiOpen(info, api.DialOpts{})
   105  		if err != nil {
   106  			return nil, nil, err
   107  		}
   108  	}
   109  
   110  	return st, entity, nil
   111  }
   112  
   113  func setAgentPassword(newPw, oldPw string, a agent.Agent, entity *apiagent.Entity) error {
   114  	// Change the configuration *before* setting the entity
   115  	// password, so that we avoid the possibility that
   116  	// we might successfully change the entity's
   117  	// password but fail to write the configuration,
   118  	// thus locking us out completely.
   119  	if err := a.ChangeConfig(func(c agent.ConfigSetter) error {
   120  		c.SetPassword(newPw)
   121  		c.SetOldPassword(oldPw)
   122  		return nil
   123  	}); err != nil {
   124  		return err
   125  	}
   126  	return entity.SetPassword(newPw)
   127  }
   128  
   129  // OpenAPIStateUsingInfo opens the API using the given API
   130  // information, and returns the opened state and the api entity with
   131  // the given tag.
   132  func OpenAPIStateUsingInfo(info *api.Info, oldPassword string) (api.Connection, error) {
   133  	st, _, err := openAPIStateUsingInfo(info, oldPassword)
   134  	return st, err
   135  }
   136  
   137  func openAPIStateUsingInfo(info *api.Info, oldPassword string) (api.Connection, bool, error) {
   138  	// We let the API dial fail immediately because the
   139  	// runner's loop outside the caller of openAPIState will
   140  	// keep on retrying. If we block for ages here,
   141  	// then the worker that's calling this cannot
   142  	// be interrupted.
   143  	st, err := apiOpen(info, api.DialOpts{})
   144  	usedOldPassword := false
   145  	if params.IsCodeUnauthorized(err) {
   146  		// We've perhaps used the wrong password, so
   147  		// try again with the fallback password.
   148  		infoCopy := *info
   149  		info = &infoCopy
   150  		info.Password = oldPassword
   151  		usedOldPassword = true
   152  		st, err = apiOpen(info, api.DialOpts{})
   153  	}
   154  	// The provisioner may take some time to record the agent's
   155  	// machine instance ID, so wait until it does so.
   156  	if params.IsCodeNotProvisioned(err) {
   157  		for a := checkProvisionedStrategy.Start(); a.Next(); {
   158  			st, err = apiOpen(info, api.DialOpts{})
   159  			if !params.IsCodeNotProvisioned(err) {
   160  				break
   161  			}
   162  		}
   163  	}
   164  	if err != nil {
   165  		if params.IsCodeNotProvisioned(err) || params.IsCodeUnauthorized(err) {
   166  			logger.Errorf("agent terminating due to error returned during API open: %v", err)
   167  			return nil, false, worker.ErrTerminateAgent
   168  		}
   169  		return nil, false, err
   170  	}
   171  
   172  	return st, usedOldPassword, nil
   173  }