~sinzui/ubuntu/vivid/juju-core/vivid-1.24.6

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/service/addunit.go

  • Committer: Curtis Hovey
  • Date: 2015-09-30 14:14:54 UTC
  • mfrom: (1.1.34)
  • Revision ID: curtis@hovey.name-20150930141454-o3ldf23dzyjio6c0
Backport of 1.24.6 from wily. (LP: #1500916)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2012-2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package service
 
5
 
 
6
import (
 
7
        "errors"
 
8
        "fmt"
 
9
        "regexp"
 
10
 
 
11
        "github.com/juju/cmd"
 
12
        "github.com/juju/names"
 
13
        "launchpad.net/gnuflag"
 
14
 
 
15
        "github.com/juju/juju/cmd/envcmd"
 
16
        "github.com/juju/juju/cmd/juju/block"
 
17
        "github.com/juju/juju/environs/config"
 
18
        "github.com/juju/juju/provider"
 
19
)
 
20
 
 
21
// UnitCommandBase provides support for commands which deploy units. It handles the parsing
 
22
// and validation of --to and --num-units arguments.
 
23
type UnitCommandBase struct {
 
24
        ToMachineSpec string
 
25
        NumUnits      int
 
26
}
 
27
 
 
28
func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) {
 
29
        f.IntVar(&c.NumUnits, "num-units", 1, "")
 
30
        f.StringVar(&c.ToMachineSpec, "to", "", "the machine or container to deploy the unit in, bypasses constraints")
 
31
}
 
32
 
 
33
func (c *UnitCommandBase) Init(args []string) error {
 
34
        if c.NumUnits < 1 {
 
35
                return errors.New("--num-units must be a positive integer")
 
36
        }
 
37
        if c.ToMachineSpec != "" {
 
38
                if c.NumUnits > 1 {
 
39
                        return errors.New("cannot use --num-units > 1 with --to")
 
40
                }
 
41
                if !IsMachineOrNewContainer(c.ToMachineSpec) {
 
42
                        return fmt.Errorf("invalid --to parameter %q", c.ToMachineSpec)
 
43
                }
 
44
 
 
45
        }
 
46
        return nil
 
47
}
 
48
 
 
49
// TODO(anastasiamac) 2014-10-20 Bug#1383116
 
50
// This exists to provide more context to the user about
 
51
// why they cannot allocate units to machine 0. Remove
 
52
// this when the local provider's machine 0 is a container.
 
53
// TODO(cherylj) Unexport CheckProvider once deploy is moved under service
 
54
func (c *UnitCommandBase) CheckProvider(conf *config.Config) error {
 
55
        if conf.Type() == provider.Local && c.ToMachineSpec == "0" {
 
56
                return errors.New("machine 0 is the state server for a local environment and cannot host units")
 
57
        }
 
58
        return nil
 
59
}
 
60
 
 
61
// TODO(cherylj) Unexport GetClientConfig and make it a standard function
 
62
// once deploy is moved under service
 
63
var GetClientConfig = func(client ServiceAddUnitAPI) (*config.Config, error) {
 
64
        // Separated into a variable for easy overrides
 
65
        attrs, err := client.EnvironmentGet()
 
66
        if err != nil {
 
67
                return nil, err
 
68
        }
 
69
 
 
70
        return config.New(config.NoDefaults, attrs)
 
71
}
 
72
 
 
73
// AddUnitCommand is responsible adding additional units to a service.
 
74
type AddUnitCommand struct {
 
75
        envcmd.EnvCommandBase
 
76
        UnitCommandBase
 
77
        ServiceName string
 
78
        api         ServiceAddUnitAPI
 
79
}
 
80
 
 
81
const addUnitDoc = `
 
82
Adding units to an existing service is a way to scale out an environment by
 
83
deploying more instances of a service.  Add-unit must be called on services that
 
84
have already been deployed via juju deploy.
 
85
 
 
86
By default, services are deployed to newly provisioned machines.  Alternatively,
 
87
service units can be added to a specific existing machine using the --to
 
88
argument.
 
89
 
 
90
Examples:
 
91
 juju service add-unit mysql -n 5          (Add 5 mysql units on 5 new machines)
 
92
 juju service add-unit mysql --to 23       (Add a mysql unit to machine 23)
 
93
 juju service add-unit mysql --to 24/lxc/3 (Add unit to lxc container 3 on host machine 24)
 
94
 juju service add-unit mysql --to lxc:25   (Add unit to a new lxc container on host machine 25)
 
95
`
 
96
 
 
97
func (c *AddUnitCommand) Info() *cmd.Info {
 
98
        return &cmd.Info{
 
99
                Name:    "add-unit",
 
100
                Args:    "<service name>",
 
101
                Purpose: "add one or more units of an already-deployed service",
 
102
                Doc:     addUnitDoc,
 
103
        }
 
104
}
 
105
 
 
106
func (c *AddUnitCommand) SetFlags(f *gnuflag.FlagSet) {
 
107
        c.UnitCommandBase.SetFlags(f)
 
108
        f.IntVar(&c.NumUnits, "n", 1, "number of service units to add")
 
109
}
 
110
 
 
111
func (c *AddUnitCommand) Init(args []string) error {
 
112
        switch len(args) {
 
113
        case 1:
 
114
                c.ServiceName = args[0]
 
115
        case 0:
 
116
                return errors.New("no service specified")
 
117
        }
 
118
        if err := cmd.CheckEmpty(args[1:]); err != nil {
 
119
                return err
 
120
        }
 
121
        return c.UnitCommandBase.Init(args)
 
122
}
 
123
 
 
124
// ServiceAddUnitAPI defines the methods on the client API
 
125
// that the service add-unit command calls.
 
126
type ServiceAddUnitAPI interface {
 
127
        Close() error
 
128
        AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error)
 
129
        EnvironmentGet() (map[string]interface{}, error)
 
130
}
 
131
 
 
132
func (c *AddUnitCommand) getAPI() (ServiceAddUnitAPI, error) {
 
133
        if c.api != nil {
 
134
                return c.api, nil
 
135
        }
 
136
        return c.NewAPIClient()
 
137
}
 
138
 
 
139
// Run connects to the environment specified on the command line
 
140
// and calls AddServiceUnits for the given service.
 
141
func (c *AddUnitCommand) Run(_ *cmd.Context) error {
 
142
        apiclient, err := c.getAPI()
 
143
        if err != nil {
 
144
                return err
 
145
        }
 
146
        defer apiclient.Close()
 
147
 
 
148
        conf, err := GetClientConfig(apiclient)
 
149
        if err != nil {
 
150
                return err
 
151
        }
 
152
 
 
153
        if err := c.CheckProvider(conf); err != nil {
 
154
                return err
 
155
        }
 
156
 
 
157
        _, err = apiclient.AddServiceUnits(c.ServiceName, c.NumUnits, c.ToMachineSpec)
 
158
        return block.ProcessBlockedError(err, block.BlockChange)
 
159
}
 
160
 
 
161
// deployTarget describes the format a machine or container target must match to be valid.
 
162
const deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$"
 
163
 
 
164
var validMachineOrNewContainer = regexp.MustCompile(deployTarget)
 
165
 
 
166
// IsMachineOrNewContainer returns whether spec is a valid machine id
 
167
// or new container definition.
 
168
func IsMachineOrNewContainer(spec string) bool {
 
169
        return validMachineOrNewContainer.MatchString(spec)
 
170
}