github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/uniter/context.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"io"
    10  	// "os"
    11  	"os/exec"
    12  	// "path/filepath"
    13  	"sort"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/juju/loggo"
    19  
    20  	"launchpad.net/juju-core/charm"
    21  	"launchpad.net/juju-core/juju/osenv"
    22  	"launchpad.net/juju-core/state/api/params"
    23  	"launchpad.net/juju-core/state/api/uniter"
    24  	utilexec "launchpad.net/juju-core/utils/exec"
    25  	"launchpad.net/juju-core/worker/uniter/jujuc"
    26  	unitdebug "launchpad.net/juju-core/worker/uniter/debug"
    27  )
    28  
    29  type missingHookError struct {
    30  	hookName string
    31  }
    32  
    33  func RebootRequiredError(err error) bool {
    34      if err == nil {
    35          return false
    36      }
    37      msg, _ := err.(*exec.ExitError)
    38      if msg == nil {
    39      	return false
    40      }
    41      code := msg.Sys().(syscall.WaitStatus).ExitStatus()
    42      if code == osenv.MustReboot {
    43          return true
    44      }
    45  
    46      return false
    47  }
    48  
    49  func (e *missingHookError) Error() string {
    50  	return e.hookName + " does not exist"
    51  }
    52  
    53  func IsMissingHookError(err error) bool {
    54  	_, ok := err.(*missingHookError)
    55  	return ok
    56  }
    57  
    58  // HookContext is the implementation of jujuc.Context.
    59  type HookContext struct {
    60  	unit *uniter.Unit
    61  
    62  	// privateAddress is the cached value of the unit's private
    63  	// address.
    64  	privateAddress string
    65  
    66  	// publicAddress is the cached value of the unit's public
    67  	// address.
    68  	publicAddress string
    69  
    70  	// configSettings holds the service configuration.
    71  	configSettings charm.Settings
    72  
    73  	// id identifies the context.
    74  	id string
    75  
    76  	// uuid is the universally unique identifier of the environment.
    77  	uuid string
    78  
    79  	// envName is the human friendly name of the environment.
    80  	envName string
    81  
    82  	// relationId identifies the relation for which a relation hook is
    83  	// executing. If it is -1, the context is not running a relation hook;
    84  	// otherwise, its value must be a valid key into the relations map.
    85  	relationId int
    86  
    87  	// remoteUnitName identifies the changing unit of the executing relation
    88  	// hook. It will be empty if the context is not running a relation hook,
    89  	// or if it is running a relation-broken hook.
    90  	remoteUnitName string
    91  
    92  	// relations contains the context for every relation the unit is a member
    93  	// of, keyed on relation id.
    94  	relations map[int]*ContextRelation
    95  
    96  	// apiAddrs contains the API server addresses.
    97  	apiAddrs []string
    98  
    99  	// serviceOwner contains the owner of the service
   100  	serviceOwner string
   101  
   102  	// proxySettings are the current proxy settings that the uniter knows about
   103  	proxySettings osenv.ProxySettings
   104  }
   105  
   106  func NewHookContext(unit *uniter.Unit, id, uuid, envName string,
   107  	relationId int, remoteUnitName string, relations map[int]*ContextRelation,
   108  	apiAddrs []string, serviceOwner string, proxySettings osenv.ProxySettings) (*HookContext, error) {
   109  	ctx := &HookContext{
   110  		unit:           unit,
   111  		id:             id,
   112  		uuid:           uuid,
   113  		envName:        envName,
   114  		relationId:     relationId,
   115  		remoteUnitName: remoteUnitName,
   116  		relations:      relations,
   117  		apiAddrs:       apiAddrs,
   118  		serviceOwner:   serviceOwner,
   119  		proxySettings:  proxySettings,
   120  	}
   121  	// Get and cache the addresses.
   122  	var err error
   123  	ctx.publicAddress, err = unit.PublicAddress()
   124  	if err != nil && !params.IsCodeNoAddressSet(err) {
   125  		return nil, err
   126  	}
   127  	ctx.privateAddress, err = unit.PrivateAddress()
   128  	if err != nil && !params.IsCodeNoAddressSet(err) {
   129  		return nil, err
   130  	}
   131  	return ctx, nil
   132  }
   133  
   134  func (ctx *HookContext) UnitName() string {
   135  	return ctx.unit.Name()
   136  }
   137  
   138  func (ctx *HookContext) PublicAddress() (string, bool) {
   139  	return ctx.publicAddress, ctx.publicAddress != ""
   140  }
   141  
   142  func (ctx *HookContext) PrivateAddress() (string, bool) {
   143  	return ctx.privateAddress, ctx.privateAddress != ""
   144  }
   145  
   146  func (ctx *HookContext) OpenPort(protocol string, port int) error {
   147  	return ctx.unit.OpenPort(protocol, port)
   148  }
   149  
   150  func (ctx *HookContext) ClosePort(protocol string, port int) error {
   151  	return ctx.unit.ClosePort(protocol, port)
   152  }
   153  
   154  func (ctx *HookContext) OwnerTag() string {
   155  	return ctx.serviceOwner
   156  }
   157  
   158  func (ctx *HookContext) ConfigSettings() (charm.Settings, error) {
   159  	if ctx.configSettings == nil {
   160  		var err error
   161  		ctx.configSettings, err = ctx.unit.ConfigSettings()
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  	}
   166  	result := charm.Settings{}
   167  	for name, value := range ctx.configSettings {
   168  		result[name] = value
   169  	}
   170  	return result, nil
   171  }
   172  
   173  func (ctx *HookContext) HookRelation() (jujuc.ContextRelation, bool) {
   174  	return ctx.Relation(ctx.relationId)
   175  }
   176  
   177  func (ctx *HookContext) RemoteUnitName() (string, bool) {
   178  	return ctx.remoteUnitName, ctx.remoteUnitName != ""
   179  }
   180  
   181  func (ctx *HookContext) Relation(id int) (jujuc.ContextRelation, bool) {
   182  	r, found := ctx.relations[id]
   183  	return r, found
   184  }
   185  
   186  func (ctx *HookContext) RelationIds() []int {
   187  	ids := []int{}
   188  	for id := range ctx.relations {
   189  		ids = append(ids, id)
   190  	}
   191  	return ids
   192  }
   193  
   194  func (ctx *HookContext) finalizeContext(process string, err error) error {
   195      if err != nil{
   196          // gsamfira: We need this later to requeue the hook
   197          logger.Infof("Checking if reboot is needed")
   198          if RebootRequiredError(err){
   199          	logger.Infof("Error code is reboot code")
   200              return err
   201          }
   202      }
   203      logger.Infof("Continuing without reboot")
   204      writeChanges := err == nil
   205      for id, rctx := range ctx.relations {
   206          if writeChanges {
   207              if e := rctx.WriteSettings(); e != nil {
   208                  e = fmt.Errorf(
   209                      "could not write settings from %q to relation %d: %v",
   210                      process, id, e,
   211                  )
   212                  logger.Errorf("%v", e)
   213                  if err == nil {
   214                      err = e
   215                  }
   216              }
   217          }
   218          rctx.ClearCache()
   219      }
   220      return err
   221  }
   222  
   223  // RunCommands executes the commands in an environment which allows it to to
   224  // call back into the hook context to execute jujuc tools.
   225  func (ctx *HookContext) RunCommands(commands, charmDir, toolsDir, socketPath string) (*utilexec.ExecResponse, error) {
   226  	env := ctx.hookVars(charmDir, toolsDir, socketPath)
   227  	result, err := utilexec.RunCommands(
   228  		utilexec.RunParams{
   229  			Commands:    commands,
   230  			WorkingDir:  charmDir,
   231  			Environment: env})
   232  	return result, ctx.finalizeContext("run commands", err)
   233  }
   234  
   235  func (ctx *HookContext) GetLogger(hookName string) loggo.Logger {
   236  	return loggo.GetLogger(fmt.Sprintf("unit.%s.%s", ctx.UnitName(), hookName))
   237  }
   238  
   239  
   240  // RunHook executes a hook in an environment which allows it to to call back
   241  // into the hook context to execute jujuc tools.
   242  func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error {
   243      var err error
   244      env := ctx.hookVars(charmDir, toolsDir, socketPath)
   245      debugctx := unitdebug.NewHooksContext(ctx.unit.Name())
   246      if session, _ := debugctx.FindSession(); session != nil && session.MatchHook(hookName) {
   247          logger.Infof("executing %s via debug-hooks", hookName)
   248          err = session.RunHook(hookName, charmDir, env)
   249      } else {
   250          err = ctx.runCharmHook(hookName, charmDir, env)
   251      }
   252      return ctx.finalizeContext(hookName, err)
   253  }
   254  
   255  type hookLogger struct {
   256  	r       io.ReadCloser
   257  	done    chan struct{}
   258  	mu      sync.Mutex
   259  	stopped bool
   260  	logger  loggo.Logger
   261  }
   262  
   263  func (l *hookLogger) run() {
   264  	defer close(l.done)
   265  	defer l.r.Close()
   266  	br := bufio.NewReaderSize(l.r, 4096)
   267  	for {
   268  		line, _, err := br.ReadLine()
   269  		if err != nil {
   270  			if err != io.EOF {
   271  				logger.Errorf("cannot read hook output: %v", err)
   272  			}
   273  			break
   274  		}
   275  		l.mu.Lock()
   276  		if l.stopped {
   277  			l.mu.Unlock()
   278  			return
   279  		}
   280  		l.logger.Infof("%s", line)
   281  		l.mu.Unlock()
   282  	}
   283  }
   284  
   285  func (l *hookLogger) stop() {
   286  	// We can see the process exit before the logger has processed
   287  	// all its output, so allow a moment for the data buffered
   288  	// in the pipe to be processed. We don't wait indefinitely though,
   289  	// because the hook may have started a background process
   290  	// that keeps the pipe open.
   291  	select {
   292  	case <-l.done:
   293  	case <-time.After(100 * time.Millisecond):
   294  	}
   295  	// We can't close the pipe asynchronously, so just
   296  	// stifle output instead.
   297  	l.mu.Lock()
   298  	l.stopped = true
   299  	l.mu.Unlock()
   300  }
   301  
   302  // SettingsMap is a map from unit name to relation settings.
   303  type SettingsMap map[string]params.RelationSettings
   304  
   305  // ContextRelation is the implementation of jujuc.ContextRelation.
   306  type ContextRelation struct {
   307  	ru *uniter.RelationUnit
   308  
   309  	// members contains settings for known relation members. Nil values
   310  	// indicate members whose settings have not yet been cached.
   311  	members SettingsMap
   312  
   313  	// settings allows read and write access to the relation unit settings.
   314  	settings *uniter.Settings
   315  
   316  	// cache is a short-term cache that enables consistent access to settings
   317  	// for units that are not currently participating in the relation. Its
   318  	// contents should be cleared whenever a new hook is executed.
   319  	cache SettingsMap
   320  }
   321  
   322  // NewContextRelation creates a new context for the given relation unit.
   323  // The unit-name keys of members supplies the initial membership.
   324  func NewContextRelation(ru *uniter.RelationUnit, members map[string]int64) *ContextRelation {
   325  	ctx := &ContextRelation{ru: ru, members: SettingsMap{}}
   326  	for unit := range members {
   327  		ctx.members[unit] = nil
   328  	}
   329  	ctx.ClearCache()
   330  	return ctx
   331  }
   332  
   333  // WriteSettings persists all changes made to the unit's relation settings.
   334  func (ctx *ContextRelation) WriteSettings() (err error) {
   335  	if ctx.settings != nil {
   336  		err = ctx.settings.Write()
   337  	}
   338  	return
   339  }
   340  
   341  // ClearCache discards all cached settings for units that are not members
   342  // of the relation, and all unwritten changes to the unit's relation settings.
   343  // including any changes to Settings that have not been written.
   344  func (ctx *ContextRelation) ClearCache() {
   345  	ctx.settings = nil
   346  	ctx.cache = make(SettingsMap)
   347  }
   348  
   349  // UpdateMembers ensures that the context is aware of every supplied
   350  // member unit. For each supplied member, the cached settings will be
   351  // overwritten.
   352  func (ctx *ContextRelation) UpdateMembers(members SettingsMap) {
   353  	for m, s := range members {
   354  		ctx.members[m] = s
   355  	}
   356  }
   357  
   358  // DeleteMember drops the membership and cache of a single remote unit, without
   359  // perturbing settings for the remaining members.
   360  func (ctx *ContextRelation) DeleteMember(unitName string) {
   361  	delete(ctx.members, unitName)
   362  }
   363  
   364  func (ctx *ContextRelation) Id() int {
   365  	return ctx.ru.Relation().Id()
   366  }
   367  
   368  func (ctx *ContextRelation) Name() string {
   369  	return ctx.ru.Endpoint().Name
   370  }
   371  
   372  func (ctx *ContextRelation) FakeId() string {
   373  	return fmt.Sprintf("%s:%d", ctx.Name(), ctx.ru.Relation().Id())
   374  }
   375  
   376  func (ctx *ContextRelation) UnitNames() (units []string) {
   377  	for unit := range ctx.members {
   378  		units = append(units, unit)
   379  	}
   380  	sort.Strings(units)
   381  	return units
   382  }
   383  
   384  func (ctx *ContextRelation) Settings() (jujuc.Settings, error) {
   385  	if ctx.settings == nil {
   386  		node, err := ctx.ru.Settings()
   387  		if err != nil {
   388  			return nil, err
   389  		}
   390  		ctx.settings = node
   391  	}
   392  	return ctx.settings, nil
   393  }
   394  
   395  func (ctx *ContextRelation) ReadSettings(unit string) (settings params.RelationSettings, err error) {
   396  	settings, member := ctx.members[unit]
   397  	if settings == nil {
   398  		if settings = ctx.cache[unit]; settings == nil {
   399  			settings, err = ctx.ru.ReadSettings(unit)
   400  			if err != nil {
   401  				return nil, err
   402  			}
   403  		}
   404  	}
   405  	if member {
   406  		ctx.members[unit] = settings
   407  	} else {
   408  		ctx.cache[unit] = settings
   409  	}
   410  	return settings, nil
   411  }