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 }