github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/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/version" 10 "gopkg.in/juju/names.v2" 11 12 "github.com/juju/juju/apiserver/common" 13 "github.com/juju/juju/apiserver/facade" 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/environs/config" 16 "github.com/juju/juju/state" 17 "github.com/juju/juju/state/stateenvirons" 18 "github.com/juju/juju/state/watcher" 19 jujuversion "github.com/juju/juju/version" 20 ) 21 22 var logger = loggo.GetLogger("juju.apiserver.upgrader") 23 24 // The upgrader facade is a bit unique vs the other API Facades, as it 25 // has two implementations that actually expose the same API and which 26 // one gets returned depends on who is calling. Both of them conform 27 // to the exact Upgrader API, so the actual calls that are available 28 // do not depend on who is currently connected. 29 30 // NewUpgraderFacade provides the signature required for facade registration. 31 func NewUpgraderFacade(st *state.State, resources facade.Resources, auth facade.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 m *state.Model 65 resources facade.Resources 66 authorizer facade.Authorizer 67 } 68 69 // NewUpgraderAPI creates a new server-side UpgraderAPI facade. 70 func NewUpgraderAPI( 71 st *state.State, 72 resources facade.Resources, 73 authorizer facade.Authorizer, 74 ) (*UpgraderAPI, error) { 75 if !authorizer.AuthMachineAgent() { 76 return nil, common.ErrPerm 77 } 78 getCanReadWrite := func() (common.AuthFunc, error) { 79 return authorizer.AuthOwner, nil 80 } 81 model, err := st.Model() 82 if err != nil { 83 return nil, err 84 } 85 urlGetter := common.NewToolsURLGetter(model.UUID(), st) 86 configGetter := stateenvirons.EnvironConfigGetter{st, model} 87 return &UpgraderAPI{ 88 ToolsGetter: common.NewToolsGetter(st, configGetter, st, urlGetter, getCanReadWrite), 89 ToolsSetter: common.NewToolsSetter(st, getCanReadWrite), 90 st: st, 91 m: model, 92 resources: resources, 93 authorizer: authorizer, 94 }, nil 95 } 96 97 // WatchAPIVersion starts a watcher to track if there is a new version 98 // of the API that we want to upgrade to 99 func (u *UpgraderAPI) WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) { 100 result := params.NotifyWatchResults{ 101 Results: make([]params.NotifyWatchResult, len(args.Entities)), 102 } 103 for i, agent := range args.Entities { 104 tag, err := names.ParseTag(agent.Tag) 105 if err != nil { 106 return params.NotifyWatchResults{}, errors.Trace(err) 107 } 108 err = common.ErrPerm 109 if u.authorizer.AuthOwner(tag) { 110 watch := u.m.WatchForModelConfigChanges() 111 // Consume the initial event. Technically, API 112 // calls to Watch 'transmit' the initial event 113 // in the Watch response. But NotifyWatchers 114 // have no state to transmit. 115 if _, ok := <-watch.Changes(); ok { 116 result.Results[i].NotifyWatcherId = u.resources.Register(watch) 117 err = nil 118 } else { 119 err = watcher.EnsureErr(watch) 120 } 121 } 122 result.Results[i].Error = common.ServerError(err) 123 } 124 return result, nil 125 } 126 127 func (u *UpgraderAPI) getGlobalAgentVersion() (version.Number, *config.Config, error) { 128 // Get the Agent Version requested in the Model Config 129 cfg, err := u.m.ModelConfig() 130 if err != nil { 131 return version.Number{}, nil, err 132 } 133 agentVersion, ok := cfg.AgentVersion() 134 if !ok { 135 return version.Number{}, nil, errors.New("agent version not set in model config") 136 } 137 return agentVersion, cfg, nil 138 } 139 140 type hasIsManager interface { 141 IsManager() bool 142 } 143 144 func (u *UpgraderAPI) entityIsManager(tag names.Tag) bool { 145 entity, err := u.st.FindEntity(tag) 146 if err != nil { 147 return false 148 } 149 if m, ok := entity.(hasIsManager); !ok { 150 return false 151 } else { 152 return m.IsManager() 153 } 154 } 155 156 // DesiredVersion reports the Agent Version that we want that agent to be running 157 func (u *UpgraderAPI) DesiredVersion(args params.Entities) (params.VersionResults, error) { 158 results := make([]params.VersionResult, len(args.Entities)) 159 if len(args.Entities) == 0 { 160 return params.VersionResults{}, nil 161 } 162 agentVersion, _, err := u.getGlobalAgentVersion() 163 if err != nil { 164 return params.VersionResults{}, common.ServerError(err) 165 } 166 // Is the desired version greater than the current API server version? 167 isNewerVersion := agentVersion.Compare(jujuversion.Current) > 0 168 for i, entity := range args.Entities { 169 tag, err := names.ParseTag(entity.Tag) 170 if err != nil { 171 results[i].Error = common.ServerError(err) 172 continue 173 } 174 err = common.ErrPerm 175 if u.authorizer.AuthOwner(tag) { 176 // Only return the globally desired agent version if the 177 // asking entity is a machine agent with JobManageModel or 178 // if this API server is running the globally desired agent 179 // version. Otherwise report this API server's current 180 // agent version. 181 // 182 // This ensures that state machine agents will upgrade 183 // first - once they have restarted and are running the 184 // new version other agents will start to see the new 185 // agent version. 186 if !isNewerVersion || u.entityIsManager(tag) { 187 results[i].Version = &agentVersion 188 } else { 189 logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, jujuversion.Current) 190 results[i].Version = &jujuversion.Current 191 } 192 err = nil 193 } 194 results[i].Error = common.ServerError(err) 195 } 196 return params.VersionResults{Results: results}, nil 197 }