github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/highavailability/highavailability.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package highavailability
     5  
     6  import (
     7  	"sort"
     8  	"strconv"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names"
    13  
    14  	"github.com/juju/juju/apiserver/common"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/constraints"
    17  	"github.com/juju/juju/state"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.apiserver.highavailability")
    21  
    22  func init() {
    23  	common.RegisterStandardFacade("HighAvailability", 2, NewHighAvailabilityAPI)
    24  }
    25  
    26  // HighAvailability defines the methods on the highavailability API end point.
    27  type HighAvailability interface {
    28  	EnableHA(args params.ControllersSpecs) (params.ControllersChangeResults, error)
    29  }
    30  
    31  // HighAvailabilityAPI implements the HighAvailability interface and is the concrete
    32  // implementation of the api end point.
    33  type HighAvailabilityAPI struct {
    34  	state      *state.State
    35  	resources  *common.Resources
    36  	authorizer common.Authorizer
    37  }
    38  
    39  var _ HighAvailability = (*HighAvailabilityAPI)(nil)
    40  
    41  // NewHighAvailabilityAPI creates a new server-side highavailability API end point.
    42  func NewHighAvailabilityAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*HighAvailabilityAPI, error) {
    43  	// Only clients and environment managers can access the high availability service.
    44  	if !authorizer.AuthClient() && !authorizer.AuthModelManager() {
    45  		return nil, common.ErrPerm
    46  	}
    47  	return &HighAvailabilityAPI{
    48  		state:      st,
    49  		resources:  resources,
    50  		authorizer: authorizer,
    51  	}, nil
    52  }
    53  
    54  func (api *HighAvailabilityAPI) EnableHA(args params.ControllersSpecs) (params.ControllersChangeResults, error) {
    55  	results := params.ControllersChangeResults{Results: make([]params.ControllersChangeResult, len(args.Specs))}
    56  	for i, controllersServersSpec := range args.Specs {
    57  		result, err := EnableHASingle(api.state, controllersServersSpec)
    58  		results.Results[i].Result = result
    59  		results.Results[i].Error = common.ServerError(err)
    60  	}
    61  	return results, nil
    62  }
    63  
    64  // Convert machine ids to tags.
    65  func machineIdsToTags(ids ...string) []string {
    66  	var result []string
    67  	for _, id := range ids {
    68  		result = append(result, names.NewMachineTag(id).String())
    69  	}
    70  	return result
    71  }
    72  
    73  // Generate a ControllersChanges structure.
    74  func controllersChanges(change state.ControllersChanges) params.ControllersChanges {
    75  	return params.ControllersChanges{
    76  		Added:      machineIdsToTags(change.Added...),
    77  		Maintained: machineIdsToTags(change.Maintained...),
    78  		Removed:    machineIdsToTags(change.Removed...),
    79  		Promoted:   machineIdsToTags(change.Promoted...),
    80  		Demoted:    machineIdsToTags(change.Demoted...),
    81  		Converted:  machineIdsToTags(change.Converted...),
    82  	}
    83  }
    84  
    85  // EnableHASingle applies a single ControllersServersSpec specification to the current environment.
    86  // Exported so it can be called by the legacy client API in the client package.
    87  func EnableHASingle(st *state.State, spec params.ControllersSpec) (params.ControllersChanges, error) {
    88  	if !st.IsController() {
    89  		return params.ControllersChanges{}, errors.New("unsupported with hosted models")
    90  	}
    91  	// Check if changes are allowed and the command may proceed.
    92  	blockChecker := common.NewBlockChecker(st)
    93  	if err := blockChecker.ChangeAllowed(); err != nil {
    94  		return params.ControllersChanges{}, errors.Trace(err)
    95  	}
    96  	// Validate the environment tag if present.
    97  	if spec.ModelTag != "" {
    98  		tag, err := names.ParseModelTag(spec.ModelTag)
    99  		if err != nil {
   100  			return params.ControllersChanges{}, errors.Errorf("invalid model tag: %v", err)
   101  		}
   102  		if _, err := st.FindEntity(tag); err != nil {
   103  			return params.ControllersChanges{}, err
   104  		}
   105  	}
   106  
   107  	series := spec.Series
   108  	if series == "" {
   109  		ssi, err := st.ControllerInfo()
   110  		if err != nil {
   111  			return params.ControllersChanges{}, err
   112  		}
   113  
   114  		// We should always have at least one voting machine
   115  		// If we *really* wanted we could just pick whatever series is
   116  		// in the majority, but really, if we always copy the value of
   117  		// the first one, then they'll stay in sync.
   118  		if len(ssi.VotingMachineIds) == 0 {
   119  			// Better than a panic()?
   120  			return params.ControllersChanges{}, errors.Errorf("internal error, failed to find any voting machines")
   121  		}
   122  		templateMachine, err := st.Machine(ssi.VotingMachineIds[0])
   123  		if err != nil {
   124  			return params.ControllersChanges{}, err
   125  		}
   126  		series = templateMachine.Series()
   127  	}
   128  	if constraints.IsEmpty(&spec.Constraints) {
   129  		// No constraints specified, so we'll use the constraints off
   130  		// a running controller.
   131  		controllerInfo, err := st.ControllerInfo()
   132  		if err != nil {
   133  			return params.ControllersChanges{}, err
   134  		}
   135  		// We'll sort the controller ids to find the smallest.
   136  		// This will typically give the initial bootstrap machine.
   137  		var controllerIds []int
   138  		for _, id := range controllerInfo.MachineIds {
   139  			idNum, err := strconv.Atoi(id)
   140  			if err != nil {
   141  				logger.Warningf("ignoring non numeric controller id %v", id)
   142  				continue
   143  			}
   144  			controllerIds = append(controllerIds, idNum)
   145  		}
   146  		if len(controllerIds) == 0 {
   147  			errors.Errorf("internal error, failed to find any controllers")
   148  		}
   149  		sort.Ints(controllerIds)
   150  
   151  		// Load the controller machine and get its constraints.
   152  		controllerId := controllerIds[0]
   153  		controller, err := st.Machine(strconv.Itoa(controllerId))
   154  		if err != nil {
   155  			return params.ControllersChanges{}, errors.Annotatef(err, "reading controller id %v", controllerId)
   156  		}
   157  		spec.Constraints, err = controller.Constraints()
   158  		if err != nil {
   159  			return params.ControllersChanges{}, errors.Annotatef(err, "reading constraints for controller id %v", controllerId)
   160  		}
   161  	}
   162  
   163  	changes, err := st.EnableHA(spec.NumControllers, spec.Constraints, series, spec.Placement)
   164  	if err != nil {
   165  		return params.ControllersChanges{}, err
   166  	}
   167  	return controllersChanges(changes), nil
   168  }
   169  
   170  // StopHAReplicationForUpgrade will prompt the HA cluster to enter upgrade
   171  // mongo mode.
   172  func (api *HighAvailabilityAPI) StopHAReplicationForUpgrade(args params.UpgradeMongoParams) (params.MongoUpgradeResults, error) {
   173  	ha, err := api.state.SetUpgradeMongoMode(args.Target)
   174  	if err != nil {
   175  		return params.MongoUpgradeResults{}, errors.Annotate(err, "cannot stop HA for ugprade")
   176  	}
   177  	members := make([]params.HAMember, len(ha.Members))
   178  	for i, m := range ha.Members {
   179  		members[i] = params.HAMember{
   180  			Tag:           m.Tag,
   181  			PublicAddress: m.PublicAddress,
   182  			Series:        m.Series,
   183  		}
   184  	}
   185  	return params.MongoUpgradeResults{
   186  		Master: params.HAMember{
   187  			Tag:           ha.Master.Tag,
   188  			PublicAddress: ha.Master.PublicAddress,
   189  			Series:        ha.Master.Series,
   190  		},
   191  		Members:   members,
   192  		RsMembers: ha.RsMembers,
   193  	}, nil
   194  }
   195  
   196  // ResumeHAReplicationAfterUpgrade will add the upgraded members of HA
   197  // cluster to the upgraded master.
   198  func (api *HighAvailabilityAPI) ResumeHAReplicationAfterUpgrade(args params.ResumeReplicationParams) error {
   199  	return api.state.ResumeReplication(args.Members)
   200  }