github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/upgrader/upgrader.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package upgrader 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "github.com/juju/names" 10 "github.com/juju/version" 11 12 "github.com/juju/juju/apiserver/common" 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/environs/config" 15 "github.com/juju/juju/state" 16 "github.com/juju/juju/state/watcher" 17 jujuversion "github.com/juju/juju/version" 18 ) 19 20 var logger = loggo.GetLogger("juju.apiserver.upgrader") 21 22 func init() { 23 common.RegisterStandardFacade("Upgrader", 1, upgraderFacade) 24 } 25 26 // upgraderFacade is a bit unique vs the other API Facades, as it has two 27 // implementations that actually expose the same API and which one gets 28 // returned depends on who is calling. 29 // Both of them conform to the exact Upgrader API, so the actual calls that are 30 // available do not depend on who is currently connected. 31 func upgraderFacade(st *state.State, resources *common.Resources, auth common.Authorizer) (Upgrader, error) { 32 // The type of upgrader we return depends on who is asking. 33 // Machines get an UpgraderAPI, units get a UnitUpgraderAPI. 34 // This is tested in the api/upgrader package since there 35 // are currently no direct srvRoot tests. 36 // TODO(dfc) this is redundant 37 tag, err := names.ParseTag(auth.GetAuthTag().String()) 38 if err != nil { 39 return nil, common.ErrPerm 40 } 41 switch tag.(type) { 42 case names.MachineTag: 43 return NewUpgraderAPI(st, resources, auth) 44 case names.UnitTag: 45 return NewUnitUpgraderAPI(st, resources, auth) 46 } 47 // Not a machine or unit. 48 return nil, common.ErrPerm 49 } 50 51 type Upgrader interface { 52 WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) 53 DesiredVersion(args params.Entities) (params.VersionResults, error) 54 Tools(args params.Entities) (params.ToolsResults, error) 55 SetTools(args params.EntitiesVersion) (params.ErrorResults, error) 56 } 57 58 // UpgraderAPI provides access to the Upgrader API facade. 59 type UpgraderAPI struct { 60 *common.ToolsGetter 61 *common.ToolsSetter 62 63 st *state.State 64 resources *common.Resources 65 authorizer common.Authorizer 66 } 67 68 // NewUpgraderAPI creates a new server-side UpgraderAPI facade. 69 func NewUpgraderAPI( 70 st *state.State, 71 resources *common.Resources, 72 authorizer common.Authorizer, 73 ) (*UpgraderAPI, error) { 74 if !authorizer.AuthMachineAgent() { 75 return nil, common.ErrPerm 76 } 77 getCanReadWrite := func() (common.AuthFunc, error) { 78 return authorizer.AuthOwner, nil 79 } 80 env, err := st.Model() 81 if err != nil { 82 return nil, err 83 } 84 urlGetter := common.NewToolsURLGetter(env.UUID(), st) 85 return &UpgraderAPI{ 86 ToolsGetter: common.NewToolsGetter(st, st, st, urlGetter, getCanReadWrite), 87 ToolsSetter: common.NewToolsSetter(st, getCanReadWrite), 88 st: st, 89 resources: resources, 90 authorizer: authorizer, 91 }, nil 92 } 93 94 // WatchAPIVersion starts a watcher to track if there is a new version 95 // of the API that we want to upgrade to 96 func (u *UpgraderAPI) WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) { 97 result := params.NotifyWatchResults{ 98 Results: make([]params.NotifyWatchResult, len(args.Entities)), 99 } 100 for i, agent := range args.Entities { 101 tag, err := names.ParseTag(agent.Tag) 102 if err != nil { 103 return params.NotifyWatchResults{}, errors.Trace(err) 104 } 105 err = common.ErrPerm 106 if u.authorizer.AuthOwner(tag) { 107 watch := u.st.WatchForModelConfigChanges() 108 // Consume the initial event. Technically, API 109 // calls to Watch 'transmit' the initial event 110 // in the Watch response. But NotifyWatchers 111 // have no state to transmit. 112 if _, ok := <-watch.Changes(); ok { 113 result.Results[i].NotifyWatcherId = u.resources.Register(watch) 114 err = nil 115 } else { 116 err = watcher.EnsureErr(watch) 117 } 118 } 119 result.Results[i].Error = common.ServerError(err) 120 } 121 return result, nil 122 } 123 124 func (u *UpgraderAPI) getGlobalAgentVersion() (version.Number, *config.Config, error) { 125 // Get the Agent Version requested in the Environment Config 126 cfg, err := u.st.ModelConfig() 127 if err != nil { 128 return version.Number{}, nil, err 129 } 130 agentVersion, ok := cfg.AgentVersion() 131 if !ok { 132 return version.Number{}, nil, errors.New("agent version not set in model config") 133 } 134 return agentVersion, cfg, nil 135 } 136 137 type hasIsManager interface { 138 IsManager() bool 139 } 140 141 func (u *UpgraderAPI) entityIsManager(tag names.Tag) bool { 142 entity, err := u.st.FindEntity(tag) 143 if err != nil { 144 return false 145 } 146 if m, ok := entity.(hasIsManager); !ok { 147 return false 148 } else { 149 return m.IsManager() 150 } 151 } 152 153 // DesiredVersion reports the Agent Version that we want that agent to be running 154 func (u *UpgraderAPI) DesiredVersion(args params.Entities) (params.VersionResults, error) { 155 results := make([]params.VersionResult, len(args.Entities)) 156 if len(args.Entities) == 0 { 157 return params.VersionResults{}, nil 158 } 159 agentVersion, _, err := u.getGlobalAgentVersion() 160 if err != nil { 161 return params.VersionResults{}, common.ServerError(err) 162 } 163 // Is the desired version greater than the current API server version? 164 isNewerVersion := agentVersion.Compare(jujuversion.Current) > 0 165 for i, entity := range args.Entities { 166 tag, err := names.ParseTag(entity.Tag) 167 if err != nil { 168 results[i].Error = common.ServerError(err) 169 continue 170 } 171 err = common.ErrPerm 172 if u.authorizer.AuthOwner(tag) { 173 // Only return the globally desired agent version if the 174 // asking entity is a machine agent with JobManageModel or 175 // if this API server is running the globally desired agent 176 // version. Otherwise report this API server's current 177 // agent version. 178 // 179 // This ensures that state machine agents will upgrade 180 // first - once they have restarted and are running the 181 // new version other agents will start to see the new 182 // agent version. 183 if !isNewerVersion || u.entityIsManager(tag) { 184 results[i].Version = &agentVersion 185 } else { 186 logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, jujuversion.Current) 187 results[i].Version = &jujuversion.Current 188 } 189 err = nil 190 } 191 results[i].Error = common.ServerError(err) 192 } 193 return params.VersionResults{Results: results}, nil 194 }