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