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

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	jc "github.com/juju/testing/checkers"
     9  	jujutxn "github.com/juju/txn/v3"
    10  	gc "gopkg.in/check.v1"
    11  
    12  	"github.com/juju/juju/core/network"
    13  	"github.com/juju/juju/state"
    14  	statetesting "github.com/juju/juju/state/testing"
    15  	"github.com/juju/juju/testing/factory"
    16  )
    17  
    18  type MachinePortsDocSuite struct {
    19  	ConnSuite
    20  	charm          *state.Charm
    21  	application    *state.Application
    22  	unit1          *state.Unit
    23  	unit2          *state.Unit
    24  	machine        *state.Machine
    25  	subnet         *state.Subnet
    26  	machPortRanges state.MachinePortRanges
    27  }
    28  
    29  var _ = gc.Suite(&MachinePortsDocSuite{})
    30  
    31  const allEndpoints = ""
    32  
    33  func (s *MachinePortsDocSuite) SetUpTest(c *gc.C) {
    34  	s.ConnSuite.SetUpTest(c)
    35  
    36  	s.charm = s.Factory.MakeCharm(c, &factory.CharmParams{Name: "wordpress"})
    37  	s.application = s.Factory.MakeApplication(c, &factory.ApplicationParams{Name: "wordpress", Charm: s.charm})
    38  	s.machine = s.Factory.MakeMachine(c, &factory.MachineParams{Base: state.UbuntuBase("12.10")})
    39  	s.unit1 = s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.application, Machine: s.machine})
    40  	s.unit2 = s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.application, Machine: s.machine})
    41  
    42  	var err error
    43  	s.subnet, err = s.State.AddSubnet(network.SubnetInfo{CIDR: "0.1.2.0/24"})
    44  	c.Assert(err, jc.ErrorIsNil)
    45  
    46  	s.machPortRanges, err = s.machine.OpenedPortRanges()
    47  	c.Assert(err, jc.ErrorIsNil)
    48  	c.Assert(s.machPortRanges.UniquePortRanges(), gc.HasLen, 0, gc.Commentf("expected no port ranges to be open for machine"))
    49  }
    50  
    51  func assertRefreshMachinePortsDoc(c *gc.C, p state.MachinePortRanges, errSatisfier func(error) bool) {
    52  	type refresher interface {
    53  		Refresh() error
    54  	}
    55  
    56  	portRefresher, supports := p.(refresher)
    57  	c.Assert(supports, jc.IsTrue, gc.Commentf("machine ports interface does not implement Refresh()"))
    58  
    59  	err := portRefresher.Refresh()
    60  	if errSatisfier != nil {
    61  		c.Assert(err, jc.Satisfies, errSatisfier)
    62  	} else {
    63  		c.Assert(err, jc.ErrorIsNil)
    64  	}
    65  }
    66  
    67  func assertRemoveMachinePortsDoc(c *gc.C, p state.MachinePortRanges) {
    68  	type remover interface {
    69  		Remove() error
    70  	}
    71  
    72  	portRemover, supports := p.(remover)
    73  	c.Assert(supports, jc.IsTrue, gc.Commentf("port document does not implement Remove()"))
    74  	c.Assert(portRemover.Remove(), jc.ErrorIsNil)
    75  }
    76  
    77  func assertMachinePortsPersisted(c *gc.C, p state.MachinePortRanges, persisted bool) {
    78  	type persistChecker interface {
    79  		Persisted() bool
    80  	}
    81  
    82  	checker, supports := p.(persistChecker)
    83  	c.Assert(supports, jc.IsTrue, gc.Commentf("machine ports interface does not implement Persisted()"))
    84  	c.Assert(checker.Persisted(), gc.Equals, persisted)
    85  }
    86  
    87  func (s *MachinePortsDocSuite) mustOpenCloseMachinePorts(c *gc.C, machPorts state.MachinePortRanges, unitName, endpointName string, openRanges, closeRanges []network.PortRange) {
    88  	err := s.openCloseMachinePorts(machPorts, unitName, endpointName, openRanges, closeRanges)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  }
    91  
    92  func (s *MachinePortsDocSuite) openCloseMachinePorts(machPorts state.MachinePortRanges, unitName, endpointName string, openRanges, closeRanges []network.PortRange) error {
    93  	unitPorts := machPorts.ForUnit(unitName)
    94  	for _, pr := range openRanges {
    95  		unitPorts.Open(endpointName, pr)
    96  	}
    97  	for _, pr := range closeRanges {
    98  		unitPorts.Close(endpointName, pr)
    99  	}
   100  	return s.State.ApplyOperation(machPorts.Changes())
   101  }
   102  
   103  func (s *MachinePortsDocSuite) TestModelAllOpenPortRanges(c *gc.C) {
   104  	toOpen := []network.PortRange{
   105  		network.MustParsePortRange("100-200/tcp"),
   106  		network.MustParsePortRange("300-400/tcp"),
   107  		network.MustParsePortRange("500-600/tcp"),
   108  	}
   109  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen[0:1], nil)
   110  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit2.Name(), allEndpoints, toOpen[1:2], nil)
   111  
   112  	// Add a second machine with another unit and open the last port range
   113  	mach2 := s.Factory.MakeMachine(c, &factory.MachineParams{Base: state.UbuntuBase("12.10")})
   114  	unit3 := s.Factory.MakeUnit(c, &factory.UnitParams{Application: s.application, Machine: mach2})
   115  	mach2Ports, err := mach2.OpenedPortRanges()
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	s.mustOpenCloseMachinePorts(c, mach2Ports, unit3.Name(), allEndpoints, toOpen[2:3], nil)
   118  
   119  	allMachinePortRanges, err := s.Model.OpenedPortRangesForAllMachines()
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	c.Assert(allMachinePortRanges, gc.HasLen, 2)
   122  
   123  	c.Assert(allMachinePortRanges[0].UniquePortRanges(), gc.DeepEquals, toOpen[0:2])
   124  	c.Assert(allMachinePortRanges[1].UniquePortRanges(), gc.DeepEquals, toOpen[2:])
   125  }
   126  
   127  func (s *MachinePortsDocSuite) TestOpenMachinePortsForWildcardEndpoint(c *gc.C) {
   128  	s.testOpenPortsForEndpoint(c, allEndpoints)
   129  }
   130  
   131  func (s *MachinePortsDocSuite) TestOpenMachinePortsForEndpoint(c *gc.C) {
   132  	s.testOpenPortsForEndpoint(c, "monitoring-port")
   133  }
   134  
   135  func (s *MachinePortsDocSuite) testOpenPortsForEndpoint(c *gc.C, endpoint string) {
   136  	toOpen := []network.PortRange{network.MustParsePortRange("100-200/tcp")}
   137  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), endpoint, toOpen, nil)
   138  
   139  	machPorts, err := s.machine.OpenedPortRanges()
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(machPorts.MachineID(), gc.Equals, s.machine.Id())
   142  	c.Assert(machPorts.UniquePortRanges(), gc.DeepEquals, toOpen)
   143  }
   144  
   145  func (s *MachinePortsDocSuite) TestOpenAndCloseMachinePorts(c *gc.C) {
   146  	type unitPortRange struct {
   147  		UnitName  string
   148  		PortRange network.PortRange
   149  	}
   150  	testCases := []struct {
   151  		about    string
   152  		existing []unitPortRange
   153  		toOpen   *unitPortRange
   154  		toClose  *unitPortRange
   155  		expected string
   156  	}{{
   157  		about:    "open and close same port range",
   158  		existing: nil,
   159  		toOpen: &unitPortRange{
   160  			PortRange: network.MustParsePortRange("100-200/tcp"),
   161  			UnitName:  s.unit1.Name(),
   162  		},
   163  		toClose: &unitPortRange{
   164  			PortRange: network.MustParsePortRange("100-200/tcp"),
   165  			UnitName:  s.unit1.Name(),
   166  		},
   167  		expected: "",
   168  	}, {
   169  		about:    "open and close icmp",
   170  		existing: nil,
   171  		toOpen: &unitPortRange{
   172  			PortRange: network.MustParsePortRange("icmp"),
   173  			UnitName:  s.unit1.Name(),
   174  		},
   175  		toClose: &unitPortRange{
   176  			PortRange: network.MustParsePortRange("icmp"),
   177  			UnitName:  s.unit1.Name(),
   178  		},
   179  		expected: "",
   180  	}, {
   181  		about: "try to close part of a port range",
   182  		existing: []unitPortRange{{
   183  			PortRange: network.MustParsePortRange("100-200/tcp"),
   184  			UnitName:  s.unit1.Name(),
   185  		}},
   186  		toOpen: nil,
   187  		toClose: &unitPortRange{
   188  			PortRange: network.MustParsePortRange("100-150/tcp"),
   189  			UnitName:  s.unit1.Name(),
   190  		},
   191  		expected: `cannot close ports 100-150/tcp: port ranges 100-200/tcp \("wordpress/0"\) and 100-150/tcp \("wordpress/0"\) conflict`,
   192  	}, {
   193  		about: "close a port range opened by another unit",
   194  		existing: []unitPortRange{{
   195  			PortRange: network.MustParsePortRange("100-150/tcp"),
   196  			UnitName:  s.unit2.Name(),
   197  		}},
   198  		toOpen: nil,
   199  		toClose: &unitPortRange{
   200  			PortRange: network.MustParsePortRange("100-150/tcp"),
   201  			UnitName:  s.unit1.Name(),
   202  		},
   203  		expected: `cannot close ports 100-150/tcp: port ranges 100-150/tcp \("wordpress/1"\) and 100-150/tcp \("wordpress/0"\) conflict`,
   204  	}, {
   205  		about: "open twice the same port range",
   206  		existing: []unitPortRange{{
   207  			PortRange: network.MustParsePortRange("100-150/tcp"),
   208  			UnitName:  s.unit1.Name(),
   209  		}},
   210  		toOpen: &unitPortRange{
   211  			PortRange: network.MustParsePortRange("100-150/tcp"),
   212  			UnitName:  s.unit1.Name(),
   213  		},
   214  		toClose:  nil,
   215  		expected: "",
   216  	}, {
   217  		about:    "close an unopened port range",
   218  		existing: nil,
   219  		toOpen:   nil,
   220  		toClose: &unitPortRange{
   221  			PortRange: network.MustParsePortRange("100-150/tcp"),
   222  			UnitName:  s.unit1.Name(),
   223  		},
   224  		expected: "",
   225  	}, {
   226  		about: "try to close an overlapping port range",
   227  		existing: []unitPortRange{{
   228  			PortRange: network.MustParsePortRange("100-200/tcp"),
   229  			UnitName:  s.unit1.Name(),
   230  		}},
   231  		toOpen: nil,
   232  		toClose: &unitPortRange{
   233  			PortRange: network.MustParsePortRange("100-300/tcp"),
   234  			UnitName:  s.unit1.Name(),
   235  		},
   236  		expected: `cannot close ports 100-300/tcp: port ranges 100-200/tcp \("wordpress/0"\) and 100-300/tcp \("wordpress/0"\) conflict`,
   237  	}, {
   238  		about: "try to open an overlapping port range with different unit",
   239  		existing: []unitPortRange{{
   240  			PortRange: network.MustParsePortRange("100-200/tcp"),
   241  			UnitName:  s.unit1.Name(),
   242  		}},
   243  		toOpen: &unitPortRange{
   244  			PortRange: network.MustParsePortRange("100-300/tcp"),
   245  			UnitName:  s.unit2.Name(),
   246  		},
   247  		expected: `cannot open ports 100-300/tcp: port ranges 100-200/tcp \("wordpress/0"\) and 100-300/tcp \("wordpress/1"\) conflict`,
   248  	}, {
   249  		about: "try to open an identical port range with different unit",
   250  		existing: []unitPortRange{{
   251  			PortRange: network.MustParsePortRange("100-200/tcp"),
   252  			UnitName:  s.unit1.Name(),
   253  		}},
   254  		toOpen: &unitPortRange{
   255  			PortRange: network.MustParsePortRange("100-200/tcp"),
   256  			UnitName:  s.unit2.Name(),
   257  		},
   258  		expected: `cannot open ports 100-200/tcp: port ranges 100-200/tcp \("wordpress/0"\) and 100-200/tcp \("wordpress/1"\) conflict`,
   259  	}, {
   260  		about: "try to open a port range with different protocol with different unit",
   261  		existing: []unitPortRange{{
   262  			PortRange: network.MustParsePortRange("100-200/tcp"),
   263  			UnitName:  s.unit1.Name(),
   264  		}},
   265  		toOpen: &unitPortRange{
   266  			PortRange: network.MustParsePortRange("100-200/udp"),
   267  			UnitName:  s.unit2.Name(),
   268  		},
   269  		expected: "",
   270  	}, {
   271  		about: "try to open a non-overlapping port range with different unit",
   272  		existing: []unitPortRange{{
   273  			PortRange: network.MustParsePortRange("100-200/tcp"), UnitName: s.unit1.Name(),
   274  		}},
   275  		toOpen: &unitPortRange{
   276  			PortRange: network.MustParsePortRange("300-400/tcp"),
   277  			UnitName:  s.unit2.Name(),
   278  		},
   279  		expected: "",
   280  	}}
   281  
   282  	for i, t := range testCases {
   283  		c.Logf("test %d: %s", i, t.about)
   284  
   285  		ports, err := s.machine.OpenedPortRanges()
   286  		c.Assert(err, jc.ErrorIsNil)
   287  
   288  		// open ports that should exist for the test case
   289  		for _, pr := range t.existing {
   290  			s.mustOpenCloseMachinePorts(c, ports, pr.UnitName, allEndpoints, []network.PortRange{pr.PortRange}, nil)
   291  		}
   292  		if len(t.existing) != 0 {
   293  			assertRefreshMachinePortsDoc(c, ports, nil)
   294  		}
   295  		if t.toOpen != nil {
   296  			err := s.openCloseMachinePorts(ports, t.toOpen.UnitName, allEndpoints, []network.PortRange{t.toOpen.PortRange}, nil)
   297  			if t.expected == "" {
   298  				c.Check(err, jc.ErrorIsNil)
   299  			} else {
   300  				c.Check(err, gc.ErrorMatches, t.expected)
   301  			}
   302  			assertRefreshMachinePortsDoc(c, ports, nil)
   303  		}
   304  
   305  		if t.toClose != nil {
   306  			err := s.openCloseMachinePorts(ports, t.toClose.UnitName, allEndpoints, nil, []network.PortRange{t.toClose.PortRange})
   307  			if t.expected == "" {
   308  				c.Check(err, jc.ErrorIsNil)
   309  			} else {
   310  				c.Check(err, gc.ErrorMatches, t.expected)
   311  			}
   312  		}
   313  		assertRemoveMachinePortsDoc(c, ports)
   314  	}
   315  }
   316  
   317  func (s *MachinePortsDocSuite) TestClosePortRangeOperationSucceedsForDyingModel(c *gc.C) {
   318  	// Open initial port range
   319  	toOpen := []network.PortRange{network.MustParsePortRange("200-210/tcp")}
   320  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen, nil)
   321  
   322  	defer state.SetBeforeHooks(c, s.State, func() {
   323  		c.Assert(s.Model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
   324  	}).Check()
   325  
   326  	// Close the initially opened ports
   327  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints,
   328  		nil,
   329  		[]network.PortRange{network.MustParsePortRange("200-210/tcp")},
   330  	)
   331  }
   332  
   333  func (s *MachinePortsDocSuite) TestComposedOpenCloseOperation(c *gc.C) {
   334  	// Open initial port range
   335  	toOpen := []network.PortRange{network.MustParsePortRange("200-210/tcp")}
   336  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen, nil)
   337  
   338  	// Run a composed open/close operation
   339  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints,
   340  		[]network.PortRange{network.MustParsePortRange("400-500/tcp")},
   341  		[]network.PortRange{network.MustParsePortRange("200-210/tcp")},
   342  	)
   343  
   344  	// Enumerate ports
   345  	assertRefreshMachinePortsDoc(c, s.machPortRanges, nil)
   346  	unitRanges := s.machPortRanges.ForUnit(s.unit1.Name()).ForEndpoint(allEndpoints)
   347  	c.Assert(unitRanges, gc.DeepEquals, []network.PortRange{network.MustParsePortRange("400-500/tcp")})
   348  
   349  	// If we open and close the same set of ports the port doc should be deleted.
   350  	assertRefreshMachinePortsDoc(c, s.machPortRanges, nil)
   351  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints,
   352  		[]network.PortRange{network.MustParsePortRange("400-500/tcp")},
   353  		[]network.PortRange{network.MustParsePortRange("400-500/tcp")},
   354  	)
   355  
   356  	// The next refresh should fail with ErrNotFound as the document has been removed.
   357  	assertRefreshMachinePortsDoc(c, s.machPortRanges, errors.IsNotFound)
   358  }
   359  
   360  func (s *MachinePortsDocSuite) TestComposedOpenCloseOperationNoEffectiveOps(c *gc.C) {
   361  	// Run a composed open/close operation
   362  	unitPortRanges := s.machPortRanges.ForUnit(s.unit1.Name())
   363  
   364  	// Duplicate range should be skipped
   365  	unitPortRanges.Open("monitoring-port", network.MustParsePortRange("400-500/tcp"))
   366  	unitPortRanges.Open("monitoring-port", network.MustParsePortRange("400-500/tcp"))
   367  
   368  	unitPortRanges.Close("monitoring-port", network.MustParsePortRange("400-500/tcp"))
   369  
   370  	// As the doc does not exist and the end result is still an empty port range
   371  	// this should return ErrNoOperations
   372  	_, err := s.machPortRanges.Changes().Build(0)
   373  	c.Assert(err, gc.Equals, jujutxn.ErrNoOperations)
   374  }
   375  
   376  func (s *MachinePortsDocSuite) TestICMP(c *gc.C) {
   377  	// Open initial port range
   378  	toOpen := []network.PortRange{network.MustParsePortRange("icmp")}
   379  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen, nil)
   380  
   381  	ranges := s.machPortRanges.ForUnit(s.unit1.Name()).UniquePortRanges()
   382  	c.Assert(ranges, gc.HasLen, 1)
   383  }
   384  
   385  func (s *MachinePortsDocSuite) TestOpenInvalidRange(c *gc.C) {
   386  	toOpen := []network.PortRange{{FromPort: 400, ToPort: 200, Protocol: "tcp"}}
   387  	err := s.openCloseMachinePorts(s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen, nil)
   388  	c.Assert(err, gc.ErrorMatches, `cannot open ports 400-200/tcp: invalid port range 400-200/tcp`)
   389  }
   390  
   391  func (s *MachinePortsDocSuite) TestCloseInvalidRange(c *gc.C) {
   392  	// Open initial port range
   393  	toOpen := []network.PortRange{network.MustParsePortRange("100-200/tcp")}
   394  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen, nil)
   395  
   396  	assertRefreshMachinePortsDoc(c, s.machPortRanges, nil)
   397  
   398  	toClose := []network.PortRange{{FromPort: 150, ToPort: 200, Protocol: "tcp"}}
   399  	err := s.openCloseMachinePorts(s.machPortRanges, s.unit1.Name(), allEndpoints, nil, toClose)
   400  	c.Assert(err, gc.ErrorMatches, `cannot close ports 150-200/tcp: port ranges 100-200/tcp \("wordpress/0"\) and 150-200/tcp \("wordpress/0"\) conflict`)
   401  }
   402  
   403  func (s *MachinePortsDocSuite) TestRemoveMachinePortsDoc(c *gc.C) {
   404  	// Open initial port range
   405  	toOpen := []network.PortRange{network.MustParsePortRange("100-200/tcp")}
   406  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, toOpen, nil)
   407  
   408  	// Remove document
   409  	assertRemoveMachinePortsDoc(c, s.machPortRanges)
   410  
   411  	// If we lookup the opened ports for the machine we should now get a blank doc.
   412  	machPorts, err := s.machine.OpenedPortRanges()
   413  	c.Assert(err, jc.ErrorIsNil)
   414  	assertMachinePortsPersisted(c, machPorts, false)
   415  }
   416  
   417  func (s *MachinePortsDocSuite) TestWatchMachinePorts(c *gc.C) {
   418  	// No port ranges open initially, no changes.
   419  	w := s.State.WatchOpenedPorts()
   420  	c.Assert(w, gc.NotNil)
   421  
   422  	defer statetesting.AssertStop(c, w)
   423  	wc := statetesting.NewStringsWatcherC(c, w)
   424  	// The first change we get is an empty one, as there are no ports
   425  	// opened yet and we need an initial event for the API watcher to
   426  	// work.
   427  	wc.AssertChange()
   428  	wc.AssertNoChange()
   429  
   430  	portRange := network.MustParsePortRange("100-200/tcp")
   431  
   432  	// Open a port range, detect a change.
   433  	expectChange := s.machine.Id()
   434  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, []network.PortRange{portRange}, nil)
   435  	wc.AssertChange(expectChange)
   436  	wc.AssertNoChange()
   437  
   438  	// Close the port range, detect a change.
   439  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, nil, []network.PortRange{portRange})
   440  	wc.AssertChange(expectChange)
   441  	wc.AssertNoChange()
   442  
   443  	// Close the port range again, no changes.
   444  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, nil, []network.PortRange{portRange})
   445  	wc.AssertNoChange()
   446  
   447  	// Open another range, detect a change.
   448  	portRange = network.MustParsePortRange("999-1999/udp")
   449  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), "monitoring-port", []network.PortRange{portRange}, nil)
   450  	wc.AssertChange(expectChange)
   451  	wc.AssertNoChange()
   452  
   453  	// Open the same range again, no changes.
   454  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), "monitoring-port", []network.PortRange{portRange}, nil)
   455  	wc.AssertNoChange()
   456  
   457  	// Open another range, detect a change.
   458  	otherRange := network.MustParsePortRange("1-100/tcp")
   459  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, []network.PortRange{otherRange}, nil)
   460  	wc.AssertChange(expectChange)
   461  	wc.AssertNoChange()
   462  
   463  	// Close the other range, detect a change.
   464  	s.mustOpenCloseMachinePorts(c, s.machPortRanges, s.unit1.Name(), allEndpoints, nil, []network.PortRange{otherRange})
   465  	wc.AssertChange(expectChange)
   466  	wc.AssertNoChange()
   467  
   468  	// Remove the ports document, detect a change.
   469  	assertRemoveMachinePortsDoc(c, s.machPortRanges)
   470  	wc.AssertChange(expectChange)
   471  	wc.AssertNoChange()
   472  
   473  	// And again - no change.
   474  	assertRemoveMachinePortsDoc(c, s.machPortRanges)
   475  	wc.AssertNoChange()
   476  }
   477  
   478  func (s *MachinePortsDocSuite) TestChangesForIndividualUnits(c *gc.C) {
   479  	unit1PortRanges := s.machPortRanges.ForUnit(s.unit1.Name())
   480  	unit1PortRanges.Open(allEndpoints, network.MustParsePortRange("100-200/tcp"))
   481  
   482  	unit2PortRanges := s.machPortRanges.ForUnit(s.unit2.Name())
   483  	unit2PortRanges.Open(allEndpoints, network.MustParsePortRange("8080/tcp"))
   484  
   485  	// Apply changes scoped to unit 1. The recorded changes for unit 2
   486  	// in the machine port ranges instance should remain intact.
   487  	c.Assert(s.State.ApplyOperation(unit1PortRanges.Changes()), jc.ErrorIsNil)
   488  
   489  	// Check that the existing machine port ranges instance reflects the
   490  	// unit 1 changes we just applied.
   491  	c.Assert(s.machPortRanges.UniquePortRanges(), gc.DeepEquals, []network.PortRange{
   492  		network.MustParsePortRange("100-200/tcp"),
   493  	}, gc.Commentf("machine port ranges instance not updated correctly after unit-scoped port change application"))
   494  
   495  	// Grab a fresh copy of the machine ranges and verify the expected ports
   496  	// have been correctly persisted.
   497  	freshMachPortRanges, err := s.machine.OpenedPortRanges()
   498  	c.Assert(err, jc.ErrorIsNil)
   499  	c.Assert(freshMachPortRanges.UniquePortRanges(), gc.DeepEquals, []network.PortRange{
   500  		network.MustParsePortRange("100-200/tcp"),
   501  	}, gc.Commentf("unit 1 changes were not correctly persisted to DB"))
   502  
   503  	// Apply pending changes scoped to unit 2.
   504  	c.Assert(s.State.ApplyOperation(unit2PortRanges.Changes()), jc.ErrorIsNil)
   505  
   506  	// Check that the existing machine port ranges instance reflects both
   507  	// unit 1 and unit 2 changes
   508  	c.Assert(s.machPortRanges.UniquePortRanges(), gc.DeepEquals, []network.PortRange{
   509  		network.MustParsePortRange("100-200/tcp"),
   510  		network.MustParsePortRange("8080/tcp"),
   511  	}, gc.Commentf("machine port ranges instance not updated correctly after unit-scoped port change application"))
   512  
   513  	// Grab a fresh copy of the machine ranges and verify the expected ports
   514  	// have been correctly persisted.
   515  	freshMachPortRanges, err = s.machine.OpenedPortRanges()
   516  	c.Assert(err, jc.ErrorIsNil)
   517  	c.Assert(freshMachPortRanges.UniquePortRanges(), gc.DeepEquals, []network.PortRange{
   518  		network.MustParsePortRange("100-200/tcp"),
   519  		network.MustParsePortRange("8080/tcp"),
   520  	}, gc.Commentf("unit changes were not correctly persisted to DB"))
   521  
   522  	// Verify that if we call changes on the machine ports instance we
   523  	// get no ops as everything has been committed.
   524  	_, err = s.machPortRanges.Changes().Build(0)
   525  	c.Assert(err, gc.Equals, jujutxn.ErrNoOperations, gc.Commentf("machine port range was not synced correctly after applying changes"))
   526  }