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 }