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