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 }