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