github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/application_ports.go (about)

     1  // Copyright 2022 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/collections/set"
     8  	"github.com/juju/collections/transform"
     9  	"github.com/juju/errors"
    10  	"github.com/juju/mgo/v3"
    11  	"github.com/juju/mgo/v3/bson"
    12  	"github.com/juju/mgo/v3/txn"
    13  	jujutxn "github.com/juju/txn/v3"
    14  
    15  	"github.com/juju/juju/core/network"
    16  )
    17  
    18  // applicationPortRangesDoc represents the state of ports opened for an application.
    19  type applicationPortRangesDoc struct {
    20  	DocID           string `bson:"_id"`
    21  	ModelUUID       string `bson:"model-uuid"`
    22  	ApplicationName string `bson:"application-name"`
    23  
    24  	// PortRanges is the application port ranges that are open for all units.
    25  	PortRanges network.GroupedPortRanges            `bson:"port-ranges"`
    26  	UnitRanges map[string]network.GroupedPortRanges `bson:"unit-port-ranges"`
    27  	TxnRevno   int64                                `bson:"txn-revno"`
    28  }
    29  
    30  func newApplicationPortRangesDoc(docID, modelUUID, appName string) applicationPortRangesDoc {
    31  	return applicationPortRangesDoc{
    32  		DocID:           docID,
    33  		ModelUUID:       modelUUID,
    34  		ApplicationName: appName,
    35  		UnitRanges:      make(map[string]network.GroupedPortRanges),
    36  	}
    37  }
    38  
    39  var _ ApplicationPortRanges = (*applicationPortRanges)(nil)
    40  
    41  type applicationPortRanges struct {
    42  	st  *State
    43  	doc applicationPortRangesDoc
    44  
    45  	// docExists is false if the port range doc has not yet been persisted
    46  	// to the backing store.
    47  	docExists bool
    48  
    49  	// The set of pending port ranges that have not yet been persisted.
    50  	pendingOpenRanges  network.GroupedPortRanges
    51  	pendingCloseRanges network.GroupedPortRanges
    52  }
    53  
    54  // Changes returns a ModelOperation for applying any changes that were made to
    55  // this port range instance.
    56  func (p *applicationPortRanges) Changes() ModelOperation {
    57  	// The application scope opened port range is not implemented yet.
    58  	// We manage(open/close) ports by units using "unitPortRanges.Open|Close|Changes()".
    59  	return nil
    60  }
    61  
    62  // Persisted returns true if the underlying document for this instance exists
    63  // in the database.
    64  func (p *applicationPortRanges) Persisted() bool {
    65  	return p.docExists
    66  }
    67  
    68  // ForUnit returns the set of port ranges opened by the specified unit.
    69  func (p *applicationPortRanges) ForUnit(unitName string) UnitPortRanges {
    70  	return &unitPortRanges{
    71  		unitName: unitName,
    72  		apg:      p,
    73  	}
    74  }
    75  
    76  // ByUnit returns the set of port ranges opened by each unit grouped by unit name.
    77  func (p *applicationPortRanges) ByUnit() map[string]UnitPortRanges {
    78  	if len(p.doc.UnitRanges) == 0 {
    79  		return nil
    80  	}
    81  	res := make(map[string]UnitPortRanges)
    82  	for unitName := range p.doc.UnitRanges {
    83  		res[unitName] = newUnitPortRanges(unitName, p)
    84  	}
    85  	return res
    86  }
    87  
    88  // ByEndpoint returns the list of open port ranges grouped by endpoint.
    89  func (p *applicationPortRanges) ByEndpoint() network.GroupedPortRanges {
    90  	out := make(network.GroupedPortRanges)
    91  	for _, gpg := range p.doc.UnitRanges {
    92  		for endpoint, prs := range gpg {
    93  			out[endpoint] = append(out[endpoint], prs...)
    94  		}
    95  	}
    96  	return out
    97  }
    98  
    99  // UniquePortRanges returns a slice of unique open PortRanges all units.
   100  func (p *applicationPortRanges) UniquePortRanges() []network.PortRange {
   101  	allRanges := make(network.GroupedPortRanges)
   102  	for _, unitRanges := range p.ByUnit() {
   103  		allRanges[""] = append(allRanges[""], unitRanges.UniquePortRanges()...)
   104  	}
   105  	return allRanges.UniquePortRanges()
   106  }
   107  
   108  func (p *applicationPortRanges) clearPendingRecords() {
   109  	p.pendingOpenRanges = make(network.GroupedPortRanges)
   110  	p.pendingCloseRanges = make(network.GroupedPortRanges)
   111  }
   112  
   113  // ApplicationName returns the application name associated with this set of port ranges.
   114  func (p *applicationPortRanges) ApplicationName() string {
   115  	return p.doc.ApplicationName
   116  }
   117  
   118  func (p *applicationPortRanges) Remove() error {
   119  	doc := &applicationPortRanges{st: p.st, doc: p.doc, docExists: p.docExists}
   120  	buildTxn := func(attempt int) ([]txn.Op, error) {
   121  		if attempt > 0 {
   122  			err := doc.Refresh()
   123  			if errors.Is(err, errors.NotFound) {
   124  				return nil, jujutxn.ErrNoOperations
   125  			} else if err != nil {
   126  				return nil, errors.Trace(err)
   127  			}
   128  		}
   129  		return doc.removeOps(), nil
   130  	}
   131  	if err := p.st.db().Run(buildTxn); err != nil {
   132  		return errors.Trace(err)
   133  	}
   134  
   135  	p.docExists = false
   136  	return nil
   137  }
   138  
   139  // Refresh refreshes the port document from state.
   140  func (p *applicationPortRanges) Refresh() error {
   141  	openedPorts, closer := p.st.db().GetCollection(openedPortsC)
   142  	defer closer()
   143  
   144  	err := openedPorts.FindId(p.doc.DocID).One(&p.doc)
   145  	if err == mgo.ErrNotFound {
   146  		p.docExists = false
   147  		return errors.NotFoundf("open port ranges for application %q", p.ApplicationName())
   148  	}
   149  	if err != nil {
   150  		return errors.Annotatef(err, "refresh open port ranges for application %q", p.ApplicationName())
   151  	}
   152  	p.docExists = true
   153  	return nil
   154  }
   155  
   156  func (p *applicationPortRanges) removeOps() []txn.Op {
   157  	if !p.docExists {
   158  		return nil
   159  	}
   160  	return []txn.Op{{
   161  		C:      openedPortsC,
   162  		Id:     p.doc.DocID,
   163  		Assert: txn.DocExists,
   164  		Remove: true,
   165  	}}
   166  }
   167  
   168  var _ UnitPortRanges = (*unitPortRanges)(nil)
   169  
   170  type unitPortRanges struct {
   171  	unitName string
   172  	apg      *applicationPortRanges
   173  }
   174  
   175  func newUnitPortRanges(unitName string, apg *applicationPortRanges) UnitPortRanges {
   176  	return &unitPortRanges{
   177  		unitName: unitName,
   178  		apg:      apg,
   179  	}
   180  }
   181  
   182  // UnitName returns the unit name associated with this set of ports.
   183  func (p *unitPortRanges) UnitName() string {
   184  	return p.unitName
   185  }
   186  
   187  // Open records a request for opening a particular port range for the specified
   188  // endpoint.
   189  func (p *unitPortRanges) Open(endpoint string, portRange network.PortRange) {
   190  	if p.apg.pendingOpenRanges == nil {
   191  		p.apg.pendingOpenRanges = make(network.GroupedPortRanges)
   192  	}
   193  
   194  	p.apg.pendingOpenRanges[endpoint] = append(p.apg.pendingOpenRanges[endpoint], portRange)
   195  }
   196  
   197  // Close records a request for closing a particular port range for the
   198  // specified endpoint.
   199  func (p *unitPortRanges) Close(endpoint string, portRange network.PortRange) {
   200  	if p.apg.pendingCloseRanges == nil {
   201  		p.apg.pendingCloseRanges = make(network.GroupedPortRanges)
   202  	}
   203  
   204  	p.apg.pendingCloseRanges[endpoint] = append(p.apg.pendingCloseRanges[endpoint], portRange)
   205  }
   206  
   207  // Changes returns a ModelOperation for applying any changes that were made to
   208  // this port range instance.
   209  func (p *unitPortRanges) Changes() ModelOperation {
   210  	return newApplicationPortRangesOperation(p.apg, p.unitName)
   211  }
   212  
   213  // UniquePortRanges returns a slice of unique open PortRanges for the unit.
   214  func (p *unitPortRanges) UniquePortRanges() []network.PortRange {
   215  	allRanges := p.apg.doc.UnitRanges[p.unitName].UniquePortRanges()
   216  	network.SortPortRanges(allRanges)
   217  	return allRanges
   218  }
   219  
   220  // ByEndpoint returns the list of open port ranges grouped by endpoint.
   221  func (p *unitPortRanges) ByEndpoint() network.GroupedPortRanges {
   222  	return p.apg.doc.UnitRanges[p.unitName]
   223  }
   224  
   225  // ForEndpoint returns a list of port ranges that the unit has opened for the
   226  // specified endpoint.
   227  func (p *unitPortRanges) ForEndpoint(endpointName string) []network.PortRange {
   228  	unitPortRange := p.apg.doc.UnitRanges[p.unitName]
   229  	if len(unitPortRange) == 0 || len(unitPortRange[endpointName]) == 0 {
   230  		return nil
   231  	}
   232  	res := append([]network.PortRange(nil), unitPortRange[endpointName]...)
   233  	network.SortPortRanges(res)
   234  	return res
   235  }
   236  
   237  var _ ModelOperation = (*applicationPortRangesOperation)(nil)
   238  
   239  type applicationPortRangesOperation struct {
   240  	unitName              string
   241  	apr                   *applicationPortRanges
   242  	updatedUnitPortRanges map[string]network.GroupedPortRanges
   243  }
   244  
   245  func newApplicationPortRangesOperation(apr *applicationPortRanges, unitName string) ModelOperation {
   246  	op := &applicationPortRangesOperation{apr: apr, unitName: unitName}
   247  	op.cloneExistingUnitPortRanges()
   248  	return op
   249  }
   250  
   251  func (op *applicationPortRangesOperation) cloneExistingUnitPortRanges() {
   252  	op.updatedUnitPortRanges = make(map[string]network.GroupedPortRanges)
   253  	for unitName, existingDoc := range op.apr.doc.UnitRanges {
   254  		newDoc := make(network.GroupedPortRanges)
   255  		for endpointName, portRanges := range existingDoc {
   256  			newDoc[endpointName] = append([]network.PortRange(nil), portRanges...)
   257  		}
   258  		op.updatedUnitPortRanges[unitName] = newDoc
   259  	}
   260  }
   261  
   262  func (op *applicationPortRangesOperation) Build(attempt int) ([]txn.Op, error) {
   263  	defer op.apr.clearPendingRecords()
   264  
   265  	if op.unitName == "" {
   266  		return nil, errors.NotValidf("empty unit name")
   267  	}
   268  
   269  	if err := checkModelNotDead(op.apr.st); err != nil {
   270  		return nil, errors.Annotate(err, "cannot open/close ports")
   271  	}
   272  
   273  	var createDoc = !op.apr.docExists
   274  	if attempt > 0 {
   275  		if err := op.apr.Refresh(); err != nil {
   276  			if !errors.Is(err, errors.NotFound) {
   277  				return nil, errors.Annotate(err, "cannot open/close ports")
   278  			}
   279  
   280  			// Doc not found; we need to create it.
   281  			createDoc = true
   282  		}
   283  	}
   284  
   285  	op.cloneExistingUnitPortRanges()
   286  
   287  	if err := op.validatePendingChanges(); err != nil {
   288  		return nil, errors.Annotate(err, "cannot open/close ports")
   289  	}
   290  
   291  	ops := []txn.Op{
   292  		assertModelNotDeadOp(op.apr.st.ModelUUID()),
   293  		assertApplicationAliveOp(op.apr.st.docID(op.apr.ApplicationName())),
   294  		assertUnitNotDeadOp(op.apr.st, op.unitName),
   295  	}
   296  
   297  	portListModified, err := op.mergePendingOpenPortRanges()
   298  	if err != nil {
   299  		return nil, errors.Trace(err)
   300  	}
   301  	modified, err := op.mergePendingClosePortRanges()
   302  	if err != nil {
   303  		return nil, errors.Trace(err)
   304  	}
   305  	portListModified = portListModified || modified
   306  
   307  	if !portListModified || (createDoc && len(op.updatedUnitPortRanges) == 0) {
   308  		return nil, jujutxn.ErrNoOperations
   309  	}
   310  
   311  	if createDoc {
   312  		assert := txn.DocMissing
   313  		ops = append(ops, insertAppPortRangesDocOps(op.apr.st, &op.apr.doc, assert, op.updatedUnitPortRanges)...)
   314  	} else if len(op.updatedUnitPortRanges) == 0 {
   315  		// Port list is empty; get rid of ports document.
   316  		ops = append(ops, op.apr.removeOps()...)
   317  	} else {
   318  		assert := bson.D{
   319  			{Name: "txn-revno", Value: op.apr.doc.TxnRevno},
   320  		}
   321  		ops = append(ops, updateAppPortRangesDocOps(op.apr.st, &op.apr.doc, assert, op.updatedUnitPortRanges)...)
   322  	}
   323  	return ops, nil
   324  }
   325  
   326  func (op *applicationPortRangesOperation) mergePendingOpenPortRanges() (bool, error) {
   327  	var modified bool
   328  	for endpointName, pendingRanges := range op.apr.pendingOpenRanges {
   329  		for _, pendingRange := range pendingRanges {
   330  			if op.rangeExistsForEndpoint(endpointName, pendingRange) {
   331  				// Exists, no op for opening.
   332  				continue
   333  			}
   334  			op.addPortRanges(endpointName, true, pendingRange)
   335  			modified = true
   336  		}
   337  	}
   338  	return modified, nil
   339  }
   340  
   341  func (op *applicationPortRangesOperation) mergePendingClosePortRanges() (bool, error) {
   342  	var modified bool
   343  	for endpointName, pendingRanges := range op.apr.pendingCloseRanges {
   344  		for _, pendingRange := range pendingRanges {
   345  			if !op.rangeExistsForEndpoint(endpointName, pendingRange) {
   346  				// Not exists, no op for closing.
   347  				continue
   348  			}
   349  			modified = op.removePortRange(endpointName, pendingRange)
   350  		}
   351  	}
   352  	return modified, nil
   353  }
   354  
   355  func (op *applicationPortRangesOperation) addPortRanges(endpointName string, merge bool, portRanges ...network.PortRange) {
   356  	if op.updatedUnitPortRanges[op.unitName] == nil {
   357  		op.updatedUnitPortRanges[op.unitName] = make(network.GroupedPortRanges)
   358  	}
   359  	if !merge {
   360  		op.updatedUnitPortRanges[op.unitName][endpointName] = portRanges
   361  		return
   362  	}
   363  	op.updatedUnitPortRanges[op.unitName][endpointName] = append(op.updatedUnitPortRanges[op.unitName][endpointName], portRanges...)
   364  }
   365  
   366  func (op *applicationPortRangesOperation) removePortRange(endpointName string, portRange network.PortRange) bool {
   367  	if op.updatedUnitPortRanges[op.unitName] == nil || op.updatedUnitPortRanges[op.unitName][endpointName] == nil {
   368  		return false
   369  	}
   370  	var modified bool
   371  	existingRanges := op.updatedUnitPortRanges[op.unitName][endpointName]
   372  	for i, v := range existingRanges {
   373  		if v != portRange {
   374  			continue
   375  		}
   376  		existingRanges = append(existingRanges[:i], existingRanges[i+1:]...)
   377  		if len(existingRanges) == 0 {
   378  			delete(op.updatedUnitPortRanges[op.unitName], endpointName)
   379  		} else {
   380  			op.addPortRanges(endpointName, false, existingRanges...)
   381  		}
   382  		modified = true
   383  	}
   384  	return modified
   385  }
   386  
   387  func (op *applicationPortRangesOperation) rangeExistsForEndpoint(endpointName string, portRange network.PortRange) bool {
   388  	// For k8s applications, no endpoint level portrange supported currently.
   389  	// There is only one endpoint(which is empty string - "").
   390  	if len(op.updatedUnitPortRanges[op.unitName][endpointName]) == 0 {
   391  		return false
   392  	}
   393  
   394  	for _, existingRange := range op.updatedUnitPortRanges[op.unitName][endpointName] {
   395  		if existingRange == portRange {
   396  			return true
   397  		}
   398  	}
   399  	return false
   400  }
   401  
   402  func (op *applicationPortRangesOperation) getEndpointBindings() (set.Strings, error) {
   403  	appEndpoints := set.NewStrings()
   404  	endpointToSpaceIDMap, _, err := readEndpointBindings(op.apr.st, applicationGlobalKey(op.apr.ApplicationName()))
   405  	if errors.Is(err, errors.NotFound) {
   406  		return appEndpoints, nil
   407  	}
   408  	if err != nil {
   409  		return appEndpoints, errors.Trace(err)
   410  	}
   411  	for endpointName := range endpointToSpaceIDMap {
   412  		if endpointName == "" {
   413  			continue
   414  		}
   415  		appEndpoints.Add(endpointName)
   416  	}
   417  	return appEndpoints, nil
   418  }
   419  
   420  func (op *applicationPortRangesOperation) validatePendingChanges() error {
   421  	endpointsNames, err := op.getEndpointBindings()
   422  	if err != nil {
   423  		return errors.Trace(err)
   424  	}
   425  
   426  	for endpointName := range op.apr.pendingOpenRanges {
   427  		if len(endpointName) > 0 && !endpointsNames.Contains(endpointName) {
   428  			return errors.NotFoundf("open port range: endpoint %q for application %q", endpointName, op.apr.ApplicationName())
   429  		}
   430  	}
   431  	for endpointName := range op.apr.pendingCloseRanges {
   432  		if len(endpointName) > 0 && !endpointsNames.Contains(endpointName) {
   433  			return errors.NotFoundf("close port range: endpoint %q for application %q", endpointName, op.apr.ApplicationName())
   434  		}
   435  	}
   436  	return nil
   437  }
   438  
   439  // Done implements ModelOperation.
   440  func (op *applicationPortRangesOperation) Done(err error) error {
   441  	if err != nil {
   442  		return err
   443  	}
   444  	// Document has been persisted to state.
   445  	op.apr.docExists = true
   446  	op.apr.doc.UnitRanges = op.updatedUnitPortRanges
   447  
   448  	op.apr.pendingOpenRanges = nil
   449  	op.apr.pendingCloseRanges = nil
   450  	return nil
   451  }
   452  
   453  func insertAppPortRangesDocOps(
   454  	st *State, doc *applicationPortRangesDoc, asserts interface{}, unitRanges map[string]network.GroupedPortRanges,
   455  ) (o []txn.Op) {
   456  	// As the following insert operation might be rolled back, we should
   457  	// not mutate our internal doc but instead work on a copy of the
   458  	// applicationPortRangesDoc.
   459  	docCopy := new(applicationPortRangesDoc)
   460  	*docCopy = *doc
   461  	docCopy.UnitRanges = unitRanges
   462  	return []txn.Op{
   463  		{
   464  			C:      openedPortsC,
   465  			Id:     docCopy.DocID,
   466  			Assert: asserts,
   467  			Insert: docCopy,
   468  		},
   469  	}
   470  }
   471  
   472  func updateAppPortRangesDocOps(
   473  	st *State, doc *applicationPortRangesDoc, asserts interface{}, unitRanges map[string]network.GroupedPortRanges,
   474  ) (o []txn.Op) {
   475  	return []txn.Op{
   476  		{
   477  			C:      openedPortsC,
   478  			Id:     doc.DocID,
   479  			Assert: asserts,
   480  			Update: bson.D{{Name: "$set", Value: bson.D{{Name: "unit-port-ranges", Value: unitRanges}}}},
   481  		},
   482  	}
   483  }
   484  
   485  func removeApplicationPortsForUnitOps(st *State, unit *Unit) ([]txn.Op, error) {
   486  	unitName := unit.Name()
   487  	appPortRanges, err := getApplicationPortRanges(st, unit.ApplicationName())
   488  	if err != nil {
   489  		return nil, errors.Trace(err)
   490  	}
   491  	if !appPortRanges.docExists {
   492  		return nil, nil
   493  	}
   494  	if appPortRanges.doc.UnitRanges == nil || appPortRanges.doc.UnitRanges[unitName] == nil {
   495  		// No entry for the unit; nothing to do here.
   496  		return nil, nil
   497  	}
   498  	// Drop unit rules and write the doc back if non-empty or remove it if empty.
   499  	delete(appPortRanges.doc.UnitRanges, unitName)
   500  	if len(appPortRanges.doc.UnitRanges) != 0 {
   501  		assert := bson.D{{"txn-revno", appPortRanges.doc.TxnRevno}}
   502  		return updateAppPortRangesDocOps(st, &appPortRanges.doc, assert, appPortRanges.doc.UnitRanges), nil
   503  	}
   504  	return appPortRanges.removeOps(), nil
   505  }
   506  
   507  // getApplicationPortRanges attempts to retrieve the set of opened ports for
   508  // a particular embedded k8s application. If the underlying document does not exist, a blank
   509  // applicationPortRanges instance with the docExists flag set to false will be
   510  // returned instead.
   511  func getApplicationPortRanges(st *State, appName string) (*applicationPortRanges, error) {
   512  	openedPorts, closer := st.db().GetCollection(openedPortsC)
   513  	defer closer()
   514  
   515  	docID := st.docID(applicationGlobalKey(appName))
   516  	var doc applicationPortRangesDoc
   517  	if err := openedPorts.FindId(docID).One(&doc); err != nil {
   518  		if err != mgo.ErrNotFound {
   519  			return nil, errors.Annotatef(err, "cannot get opened port ranges for application %q", appName)
   520  		}
   521  		return &applicationPortRanges{
   522  			st:        st,
   523  			doc:       newApplicationPortRangesDoc(docID, st.ModelUUID(), appName),
   524  			docExists: false,
   525  		}, nil
   526  	}
   527  
   528  	return &applicationPortRanges{
   529  		st:        st,
   530  		doc:       doc,
   531  		docExists: true,
   532  	}, nil
   533  }
   534  
   535  // getOpenedApplicationPortRanges attempts to retrieve the set of opened ports for
   536  // a particular embedded k8s unit. If the underlying document does not exist, a blank
   537  // unitPortRanges instance with the docExists flag set to false will be
   538  // returned instead.
   539  func getUnitPortRanges(st *State, appName, unitName string) (*unitPortRanges, error) {
   540  	apg, err := getApplicationPortRanges(st, appName)
   541  	if err != nil {
   542  		return nil, errors.Trace(err)
   543  	}
   544  	return &unitPortRanges{unitName: unitName, apg: apg}, nil
   545  }
   546  
   547  // OpenedPortRangesForAllApplications returns a slice of opened port ranges for all
   548  // applications managed by this model.
   549  func (m *Model) OpenedPortRangesForAllApplications() ([]ApplicationPortRanges, error) {
   550  	mprResults, err := getOpenedApplicationPortRangesForAllApplications(m.st)
   551  	if err != nil {
   552  		return nil, errors.Trace(err)
   553  	}
   554  
   555  	return transform.Slice(mprResults, func(agr *applicationPortRanges) ApplicationPortRanges {
   556  		return agr
   557  	}), nil
   558  }
   559  
   560  // getOpenedApplicationPortRangesForAllApplications is used for migration export.
   561  func getOpenedApplicationPortRangesForAllApplications(st *State) ([]*applicationPortRanges, error) {
   562  	apps, err := st.AllApplications()
   563  	if err != nil {
   564  		return nil, errors.Trace(err)
   565  	}
   566  	var appNames []string
   567  	for _, app := range apps {
   568  		appNames = append(appNames, app.Name())
   569  	}
   570  	openedPorts, closer := st.db().GetCollection(openedPortsC)
   571  	defer closer()
   572  	docs := []applicationPortRangesDoc{}
   573  	err = openedPorts.Find(bson.D{{"application-name", bson.D{{"$in", appNames}}}}).All(&docs)
   574  	if err != nil {
   575  		return nil, errors.Trace(err)
   576  	}
   577  	results := make([]*applicationPortRanges, len(docs))
   578  	for i, doc := range docs {
   579  		results[i] = &applicationPortRanges{
   580  			st:        st,
   581  			doc:       doc,
   582  			docExists: true,
   583  		}
   584  	}
   585  	return results, nil
   586  }