github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/subnet/create.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  	"strings"
     8  
     9  	"launchpad.net/gnuflag"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils/set"
    15  )
    16  
    17  // CreateCommand calls the API to create a new subnet.
    18  type CreateCommand struct {
    19  	SubnetCommandBase
    20  
    21  	CIDR      names.SubnetTag
    22  	Space     names.SpaceTag
    23  	Zones     set.Strings
    24  	IsPublic  bool
    25  	IsPrivate bool
    26  
    27  	flagSet *gnuflag.FlagSet
    28  }
    29  
    30  const createCommandDoc = `
    31  Creates a new subnet with a given CIDR, associated with an existing Juju
    32  network space, and attached to one or more availablility zones. Desired
    33  access for the subnet can be specified using the mutually exclusive flags
    34  --private and --public.
    35  
    36  When --private is specified (or no flags are given, as this is the default),
    37  the created subnet will not allow access from outside the environment and
    38  the available address range is only cloud-local.
    39  
    40  When --public is specified, the created subnet will support "shadow addresses"
    41  (see "juju help glossary" for the full definition of the term). This means
    42  all machines inside the subnet will have cloud-local addresses configured,
    43  but there will also be a shadow address configured for each machine, so that
    44  the machines can be accessed from outside the environment (similarly to the
    45  automatic public IP addresses supported with AWS VPCs).
    46  
    47  This command is only supported on clouds which support creating new subnets
    48  dynamically (i.e. Software Defined Networking or SDN). If you want to make
    49  an existing subnet available for Juju to use, rather than creating a new
    50  one, use the "juju subnet add" command.
    51  
    52  Some clouds allow a subnet to span multiple zones, but others do not. It is
    53  an error to try creating a subnet spanning more than one zone if it is not
    54  supported.
    55  `
    56  
    57  // Info is defined on the cmd.Command interface.
    58  func (c *CreateCommand) Info() *cmd.Info {
    59  	return &cmd.Info{
    60  		Name:    "create",
    61  		Args:    "<CIDR> <space> <zone1> [<zone2> <zone3> ...] [--public|--private]",
    62  		Purpose: "create a new subnet",
    63  		Doc:     strings.TrimSpace(createCommandDoc),
    64  	}
    65  }
    66  
    67  // SetFlags is defined on the cmd.Command interface.
    68  func (c *CreateCommand) SetFlags(f *gnuflag.FlagSet) {
    69  	c.SubnetCommandBase.SetFlags(f)
    70  	f.BoolVar(&c.IsPublic, "public", false, "enable public access with shadow addresses")
    71  	f.BoolVar(&c.IsPrivate, "private", true, "disable public access with shadow addresses")
    72  
    73  	// Because SetFlags is called before Parse, we cannot
    74  	// use f.Visit() here to check both flags were not
    75  	// specified at once. So we store the flag set and
    76  	// defer the check to Init().
    77  	c.flagSet = f
    78  }
    79  
    80  // Init is defined on the cmd.Command interface. It checks the
    81  // arguments for sanity and sets up the command to run.
    82  func (c *CreateCommand) Init(args []string) error {
    83  	// Ensure we have at least 3 arguments.
    84  	// TODO:(mfoord) we need to support VLANTag as an additional optional
    85  	// argument.
    86  	err := c.CheckNumArgs(args, []error{errNoCIDR, errNoSpace, errNoZones})
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// Validate given CIDR.
    92  	c.CIDR, err = c.ValidateCIDR(args[0], true)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	// Validate the space name.
    98  	c.Space, err = c.ValidateSpace(args[1])
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Validate any given zones.
   104  	c.Zones = set.NewStrings()
   105  	for _, zone := range args[2:] {
   106  		if c.Zones.Contains(zone) {
   107  			return errors.Errorf("duplicate zone %q specified", zone)
   108  		}
   109  		c.Zones.Add(zone)
   110  	}
   111  
   112  	// Ensure --public and --private are not both specified.
   113  	// TODO(dimitern): This is a really awkward way to handle
   114  	// mutually exclusive bool flags and needs to be factored
   115  	// out in a helper if another command needs to do it.
   116  	var publicSet, privateSet bool
   117  	c.flagSet.Visit(func(flag *gnuflag.Flag) {
   118  		switch flag.Name {
   119  		case "public":
   120  			publicSet = true
   121  		case "private":
   122  			privateSet = true
   123  		}
   124  	})
   125  	switch {
   126  	case publicSet && privateSet:
   127  		return errors.Errorf("cannot specify both --public and --private")
   128  	case publicSet:
   129  		c.IsPrivate = false
   130  	case privateSet:
   131  		c.IsPublic = false
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  // Run implements Command.Run.
   138  func (c *CreateCommand) Run(ctx *cmd.Context) error {
   139  	return c.RunWithAPI(ctx, func(api SubnetAPI, ctx *cmd.Context) error {
   140  		if !c.Zones.IsEmpty() {
   141  			// Fetch all zones to validate the given zones.
   142  			zones, err := api.AllZones()
   143  			if err != nil {
   144  				return errors.Annotate(err, "cannot fetch availability zones")
   145  			}
   146  
   147  			// Find which of the given CIDRs match existing ones.
   148  			validZones := set.NewStrings()
   149  			for _, zone := range zones {
   150  				validZones.Add(zone)
   151  			}
   152  			diff := c.Zones.Difference(validZones)
   153  
   154  			if !diff.IsEmpty() {
   155  				// Some given zones are missing.
   156  				zones := strings.Join(diff.SortedValues(), ", ")
   157  				return errors.Errorf("unknown zones specified: %s", zones)
   158  			}
   159  		}
   160  
   161  		// Create the new subnet.
   162  		err := api.CreateSubnet(c.CIDR, c.Space, c.Zones.SortedValues(), c.IsPublic)
   163  		if err != nil {
   164  			return errors.Annotatef(err, "cannot create subnet %q", c.CIDR.Id())
   165  		}
   166  
   167  		zones := strings.Join(c.Zones.SortedValues(), ", ")
   168  		accessType := "private"
   169  		if c.IsPublic {
   170  			accessType = "public"
   171  		}
   172  		ctx.Infof(
   173  			"created a %s subnet %q in space %q with zones %s",
   174  			accessType, c.CIDR.Id(), c.Space.Id(), zones,
   175  		)
   176  		return nil
   177  	})
   178  }