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 &params.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 &params.RelationLifeSuspendedStatusChange{
   299  			Key:  key,
   300  			Life: params.Dead,
   301  		}, nil
   302  	}
   303  	if err != nil {
   304  		return nil, errors.Trace(err)
   305  	}
   306  	return &params.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 &params.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  }