github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/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  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/network"
    15  	"github.com/juju/juju/state"
    16  	statetesting "github.com/juju/juju/state/testing"
    17  	"github.com/juju/juju/testing/factory"
    18  )
    19  
    20  type PortsDocSuite struct {
    21  	ConnSuite
    22  	charm              *state.Charm
    23  	service            *state.Application
    24  	unit1              *state.Unit
    25  	unit2              *state.Unit
    26  	machine            *state.Machine
    27  	subnet             *state.Subnet
    28  	portsOnSubnet      *state.Ports
    29  	portsWithoutSubnet *state.Ports
    30  }
    31  
    32  var _ = gc.Suite(&PortsDocSuite{})
    33  
    34  func (s *PortsDocSuite) SetUpTest(c *gc.C) {
    35  	s.ConnSuite.SetUpTest(c)
    36  
    37  	f := factory.NewFactory(s.State)
    38  	s.charm = f.MakeCharm(c, &factory.CharmParams{Name: "wordpress"})
    39  	s.service = f.MakeApplication(c, &factory.ApplicationParams{Name: "wordpress", Charm: s.charm})
    40  	s.machine = f.MakeMachine(c, &factory.MachineParams{Series: "quantal"})
    41  	s.unit1 = f.MakeUnit(c, &factory.UnitParams{Application: s.service, Machine: s.machine})
    42  	s.unit2 = f.MakeUnit(c, &factory.UnitParams{Application: s.service, Machine: s.machine})
    43  
    44  	var err error
    45  	s.subnet, err = s.State.AddSubnet(state.SubnetInfo{CIDR: "0.1.2.0/24"})
    46  	c.Assert(err, jc.ErrorIsNil)
    47  
    48  	s.portsOnSubnet, err = state.GetOrCreatePorts(s.State, s.machine.Id(), s.subnet.CIDR())
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	c.Assert(s.portsOnSubnet, gc.NotNil)
    51  
    52  	s.portsWithoutSubnet, err = state.GetOrCreatePorts(s.State, s.machine.Id(), "")
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	c.Assert(s.portsWithoutSubnet, gc.NotNil)
    55  }
    56  
    57  func (s *PortsDocSuite) TestCreatePortsWithSubnet(c *gc.C) {
    58  	s.testCreatePortsWithSubnetID(c, s.subnet.CIDR())
    59  }
    60  
    61  func (s *PortsDocSuite) testCreatePortsWithSubnetID(c *gc.C, subnetID string) {
    62  	ports, err := state.GetOrCreatePorts(s.State, s.machine.Id(), subnetID)
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	c.Assert(ports, gc.NotNil)
    65  	err = ports.OpenPorts(state.PortRange{
    66  		FromPort: 100,
    67  		ToPort:   200,
    68  		UnitName: s.unit1.Name(),
    69  		Protocol: "TCP",
    70  	})
    71  	c.Assert(err, jc.ErrorIsNil)
    72  
    73  	ports, err = state.GetPorts(s.State, s.machine.Id(), subnetID)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	c.Assert(ports, gc.NotNil)
    76  
    77  	c.Assert(ports.PortsForUnit(s.unit1.Name()), gc.HasLen, 1)
    78  }
    79  
    80  func (s *PortsDocSuite) TestCreatePortsWithoutSubnet(c *gc.C) {
    81  	s.testCreatePortsWithSubnetID(c, "")
    82  }
    83  
    84  func (s *PortsDocSuite) TestOpenAndClosePorts(c *gc.C) {
    85  
    86  	testCases := []struct {
    87  		about    string
    88  		existing []state.PortRange
    89  		open     *state.PortRange
    90  		close    *state.PortRange
    91  		expected string
    92  	}{{
    93  		about:    "open and close same port range",
    94  		existing: nil,
    95  		open: &state.PortRange{
    96  			FromPort: 100,
    97  			ToPort:   200,
    98  			UnitName: s.unit1.Name(),
    99  			Protocol: "TCP",
   100  		},
   101  		close: &state.PortRange{
   102  			FromPort: 100,
   103  			ToPort:   200,
   104  			UnitName: s.unit1.Name(),
   105  			Protocol: "TCP",
   106  		},
   107  		expected: "",
   108  	}, {
   109  		about: "try to close part of a port range",
   110  		existing: []state.PortRange{{
   111  			FromPort: 100,
   112  			ToPort:   200,
   113  			UnitName: s.unit1.Name(),
   114  			Protocol: "TCP",
   115  		}},
   116  		open: nil,
   117  		close: &state.PortRange{
   118  			FromPort: 100,
   119  			ToPort:   150,
   120  			UnitName: s.unit1.Name(),
   121  			Protocol: "TCP",
   122  		},
   123  		expected: `cannot close ports 100-150/tcp \("wordpress/0"\): port ranges 100-200/tcp \("wordpress/0"\) and 100-150/tcp \("wordpress/0"\) conflict`,
   124  	}, {
   125  		about: "close an unopened port range with existing clash from other unit",
   126  		existing: []state.PortRange{{
   127  			FromPort: 100,
   128  			ToPort:   150,
   129  			UnitName: s.unit2.Name(),
   130  			Protocol: "TCP",
   131  		}},
   132  		open: nil,
   133  		close: &state.PortRange{
   134  			FromPort: 100,
   135  			ToPort:   150,
   136  			UnitName: s.unit1.Name(),
   137  			Protocol: "TCP",
   138  		},
   139  		expected: "",
   140  	}, {
   141  		about: "open twice the same port range",
   142  		existing: []state.PortRange{{
   143  			FromPort: 100,
   144  			ToPort:   150,
   145  			UnitName: s.unit1.Name(),
   146  			Protocol: "TCP",
   147  		}},
   148  		open: &state.PortRange{
   149  			FromPort: 100,
   150  			ToPort:   150,
   151  			UnitName: s.unit1.Name(),
   152  			Protocol: "TCP",
   153  		},
   154  		close:    nil,
   155  		expected: "",
   156  	}, {
   157  		about:    "close an unopened port range",
   158  		existing: nil,
   159  		open:     nil,
   160  		close: &state.PortRange{
   161  			FromPort: 100,
   162  			ToPort:   150,
   163  			UnitName: s.unit1.Name(),
   164  			Protocol: "TCP",
   165  		},
   166  		expected: "",
   167  	}, {
   168  		about: "try to close an overlapping port range",
   169  		existing: []state.PortRange{{
   170  			FromPort: 100,
   171  			ToPort:   200,
   172  			UnitName: s.unit1.Name(),
   173  			Protocol: "TCP",
   174  		}},
   175  		open: nil,
   176  		close: &state.PortRange{
   177  			FromPort: 100,
   178  			ToPort:   300,
   179  			UnitName: s.unit1.Name(),
   180  			Protocol: "TCP",
   181  		},
   182  		expected: `cannot close ports 100-300/tcp \("wordpress/0"\): port ranges 100-200/tcp \("wordpress/0"\) and 100-300/tcp \("wordpress/0"\) conflict`,
   183  	}, {
   184  		about: "try to open an overlapping port range with different unit",
   185  		existing: []state.PortRange{{
   186  			FromPort: 100,
   187  			ToPort:   200,
   188  			UnitName: s.unit1.Name(),
   189  			Protocol: "TCP",
   190  		}},
   191  		open: &state.PortRange{
   192  			FromPort: 100,
   193  			ToPort:   300,
   194  			UnitName: s.unit2.Name(),
   195  			Protocol: "TCP",
   196  		},
   197  		expected: `cannot open ports 100-300/tcp \("wordpress/1"\): port ranges 100-200/tcp \("wordpress/0"\) and 100-300/tcp \("wordpress/1"\) conflict`,
   198  	}, {
   199  		about: "try to open an identical port range with different unit",
   200  		existing: []state.PortRange{{
   201  			FromPort: 100,
   202  			ToPort:   200,
   203  			UnitName: s.unit1.Name(),
   204  			Protocol: "TCP",
   205  		}},
   206  		open: &state.PortRange{
   207  			FromPort: 100,
   208  			ToPort:   200,
   209  			UnitName: s.unit2.Name(),
   210  			Protocol: "TCP",
   211  		},
   212  		expected: `cannot open ports 100-200/tcp \("wordpress/1"\): port ranges 100-200/tcp \("wordpress/0"\) and 100-200/tcp \("wordpress/1"\) conflict`,
   213  	}, {
   214  		about: "try to open a port range with different protocol with different unit",
   215  		existing: []state.PortRange{{
   216  			FromPort: 100,
   217  			ToPort:   200,
   218  			UnitName: s.unit1.Name(),
   219  			Protocol: "TCP",
   220  		}},
   221  		open: &state.PortRange{
   222  			FromPort: 100,
   223  			ToPort:   200,
   224  			UnitName: s.unit2.Name(),
   225  			Protocol: "UDP",
   226  		},
   227  		expected: "",
   228  	}, {
   229  		about: "try to open a non-overlapping port range with different unit",
   230  		existing: []state.PortRange{{
   231  			FromPort: 100,
   232  			ToPort:   200,
   233  			UnitName: s.unit1.Name(),
   234  			Protocol: "TCP",
   235  		}},
   236  		open: &state.PortRange{
   237  			FromPort: 300,
   238  			ToPort:   400,
   239  			UnitName: s.unit2.Name(),
   240  			Protocol: "TCP",
   241  		},
   242  		expected: "",
   243  	}}
   244  
   245  	for i, t := range testCases {
   246  		c.Logf("test %d: %s", i, t.about)
   247  
   248  		ports, err := state.GetOrCreatePorts(s.State, s.machine.Id(), s.subnet.CIDR())
   249  		c.Assert(err, jc.ErrorIsNil)
   250  		c.Assert(ports, gc.NotNil)
   251  
   252  		// open ports that should exist for the test case
   253  		for _, portRange := range t.existing {
   254  			err := ports.OpenPorts(portRange)
   255  			c.Check(err, jc.ErrorIsNil)
   256  		}
   257  		if t.existing != nil {
   258  			err = ports.Refresh()
   259  			c.Check(err, jc.ErrorIsNil)
   260  		}
   261  		if t.open != nil {
   262  			err = ports.OpenPorts(*t.open)
   263  			if t.expected == "" {
   264  				c.Check(err, jc.ErrorIsNil)
   265  			} else {
   266  				c.Check(err, gc.ErrorMatches, t.expected)
   267  			}
   268  			err = ports.Refresh()
   269  			c.Check(err, jc.ErrorIsNil)
   270  
   271  		}
   272  
   273  		if t.close != nil {
   274  			err := ports.ClosePorts(*t.close)
   275  			if t.expected == "" {
   276  				c.Check(err, jc.ErrorIsNil)
   277  			} else {
   278  				c.Check(err, gc.ErrorMatches, t.expected)
   279  			}
   280  		}
   281  		err = ports.Remove()
   282  		c.Check(err, jc.ErrorIsNil)
   283  	}
   284  }
   285  
   286  func (s *PortsDocSuite) TestAllPortRanges(c *gc.C) {
   287  	portRange := state.PortRange{
   288  		FromPort: 100,
   289  		ToPort:   200,
   290  		UnitName: s.unit1.Name(),
   291  		Protocol: "TCP",
   292  	}
   293  	err := s.portsWithoutSubnet.OpenPorts(portRange)
   294  	c.Assert(err, jc.ErrorIsNil)
   295  
   296  	ranges := s.portsWithoutSubnet.AllPortRanges()
   297  	c.Assert(ranges, gc.HasLen, 1)
   298  
   299  	c.Assert(ranges[network.PortRange{100, 200, "TCP"}], gc.Equals, s.unit1.Name())
   300  }
   301  
   302  func (s *PortsDocSuite) TestOpenInvalidRange(c *gc.C) {
   303  	portRange := state.PortRange{
   304  		FromPort: 400,
   305  		ToPort:   200,
   306  		UnitName: s.unit1.Name(),
   307  		Protocol: "TCP",
   308  	}
   309  	err := s.portsWithoutSubnet.OpenPorts(portRange)
   310  	c.Assert(err, gc.ErrorMatches, `cannot open ports 400-200/tcp \("wordpress/0"\): invalid port range 400-200`)
   311  }
   312  
   313  func (s *PortsDocSuite) TestCloseInvalidRange(c *gc.C) {
   314  	portRange := state.PortRange{
   315  		FromPort: 100,
   316  		ToPort:   200,
   317  		UnitName: s.unit1.Name(),
   318  		Protocol: "TCP",
   319  	}
   320  	err := s.portsWithoutSubnet.OpenPorts(portRange)
   321  	c.Assert(err, jc.ErrorIsNil)
   322  
   323  	err = s.portsWithoutSubnet.Refresh()
   324  	c.Assert(err, jc.ErrorIsNil)
   325  	err = s.portsWithoutSubnet.ClosePorts(state.PortRange{
   326  		FromPort: 150,
   327  		ToPort:   200,
   328  		UnitName: s.unit1.Name(),
   329  		Protocol: "TCP",
   330  	})
   331  	c.Assert(err, gc.ErrorMatches, `cannot close ports 150-200/tcp \("wordpress/0"\): port ranges 100-200/tcp \("wordpress/0"\) and 150-200/tcp \("wordpress/0"\) conflict`)
   332  }
   333  
   334  func (s *PortsDocSuite) TestRemovePortsDoc(c *gc.C) {
   335  	portRange := state.PortRange{
   336  		FromPort: 100,
   337  		ToPort:   200,
   338  		UnitName: s.unit1.Name(),
   339  		Protocol: "TCP",
   340  	}
   341  	err := s.portsOnSubnet.OpenPorts(portRange)
   342  	c.Assert(err, jc.ErrorIsNil)
   343  
   344  	ports, err := state.GetPorts(s.State, s.machine.Id(), s.subnet.CIDR())
   345  	c.Assert(err, jc.ErrorIsNil)
   346  	c.Assert(ports, gc.NotNil)
   347  
   348  	allPorts, err := s.machine.AllPorts()
   349  	c.Assert(err, jc.ErrorIsNil)
   350  
   351  	for _, prt := range allPorts {
   352  		err := prt.Remove()
   353  		c.Assert(err, jc.ErrorIsNil)
   354  	}
   355  
   356  	ports, err = state.GetPorts(s.State, s.machine.Id(), s.subnet.CIDR())
   357  	c.Assert(ports, gc.IsNil)
   358  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   359  	c.Assert(err, gc.ErrorMatches, `ports for machine "0", subnet "0.1.2.0/24" not found`)
   360  }
   361  
   362  func (s *PortsDocSuite) TestWatchPorts(c *gc.C) {
   363  	// No port ranges open initially, no changes.
   364  	w := s.State.WatchOpenedPorts()
   365  	c.Assert(w, gc.NotNil)
   366  
   367  	defer statetesting.AssertStop(c, w)
   368  	wc := statetesting.NewStringsWatcherC(c, s.State, w)
   369  	// The first change we get is an empty one, as there are no ports
   370  	// opened yet and we need an initial event for the API watcher to
   371  	// work.
   372  	wc.AssertChange()
   373  	wc.AssertNoChange()
   374  
   375  	portRange := state.PortRange{
   376  		FromPort: 100,
   377  		ToPort:   200,
   378  		UnitName: s.unit1.Name(),
   379  		Protocol: "TCP",
   380  	}
   381  	expectChange := fmt.Sprintf("%s:%s", s.machine.Id(), s.subnet.CIDR())
   382  	// Open a port range, detect a change.
   383  	err := s.portsOnSubnet.OpenPorts(portRange)
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	wc.AssertChange(expectChange)
   386  	wc.AssertNoChange()
   387  
   388  	// Close the port range, detect a change.
   389  	err = s.portsOnSubnet.ClosePorts(portRange)
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	wc.AssertChange(expectChange)
   392  	wc.AssertNoChange()
   393  
   394  	// Close the port range again, no changes.
   395  	err = s.portsOnSubnet.ClosePorts(portRange)
   396  	c.Assert(err, jc.ErrorIsNil)
   397  	wc.AssertNoChange()
   398  
   399  	// Open another range, detect a change.
   400  	portRange = state.PortRange{
   401  		FromPort: 999,
   402  		ToPort:   1999,
   403  		UnitName: s.unit2.Name(),
   404  		Protocol: "udp",
   405  	}
   406  	err = s.portsOnSubnet.OpenPorts(portRange)
   407  	c.Assert(err, jc.ErrorIsNil)
   408  	wc.AssertChange(expectChange)
   409  	wc.AssertNoChange()
   410  
   411  	// Open the same range again, no changes.
   412  	err = s.portsOnSubnet.OpenPorts(portRange)
   413  	c.Assert(err, jc.ErrorIsNil)
   414  	wc.AssertNoChange()
   415  
   416  	// Open another range, detect a change.
   417  	otherRange := state.PortRange{
   418  		FromPort: 1,
   419  		ToPort:   100,
   420  		UnitName: s.unit1.Name(),
   421  		Protocol: "tcp",
   422  	}
   423  	err = s.portsOnSubnet.OpenPorts(otherRange)
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	wc.AssertChange(expectChange)
   426  	wc.AssertNoChange()
   427  
   428  	// Close the other range, detect a change.
   429  	err = s.portsOnSubnet.ClosePorts(otherRange)
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	wc.AssertChange(expectChange)
   432  	wc.AssertNoChange()
   433  
   434  	// Remove the ports document, detect a change.
   435  	err = s.portsOnSubnet.Remove()
   436  	c.Assert(err, jc.ErrorIsNil)
   437  	wc.AssertChange(expectChange)
   438  	wc.AssertNoChange()
   439  
   440  	// And again - no change.
   441  	err = s.portsOnSubnet.Remove()
   442  	c.Assert(err, jc.ErrorIsNil)
   443  	wc.AssertNoChange()
   444  }
   445  
   446  type PortRangeSuite struct{}
   447  
   448  var _ = gc.Suite(&PortRangeSuite{})
   449  
   450  // Create a port range or panic if it is invalid.
   451  func MustPortRange(unitName string, fromPort, toPort int, protocol string) state.PortRange {
   452  	portRange, err := state.NewPortRange(unitName, fromPort, toPort, protocol)
   453  	if err != nil {
   454  		panic(err)
   455  	}
   456  	return portRange
   457  }
   458  
   459  func (p *PortRangeSuite) TestPortRangeConflicts(c *gc.C) {
   460  	var testCases = []struct {
   461  		about    string
   462  		first    state.PortRange
   463  		second   state.PortRange
   464  		expected interface{}
   465  	}{{
   466  		"identical ports",
   467  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   468  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   469  		nil,
   470  	}, {
   471  		"identical port ranges",
   472  		MustPortRange("wordpress/0", 80, 100, "TCP"),
   473  		MustPortRange("wordpress/0", 80, 100, "TCP"),
   474  		nil,
   475  	}, {
   476  		"different ports",
   477  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   478  		MustPortRange("wordpress/0", 90, 90, "TCP"),
   479  		nil,
   480  	}, {
   481  		"touching ranges",
   482  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   483  		MustPortRange("wordpress/0", 201, 240, "TCP"),
   484  		nil,
   485  	}, {
   486  		"touching ranges with overlap",
   487  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   488  		MustPortRange("wordpress/0", 200, 240, "TCP"),
   489  		"port ranges .* conflict",
   490  	}, {
   491  		"identical ports with different protocols",
   492  		MustPortRange("wordpress/0", 80, 80, "UDP"),
   493  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   494  		nil,
   495  	}, {
   496  		"overlapping ranges with different protocols",
   497  		MustPortRange("wordpress/0", 80, 200, "UDP"),
   498  		MustPortRange("wordpress/0", 80, 300, "TCP"),
   499  		nil,
   500  	}, {
   501  		"outside range",
   502  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   503  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   504  		nil,
   505  	}, {
   506  		"overlap end",
   507  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   508  		MustPortRange("wordpress/0", 80, 120, "TCP"),
   509  		"port ranges .* conflict",
   510  	}, {
   511  		"complete overlap",
   512  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   513  		MustPortRange("wordpress/0", 120, 140, "TCP"),
   514  		"port ranges .* conflict",
   515  	}, {
   516  		"overlap with same end",
   517  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   518  		MustPortRange("wordpress/0", 120, 200, "TCP"),
   519  		"port ranges .* conflict",
   520  	}, {
   521  		"overlap with same start",
   522  		MustPortRange("wordpress/0", 100, 200, "TCP"),
   523  		MustPortRange("wordpress/0", 100, 120, "TCP"),
   524  		"port ranges .* conflict",
   525  	}, {
   526  		"invalid port range",
   527  		state.PortRange{"wordpress/0", 100, 80, "TCP"},
   528  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   529  		"invalid port range 100-80",
   530  	}, {
   531  		"different units, same port",
   532  		MustPortRange("mysql/0", 80, 80, "TCP"),
   533  		MustPortRange("wordpress/0", 80, 80, "TCP"),
   534  		"port ranges .* conflict",
   535  	}, {
   536  		"different units, different port ranges",
   537  		MustPortRange("mysql/0", 80, 100, "TCP"),
   538  		MustPortRange("wordpress/0", 180, 280, "TCP"),
   539  		nil,
   540  	}, {
   541  		"different units, overlapping port ranges",
   542  		MustPortRange("mysql/0", 80, 100, "TCP"),
   543  		MustPortRange("wordpress/0", 90, 280, "TCP"),
   544  		"port ranges .* conflict",
   545  	}}
   546  
   547  	for i, t := range testCases {
   548  		c.Logf("test %d: %s", i, t.about)
   549  		if t.expected == nil {
   550  			c.Check(t.first.CheckConflicts(t.second), gc.IsNil)
   551  			c.Check(t.second.CheckConflicts(t.first), gc.IsNil)
   552  		} else if _, isString := t.expected.(string); isString {
   553  			c.Check(t.first.CheckConflicts(t.second), gc.ErrorMatches, t.expected.(string))
   554  			c.Check(t.second.CheckConflicts(t.first), gc.ErrorMatches, t.expected.(string))
   555  		}
   556  		// change test case protocols and test again
   557  		c.Logf("test %d: %s (after protocol swap)", i, t.about)
   558  		t.first.Protocol = swapProtocol(t.first.Protocol)
   559  		t.second.Protocol = swapProtocol(t.second.Protocol)
   560  		c.Logf("%+v %+v %v", t.first, t.second, t.expected)
   561  		if t.expected == nil {
   562  			c.Check(t.first.CheckConflicts(t.second), gc.IsNil)
   563  			c.Check(t.second.CheckConflicts(t.first), gc.IsNil)
   564  		} else if _, isString := t.expected.(string); isString {
   565  			c.Check(t.first.CheckConflicts(t.second), gc.ErrorMatches, t.expected.(string))
   566  			c.Check(t.second.CheckConflicts(t.first), gc.ErrorMatches, t.expected.(string))
   567  		}
   568  
   569  	}
   570  }
   571  
   572  func swapProtocol(protocol string) string {
   573  	if strings.ToLower(protocol) == "tcp" {
   574  		return "udp"
   575  	}
   576  	if strings.ToLower(protocol) == "udp" {
   577  		return "tcp"
   578  	}
   579  	return protocol
   580  }
   581  
   582  func (p *PortRangeSuite) TestPortRangeString(c *gc.C) {
   583  	c.Assert(state.PortRange{"wordpress/42", 80, 80, "TCP"}.String(),
   584  		gc.Equals,
   585  		`80-80/tcp ("wordpress/42")`,
   586  	)
   587  	c.Assert(state.PortRange{"wordpress/0", 80, 100, "TCP"}.String(),
   588  		gc.Equals,
   589  		`80-100/tcp ("wordpress/0")`,
   590  	)
   591  }
   592  
   593  func (p *PortRangeSuite) TestPortRangeValidityAndLength(c *gc.C) {
   594  	testCases := []struct {
   595  		about        string
   596  		ports        state.PortRange
   597  		expectLength int
   598  		expectedErr  string
   599  	}{{
   600  		"single valid port",
   601  		state.PortRange{"wordpress/0", 80, 80, "tcp"},
   602  		1,
   603  		"",
   604  	}, {
   605  		"valid tcp port range",
   606  		state.PortRange{"wordpress/0", 80, 90, "tcp"},
   607  		11,
   608  		"",
   609  	}, {
   610  		"valid udp port range",
   611  		state.PortRange{"wordpress/0", 80, 90, "UDP"},
   612  		11,
   613  		"",
   614  	}, {
   615  		"invalid port range boundaries",
   616  		state.PortRange{"wordpress/0", 90, 80, "tcp"},
   617  		0,
   618  		"invalid port range.*",
   619  	}, {
   620  		"invalid protocol",
   621  		state.PortRange{"wordpress/0", 80, 80, "some protocol"},
   622  		0,
   623  		"invalid protocol.*",
   624  	}, {
   625  		"invalid unit",
   626  		state.PortRange{"invalid unit", 80, 80, "tcp"},
   627  		0,
   628  		"invalid unit.*",
   629  	}, {
   630  		"negative lower bound",
   631  		state.PortRange{"wordpress/0", -10, 10, "tcp"},
   632  		0,
   633  		"port range bounds must be between 1 and 65535.*",
   634  	}, {
   635  		"zero lower bound",
   636  		state.PortRange{"wordpress/0", 0, 10, "tcp"},
   637  		0,
   638  		"port range bounds must be between 1 and 65535.*",
   639  	}, {
   640  		"negative upper bound",
   641  		state.PortRange{"wordpress/0", 10, -10, "tcp"},
   642  		0,
   643  		"invalid port range.*",
   644  	}, {
   645  		"zero upper bound",
   646  		state.PortRange{"wordpress/0", 10, 0, "tcp"},
   647  		0,
   648  		"invalid port range.*",
   649  	}, {
   650  		"too large lower bound",
   651  		state.PortRange{"wordpress/0", 65540, 99999, "tcp"},
   652  		0,
   653  		"port range bounds must be between 1 and 65535.*",
   654  	}, {
   655  		"too large upper bound",
   656  		state.PortRange{"wordpress/0", 10, 99999, "tcp"},
   657  		0,
   658  		"port range bounds must be between 1 and 65535.*",
   659  	}, {
   660  		"longest valid range",
   661  		state.PortRange{"wordpress/0", 1, 65535, "tcp"},
   662  		65535,
   663  		"",
   664  	}}
   665  
   666  	for i, t := range testCases {
   667  		c.Logf("test %d: %s", i, t.about)
   668  		c.Check(t.ports.Length(), gc.Equals, t.expectLength)
   669  		if t.expectedErr == "" {
   670  			c.Check(t.ports.Validate(), gc.IsNil)
   671  		} else {
   672  			c.Check(t.ports.Validate(), gc.ErrorMatches, t.expectedErr)
   673  		}
   674  	}
   675  }
   676  
   677  func (p *PortRangeSuite) TestSanitizeBounds(c *gc.C) {
   678  	tests := []struct {
   679  		about  string
   680  		input  state.PortRange
   681  		output state.PortRange
   682  	}{{
   683  		"valid range",
   684  		state.PortRange{"", 100, 200, ""},
   685  		state.PortRange{"", 100, 200, ""},
   686  	}, {
   687  		"negative lower bound",
   688  		state.PortRange{"", -10, 10, ""},
   689  		state.PortRange{"", 1, 10, ""},
   690  	}, {
   691  		"zero lower bound",
   692  		state.PortRange{"", 0, 10, ""},
   693  		state.PortRange{"", 1, 10, ""},
   694  	}, {
   695  		"negative upper bound",
   696  		state.PortRange{"", 42, -20, ""},
   697  		state.PortRange{"", 1, 42, ""},
   698  	}, {
   699  		"zero upper bound",
   700  		state.PortRange{"", 42, 0, ""},
   701  		state.PortRange{"", 1, 42, ""},
   702  	}, {
   703  		"both bounds negative",
   704  		state.PortRange{"", -10, -20, ""},
   705  		state.PortRange{"", 1, 1, ""},
   706  	}, {
   707  		"both bounds zero",
   708  		state.PortRange{"", 0, 0, ""},
   709  		state.PortRange{"", 1, 1, ""},
   710  	}, {
   711  		"swapped bounds",
   712  		state.PortRange{"", 20, 10, ""},
   713  		state.PortRange{"", 10, 20, ""},
   714  	}, {
   715  		"too large upper bound",
   716  		state.PortRange{"", 20, 99999, ""},
   717  		state.PortRange{"", 20, 65535, ""},
   718  	}, {
   719  		"too large lower bound",
   720  		state.PortRange{"", 99999, 10, ""},
   721  		state.PortRange{"", 10, 65535, ""},
   722  	}, {
   723  		"both bounds too large",
   724  		state.PortRange{"", 88888, 99999, ""},
   725  		state.PortRange{"", 65535, 65535, ""},
   726  	}, {
   727  		"lower negative, upper too large",
   728  		state.PortRange{"", -10, 99999, ""},
   729  		state.PortRange{"", 1, 65535, ""},
   730  	}, {
   731  		"lower zero, upper too large",
   732  		state.PortRange{"", 0, 99999, ""},
   733  		state.PortRange{"", 1, 65535, ""},
   734  	}}
   735  	for i, t := range tests {
   736  		c.Logf("test %d: %s", i, t.about)
   737  		c.Check(t.input.SanitizeBounds(), jc.DeepEquals, t.output)
   738  	}
   739  }