github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/crossmodel/crossmodel.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package crossmodel 5 6 import ( 7 "fmt" 8 "net" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/core/crossmodel" 16 "github.com/juju/juju/core/status" 17 "github.com/juju/juju/network" 18 "github.com/juju/juju/state" 19 ) 20 21 var logger = loggo.GetLogger("juju.apiserver.common.crossmodel") 22 23 // PublishRelationChange applies the relation change event to the specified backend. 24 func PublishRelationChange(backend Backend, relationTag names.Tag, change params.RemoteRelationChangeEvent) error { 25 logger.Debugf("publish into model %v change for %v: %+v", backend.ModelUUID(), relationTag, change) 26 27 dyingOrDead := change.Life != "" && change.Life != params.Alive 28 // Ensure the relation exists. 29 rel, err := backend.KeyRelation(relationTag.Id()) 30 if errors.IsNotFound(err) { 31 if dyingOrDead { 32 return nil 33 } 34 } 35 if err != nil { 36 return errors.Trace(err) 37 } 38 39 // Update the relation suspended status. 40 currentStatus := rel.Suspended() 41 if !dyingOrDead && change.Suspended != nil && currentStatus != *change.Suspended { 42 var ( 43 newStatus status.Status 44 message string 45 ) 46 if *change.Suspended { 47 newStatus = status.Suspending 48 message = change.SuspendedReason 49 if message == "" { 50 message = "suspending after update from remote model" 51 } 52 } 53 if err := rel.SetSuspended(*change.Suspended, message); err != nil { 54 return errors.Trace(err) 55 } 56 if !*change.Suspended { 57 newStatus = status.Joining 58 message = "" 59 } 60 if err := rel.SetStatus(status.StatusInfo{ 61 Status: newStatus, 62 Message: message, 63 }); err != nil && !errors.IsNotValid(err) { 64 return errors.Trace(err) 65 } 66 } 67 68 // Look up the application on the remote side of this relation 69 // ie from the model which published this change. 70 applicationTag, err := backend.GetRemoteEntity(change.ApplicationToken) 71 if err != nil { 72 return errors.Trace(err) 73 } 74 logger.Debugf("application tag for token %+v is %v in model %v", change.ApplicationToken, applicationTag, backend.ModelUUID()) 75 76 // If the remote model has destroyed the relation, do it here also. 77 forceCleanUp := change.ForceCleanup != nil && *change.ForceCleanup 78 if dyingOrDead { 79 logger.Debugf("remote consuming side of %v died", relationTag) 80 if forceCleanUp { 81 logger.Debugf("forcing cleanup of units for %v", applicationTag.Id()) 82 remoteUnits, err := rel.AllRemoteUnits(applicationTag.Id()) 83 if err != nil { 84 return errors.Trace(err) 85 } 86 logger.Debugf("got %v relation units to clean", len(remoteUnits)) 87 for _, ru := range remoteUnits { 88 if err := ru.LeaveScope(); err != nil { 89 return errors.Trace(err) 90 } 91 } 92 } 93 94 if err := rel.Destroy(); err != nil { 95 return errors.Trace(err) 96 } 97 // See if we need to remove the remote application proxy - we do this 98 // on the offering side as there is 1:1 between proxy and consuming app. 99 remoteApp, err := backend.RemoteApplication(applicationTag.Id()) 100 if err != nil && !errors.IsNotFound(err) { 101 return errors.Trace(err) 102 } 103 if err == nil && remoteApp.IsConsumerProxy() { 104 logger.Debugf("destroy consuming app proxy for %v", applicationTag.Id()) 105 if err := remoteApp.Destroy(); err != nil { 106 return errors.Trace(err) 107 } 108 } 109 110 // If we are forcing cleanup, we can exit early here. 111 if forceCleanUp { 112 return nil 113 } 114 } 115 116 // TODO(wallyworld) - deal with remote application being removed 117 if applicationTag == nil { 118 logger.Infof("no remote application found for %v", relationTag.Id()) 119 return nil 120 } 121 logger.Debugf("remote application for changed relation %v is %v in model %v", relationTag.Id(), applicationTag.Id(), backend.ModelUUID()) 122 123 for _, id := range change.DepartedUnits { 124 unitTag := names.NewUnitTag(fmt.Sprintf("%s/%v", applicationTag.Id(), id)) 125 logger.Debugf("unit %v has departed relation %v", unitTag.Id(), relationTag.Id()) 126 ru, err := rel.RemoteUnit(unitTag.Id()) 127 if err != nil { 128 return errors.Trace(err) 129 } 130 logger.Debugf("%s leaving scope", unitTag.Id()) 131 if err := ru.LeaveScope(); err != nil { 132 return errors.Trace(err) 133 } 134 } 135 136 for _, change := range change.ChangedUnits { 137 unitTag := names.NewUnitTag(fmt.Sprintf("%s/%v", applicationTag.Id(), change.UnitId)) 138 logger.Debugf("changed unit tag for unit id %v is %v", change.UnitId, unitTag) 139 ru, err := rel.RemoteUnit(unitTag.Id()) 140 if err != nil { 141 return errors.Trace(err) 142 } 143 inScope, err := ru.InScope() 144 if err != nil { 145 return errors.Trace(err) 146 } 147 settings := make(map[string]interface{}) 148 for k, v := range change.Settings { 149 settings[k] = v 150 } 151 if !inScope { 152 logger.Debugf("%s entering scope (%v)", unitTag.Id(), settings) 153 err = ru.EnterScope(settings) 154 } else { 155 logger.Debugf("%s updated settings (%v)", unitTag.Id(), settings) 156 err = ru.ReplaceSettings(settings) 157 } 158 if err != nil { 159 return errors.Trace(err) 160 } 161 } 162 return nil 163 } 164 165 // WatchRelationUnits returns a watcher for changes to the units on the specified relation. 166 func WatchRelationUnits(backend Backend, tag names.RelationTag) (state.RelationUnitsWatcher, error) { 167 relation, err := backend.KeyRelation(tag.Id()) 168 if err != nil { 169 return nil, errors.Trace(err) 170 } 171 for _, ep := range relation.Endpoints() { 172 _, err := backend.Application(ep.ApplicationName) 173 if errors.IsNotFound(err) { 174 // Not found, so it's the remote application. Try the next endpoint. 175 continue 176 } else if err != nil { 177 return nil, errors.Trace(err) 178 } 179 w, err := relation.WatchUnits(ep.ApplicationName) 180 if err != nil { 181 return nil, errors.Trace(err) 182 } 183 return w, nil 184 } 185 return nil, errors.NotFoundf("local application for %s", names.ReadableString(tag)) 186 } 187 188 // RelationUnitSettings returns the unit settings for the specified relation unit. 189 func RelationUnitSettings(backend Backend, ru params.RelationUnit) (params.Settings, error) { 190 relationTag, err := names.ParseRelationTag(ru.Relation) 191 if err != nil { 192 return nil, errors.Trace(err) 193 } 194 rel, err := backend.KeyRelation(relationTag.Id()) 195 if err != nil { 196 return nil, errors.Trace(err) 197 } 198 unitTag, err := names.ParseUnitTag(ru.Unit) 199 if err != nil { 200 return nil, errors.Trace(err) 201 } 202 unit, err := rel.Unit(unitTag.Id()) 203 if err != nil { 204 return nil, errors.Trace(err) 205 } 206 settings, err := unit.Settings() 207 if err != nil { 208 return nil, errors.Trace(err) 209 } 210 paramsSettings := make(params.Settings) 211 for k, v := range settings { 212 vString, ok := v.(string) 213 if !ok { 214 return nil, errors.Errorf( 215 "invalid relation setting %q: expected string, got %T", k, v, 216 ) 217 } 218 paramsSettings[k] = vString 219 } 220 return paramsSettings, nil 221 } 222 223 // PublishIngressNetworkChange saves the specified ingress networks for a relation. 224 func PublishIngressNetworkChange(backend Backend, relationTag names.Tag, change params.IngressNetworksChangeEvent) error { 225 logger.Debugf("publish into model %v network change for %v: %+v", backend.ModelUUID(), relationTag, change) 226 227 // Ensure the relation exists. 228 rel, err := backend.KeyRelation(relationTag.Id()) 229 if errors.IsNotFound(err) { 230 return nil 231 } 232 if err != nil { 233 return errors.Trace(err) 234 } 235 236 logger.Debugf("relation %v requires ingress networks %v", rel, change.Networks) 237 if err := validateIngressNetworks(backend, change.Networks); err != nil { 238 return errors.Trace(err) 239 } 240 241 _, err = backend.SaveIngressNetworks(rel.Tag().Id(), change.Networks) 242 return err 243 } 244 245 func validateIngressNetworks(backend Backend, networks []string) error { 246 if len(networks) == 0 { 247 return nil 248 } 249 250 // Check that the required ingress is allowed. 251 rule, err := backend.FirewallRule(state.JujuApplicationOfferRule) 252 if err != nil && !errors.IsNotFound(err) { 253 return errors.Trace(err) 254 } 255 if errors.IsNotFound(err) { 256 return nil 257 } 258 var whitelistCIDRs, requestedCIDRs []*net.IPNet 259 if err := parseCIDRs(&whitelistCIDRs, rule.WhitelistCIDRs); err != nil { 260 return errors.Trace(err) 261 } 262 if err := parseCIDRs(&requestedCIDRs, networks); err != nil { 263 return errors.Trace(err) 264 } 265 if len(whitelistCIDRs) > 0 { 266 for _, n := range requestedCIDRs { 267 if !network.SubnetInAnyRange(whitelistCIDRs, n) { 268 return ¶ms.Error{ 269 Code: params.CodeForbidden, 270 Message: fmt.Sprintf("subnet %v not in firewall whitelist", n), 271 } 272 } 273 } 274 } 275 return nil 276 } 277 278 func parseCIDRs(cidrs *[]*net.IPNet, values []string) error { 279 for _, cidrStr := range values { 280 if _, ipNet, err := net.ParseCIDR(cidrStr); err != nil { 281 return err 282 } else { 283 *cidrs = append(*cidrs, ipNet) 284 } 285 } 286 return nil 287 } 288 289 type relationGetter interface { 290 KeyRelation(string) (Relation, error) 291 } 292 293 // GetRelationLifeSuspendedStatusChange returns a life/suspended status change 294 // struct for a specified relation key. 295 func GetRelationLifeSuspendedStatusChange(st relationGetter, key string) (*params.RelationLifeSuspendedStatusChange, error) { 296 rel, err := st.KeyRelation(key) 297 if errors.IsNotFound(err) { 298 return ¶ms.RelationLifeSuspendedStatusChange{ 299 Key: key, 300 Life: params.Dead, 301 }, nil 302 } 303 if err != nil { 304 return nil, errors.Trace(err) 305 } 306 return ¶ms.RelationLifeSuspendedStatusChange{ 307 Key: key, 308 Life: params.Life(rel.Life().String()), 309 Suspended: rel.Suspended(), 310 SuspendedReason: rel.SuspendedReason(), 311 }, nil 312 } 313 314 type offerGetter interface { 315 ApplicationOfferForUUID(string) (*crossmodel.ApplicationOffer, error) 316 Application(string) (Application, error) 317 } 318 319 // GetOfferStatusChange returns a status change 320 // struct for a specified offer name. 321 func GetOfferStatusChange(st offerGetter, offerUUID string) (*params.OfferStatusChange, error) { 322 offer, err := st.ApplicationOfferForUUID(offerUUID) 323 if err != nil { 324 return nil, errors.Trace(err) 325 } 326 // TODO(wallyworld) - for now, offer status is just the application status 327 app, err := st.Application(offer.ApplicationName) 328 if err != nil { 329 return nil, errors.Trace(err) 330 } 331 status, err := app.Status() 332 if err != nil { 333 return nil, errors.Trace(err) 334 } 335 return ¶ms.OfferStatusChange{ 336 OfferName: offer.OfferName, 337 Status: params.EntityStatus{ 338 Status: status.Status, 339 Info: status.Message, 340 Data: status.Data, 341 Since: status.Since, 342 }, 343 }, nil 344 }