github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/ports.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package context
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/errors"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/network"
    14  )
    15  
    16  // PortRangeInfo contains information about a pending open- or
    17  // close-port operation for a port range. This is only exported for
    18  // testing.
    19  type PortRangeInfo struct {
    20  	ShouldOpen  bool
    21  	RelationTag names.RelationTag
    22  }
    23  
    24  // PortRange contains a port range and a relation id. Used as key to
    25  // pendingRelations and is only exported for testing.
    26  type PortRange struct {
    27  	Ports      network.PortRange
    28  	RelationId int
    29  }
    30  
    31  func validatePortRange(protocol string, fromPort, toPort int) (network.PortRange, error) {
    32  	// Validate the given range.
    33  	newRange := network.PortRange{
    34  		Protocol: strings.ToLower(protocol),
    35  		FromPort: fromPort,
    36  		ToPort:   toPort,
    37  	}
    38  	if err := newRange.Validate(); err != nil {
    39  		return network.PortRange{}, err
    40  	}
    41  	return newRange, nil
    42  }
    43  
    44  func tryOpenPorts(
    45  	protocol string,
    46  	fromPort, toPort int,
    47  	unitTag names.UnitTag,
    48  	machinePorts map[network.PortRange]params.RelationUnit,
    49  	pendingPorts map[PortRange]PortRangeInfo,
    50  ) error {
    51  	// TODO(dimitern) Once port ranges are linked to relations in
    52  	// addition to networks, refactor this functions and test it
    53  	// better to ensure it handles relations properly.
    54  	relationId := -1
    55  
    56  	//Validate the given range.
    57  	newRange, err := validatePortRange(protocol, fromPort, toPort)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	rangeKey := PortRange{
    62  		Ports:      newRange,
    63  		RelationId: relationId,
    64  	}
    65  
    66  	rangeInfo, isKnown := pendingPorts[rangeKey]
    67  	if isKnown {
    68  		if !rangeInfo.ShouldOpen {
    69  			// If the same range is already pending to be closed, just
    70  			// mark is pending to be opened.
    71  			rangeInfo.ShouldOpen = true
    72  			pendingPorts[rangeKey] = rangeInfo
    73  		}
    74  		return nil
    75  	}
    76  
    77  	// Ensure there are no conflicts with existing ports on the
    78  	// machine.
    79  	for portRange, relUnit := range machinePorts {
    80  		relUnitTag, err := names.ParseUnitTag(relUnit.Unit)
    81  		if err != nil {
    82  			return errors.Annotatef(
    83  				err,
    84  				"machine ports %v contain invalid unit tag",
    85  				portRange,
    86  			)
    87  		}
    88  		if newRange.ConflictsWith(portRange) {
    89  			if portRange == newRange && relUnitTag == unitTag {
    90  				// The same unit trying to open the same range is just
    91  				// ignored.
    92  				return nil
    93  			}
    94  			return errors.Errorf(
    95  				"cannot open %v (unit %q): conflicts with existing %v (unit %q)",
    96  				newRange, unitTag.Id(), portRange, relUnitTag.Id(),
    97  			)
    98  		}
    99  	}
   100  	// Ensure other pending port ranges do not conflict with this one.
   101  	for rangeKey, rangeInfo := range pendingPorts {
   102  		if newRange.ConflictsWith(rangeKey.Ports) && rangeInfo.ShouldOpen {
   103  			return errors.Errorf(
   104  				"cannot open %v (unit %q): conflicts with %v requested earlier",
   105  				newRange, unitTag.Id(), rangeKey.Ports,
   106  			)
   107  		}
   108  	}
   109  
   110  	rangeInfo = pendingPorts[rangeKey]
   111  	rangeInfo.ShouldOpen = true
   112  	pendingPorts[rangeKey] = rangeInfo
   113  	return nil
   114  }
   115  
   116  func tryClosePorts(
   117  	protocol string,
   118  	fromPort, toPort int,
   119  	unitTag names.UnitTag,
   120  	machinePorts map[network.PortRange]params.RelationUnit,
   121  	pendingPorts map[PortRange]PortRangeInfo,
   122  ) error {
   123  	// TODO(dimitern) Once port ranges are linked to relations in
   124  	// addition to networks, refactor this functions and test it
   125  	// better to ensure it handles relations properly.
   126  	relationId := -1
   127  
   128  	// Validate the given range.
   129  	newRange, err := validatePortRange(protocol, fromPort, toPort)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	rangeKey := PortRange{
   134  		Ports:      newRange,
   135  		RelationId: relationId,
   136  	}
   137  
   138  	rangeInfo, isKnown := pendingPorts[rangeKey]
   139  	if isKnown {
   140  		if rangeInfo.ShouldOpen {
   141  			// If the same range is already pending to be opened, just
   142  			// remove it from pending.
   143  			delete(pendingPorts, rangeKey)
   144  		}
   145  		return nil
   146  	}
   147  
   148  	// Ensure the range we're trying to close is opened on the
   149  	// machine.
   150  	relUnit, found := machinePorts[newRange]
   151  	if !found {
   152  		// Trying to close a range which is not open is ignored.
   153  		return nil
   154  	} else if relUnit.Unit != unitTag.String() {
   155  		relUnitTag, err := names.ParseUnitTag(relUnit.Unit)
   156  		if err != nil {
   157  			return errors.Annotatef(
   158  				err,
   159  				"machine ports %v contain invalid unit tag",
   160  				newRange,
   161  			)
   162  		}
   163  		return errors.Errorf(
   164  			"cannot close %v (opened by %q) from %q",
   165  			newRange, relUnitTag.Id(), unitTag.Id(),
   166  		)
   167  	}
   168  
   169  	rangeInfo = pendingPorts[rangeKey]
   170  	rangeInfo.ShouldOpen = false
   171  	pendingPorts[rangeKey] = rangeInfo
   172  	return nil
   173  }