github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/subnet/subnet.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package subnet
     5  
     6  import (
     7  	"io"
     8  	"net"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/names"
    15  	"github.com/juju/utils/featureflag"
    16  
    17  	"github.com/juju/juju/api"
    18  	"github.com/juju/juju/api/subnets"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/cmd/envcmd"
    21  	"github.com/juju/juju/feature"
    22  	"github.com/juju/juju/network"
    23  )
    24  
    25  // SubnetAPI defines the necessary API methods needed by the subnet
    26  // subcommands.
    27  type SubnetAPI interface {
    28  	io.Closer
    29  
    30  	// AddSubnet adds an existing subnet to Juju.
    31  	AddSubnet(cidr names.SubnetTag, id network.Id, spaceTag names.SpaceTag, zones []string) error
    32  
    33  	// ListSubnets returns information about subnets known to Juju,
    34  	// optionally filtered by space and/or zone (both can be empty).
    35  	ListSubnets(withSpace *names.SpaceTag, withZone string) ([]params.Subnet, error)
    36  
    37  	// AllZones returns all availability zones known to Juju.
    38  	AllZones() ([]string, error)
    39  
    40  	// AllSpaces returns all Juju network spaces.
    41  	AllSpaces() ([]names.Tag, error)
    42  
    43  	// CreateSubnet creates a new Juju subnet.
    44  	CreateSubnet(subnetCIDR names.SubnetTag, spaceTag names.SpaceTag, zones []string, isPublic bool) error
    45  
    46  	// RemoveSubnet marks an existing subnet as no longer used, which
    47  	// will cause it to get removed at some point after all its
    48  	// related entites are cleaned up. It will fail if the subnet is
    49  	// still in use by any machines.
    50  	RemoveSubnet(subnetCIDR names.SubnetTag) error
    51  }
    52  
    53  // mvpAPIShim forwards SubnetAPI methods to the real API facade for
    54  // implemented methods only. Tested with a feature test only.
    55  type mvpAPIShim struct {
    56  	SubnetAPI
    57  
    58  	apiState api.Connection
    59  	facade   *subnets.API
    60  }
    61  
    62  func (m *mvpAPIShim) Close() error {
    63  	return m.apiState.Close()
    64  }
    65  
    66  func (m *mvpAPIShim) AddSubnet(cidr names.SubnetTag, id network.Id, spaceTag names.SpaceTag, zones []string) error {
    67  	return m.facade.AddSubnet(cidr, id, spaceTag, zones)
    68  }
    69  
    70  func (m *mvpAPIShim) ListSubnets(withSpace *names.SpaceTag, withZone string) ([]params.Subnet, error) {
    71  	return m.facade.ListSubnets(withSpace, withZone)
    72  }
    73  
    74  var logger = loggo.GetLogger("juju.cmd.juju.subnet")
    75  
    76  const commandDoc = `
    77  "juju subnet" provides commands to manage Juju subnets. In Juju, a
    78  subnet is a logical address range, a subdivision of a network, defined
    79  by the subnet's Classless Inter-Domain Routing (CIDR) range, like
    80  10.10.0.0/24 or 2001:db8::/32. Alternatively, subnets can be
    81  identified uniquely by their provider-specific identifier
    82  (ProviderId), if the provider supports that. Subnets have two kinds of
    83  supported access: "public" (using shadow addresses) or "private"
    84  (using cloud-local addresses, this is the default). For more
    85  information about subnets and shadow addresses, please refer to Juju's
    86  glossary help topics ("juju help glossary"). `
    87  
    88  // NewSuperCommand creates the "subnet" supercommand and registers the
    89  // subcommands that it supports.
    90  func NewSuperCommand() cmd.Command {
    91  	subnetCmd := cmd.NewSuperCommand(cmd.SuperCommandParams{
    92  		Name:        "subnet",
    93  		Doc:         strings.TrimSpace(commandDoc),
    94  		UsagePrefix: "juju",
    95  		Purpose:     "manage subnets",
    96  	})
    97  	subnetCmd.Register(envcmd.Wrap(&AddCommand{}))
    98  	subnetCmd.Register(envcmd.Wrap(&ListCommand{}))
    99  	if featureflag.Enabled(feature.PostNetCLIMVP) {
   100  		// The following commands are not part of the MVP.
   101  		subnetCmd.Register(envcmd.Wrap(&CreateCommand{}))
   102  		subnetCmd.Register(envcmd.Wrap(&RemoveCommand{}))
   103  	}
   104  
   105  	return subnetCmd
   106  }
   107  
   108  // SubnetCommandBase is the base type embedded into all subnet
   109  // subcommands.
   110  type SubnetCommandBase struct {
   111  	envcmd.EnvCommandBase
   112  	api SubnetAPI
   113  }
   114  
   115  // NewAPI returns a SubnetAPI for the root api endpoint that the
   116  // environment command returns.
   117  func (c *SubnetCommandBase) NewAPI() (SubnetAPI, error) {
   118  	if c.api != nil {
   119  		// Already created.
   120  		return c.api, nil
   121  	}
   122  	root, err := c.NewAPIRoot()
   123  	if err != nil {
   124  		return nil, errors.Trace(err)
   125  	}
   126  
   127  	// This is tested with a feature test.
   128  	shim := &mvpAPIShim{
   129  		apiState: root,
   130  		facade:   subnets.NewAPI(root),
   131  	}
   132  	return shim, nil
   133  }
   134  
   135  type RunOnAPI func(api SubnetAPI, ctx *cmd.Context) error
   136  
   137  func (c *SubnetCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error {
   138  	api, err := c.NewAPI()
   139  	if err != nil {
   140  		return errors.Annotate(err, "cannot connect to the API server")
   141  	}
   142  	defer api.Close()
   143  	return toRun(api, ctx)
   144  }
   145  
   146  // Common errors shared between subcommands.
   147  var (
   148  	errNoCIDR     = errors.New("CIDR is required")
   149  	errNoCIDROrID = errors.New("either CIDR or provider ID is required")
   150  	errNoSpace    = errors.New("space name is required")
   151  	errNoZones    = errors.New("at least one zone is required")
   152  )
   153  
   154  // CheckNumArgs is a helper used to validate the number of arguments
   155  // passed to Init(). If the number of arguments is X, errors[X] (if
   156  // set) will be returned, otherwise no error occurs.
   157  func (s *SubnetCommandBase) CheckNumArgs(args []string, errors []error) error {
   158  	for num, err := range errors {
   159  		if len(args) == num {
   160  			return err
   161  		}
   162  	}
   163  	return nil
   164  }
   165  
   166  // ValidateCIDR parses given and returns an error if it's not valid.
   167  // If the CIDR is incorrectly specified (e.g. 10.10.10.0/16 instead of
   168  // 10.10.0.0/16) and strict is false, the correctly parsed CIDR in the
   169  // expected format is returned instead without an error. Otherwise,
   170  // when strict is true and given is incorrectly formatted, an error
   171  // will be returned.
   172  func (s *SubnetCommandBase) ValidateCIDR(given string, strict bool) (names.SubnetTag, error) {
   173  	_, ipNet, err := net.ParseCIDR(given)
   174  	if err != nil {
   175  		logger.Debugf("cannot parse CIDR %q: %v", given, err)
   176  		return names.SubnetTag{}, errors.Errorf("%q is not a valid CIDR", given)
   177  	}
   178  	if strict && given != ipNet.String() {
   179  		expected := ipNet.String()
   180  		return names.SubnetTag{}, errors.Errorf("%q is not correctly specified, expected %q", given, expected)
   181  	}
   182  	// Already validated, so shouldn't error here.
   183  	return names.NewSubnetTag(ipNet.String()), nil
   184  }
   185  
   186  // ValidateSpace parses given and returns an error if it's not a valid
   187  // space name, otherwise returns the parsed tag and no error.
   188  func (s *SubnetCommandBase) ValidateSpace(given string) (names.SpaceTag, error) {
   189  	if !names.IsValidSpace(given) {
   190  		return names.SpaceTag{}, errors.Errorf("%q is not a valid space name", given)
   191  	}
   192  	return names.NewSpaceTag(given), nil
   193  }