~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/plugins/juju-upgrade-mongo/upgrade.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package main
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "fmt"
 
9
        "io"
 
10
        "os"
 
11
        "os/exec"
 
12
        "strings"
 
13
        "text/template"
 
14
        "time"
 
15
 
 
16
        "github.com/juju/cmd"
 
17
        "github.com/juju/errors"
 
18
        "github.com/juju/replicaset"
 
19
        "github.com/juju/utils"
 
20
        "gopkg.in/juju/names.v2"
 
21
        "launchpad.net/gnuflag"
 
22
 
 
23
        "github.com/juju/juju/api/highavailability"
 
24
        "github.com/juju/juju/apiserver/params"
 
25
        "github.com/juju/juju/cmd/modelcmd"
 
26
        "github.com/juju/juju/juju"
 
27
        "github.com/juju/juju/mongo"
 
28
        "github.com/juju/juju/network"
 
29
)
 
30
 
 
31
func (c *upgradeMongoCommand) SetFlags(f *gnuflag.FlagSet) {
 
32
        f.BoolVar(&c.local, "local", false, "this is a local provider")
 
33
        c.Log.AddFlags(f)
 
34
}
 
35
 
 
36
func main() {
 
37
        Main(os.Args)
 
38
}
 
39
 
 
40
// Main is the entry point for this plugins.
 
41
func Main(args []string) {
 
42
        ctx, err := cmd.DefaultContext()
 
43
        if err != nil {
 
44
                fmt.Fprintf(os.Stderr, "could not obtain context for command: %v\n", err)
 
45
                os.Exit(2)
 
46
        }
 
47
        if err := juju.InitJujuXDGDataHome(); err != nil {
 
48
                fmt.Fprintf(os.Stderr, "error: %s\n", err)
 
49
                os.Exit(2)
 
50
        }
 
51
        os.Exit(cmd.Main(modelcmd.Wrap(&upgradeMongoCommand{}), ctx, args[1:]))
 
52
}
 
53
 
 
54
const upgradeDoc = `This command upgrades the version of mongo used to store the Juju model from 2.4 to 3.x`
 
55
 
 
56
// MongoUpgradeClient defines the methods
 
57
// on the client api that mongo upgrade will call.
 
58
type MongoUpgradeClient interface {
 
59
        Close() error
 
60
        MongoUpgradeMode(mongo.Version) (params.MongoUpgradeResults, error)
 
61
        ResumeHAReplicationAfterUpgrade([]replicaset.Member) error
 
62
}
 
63
 
 
64
type upgradeMongoCommand struct {
 
65
        modelcmd.ModelCommandBase
 
66
        Log      cmd.Log
 
67
        local    bool
 
68
        haClient MongoUpgradeClient
 
69
}
 
70
 
 
71
func (c *upgradeMongoCommand) Info() *cmd.Info {
 
72
        return &cmd.Info{
 
73
                Name:    "juju-upgrade-database",
 
74
                Purpose: "Upgrade from mongo 2.4 to 3.x",
 
75
                Args:    "",
 
76
                Doc:     upgradeDoc,
 
77
        }
 
78
}
 
79
 
 
80
// runViaJujuSSH will run arbitrary code in the remote machine.
 
81
func runViaJujuSSH(machine, script string, stdout, stderr *bytes.Buffer) error {
 
82
        cmd := exec.Command("ssh", []string{"-o StrictHostKeyChecking=no", fmt.Sprintf("ubuntu@%s", machine), "sudo -n bash -c " + utils.ShQuote(script)}...)
 
83
        cmd.Stderr = stderr
 
84
        cmd.Stdout = stdout
 
85
        err := cmd.Run()
 
86
        if err != nil {
 
87
                return errors.Annotatef(err, "ssh command failed: (%q)", stderr.String())
 
88
        }
 
89
        return nil
 
90
}
 
91
 
 
92
// bufferPrinter is intended to print the output of a remote script
 
93
// in real time.
 
94
// the intention behind this is to provide the user with continuous
 
95
// feedback while waiting a remote process that might take some time.
 
96
func bufferPrinter(stdout *bytes.Buffer, closer chan int, verbose bool) {
 
97
        for {
 
98
                select {
 
99
                case <-closer:
 
100
                        return
 
101
                case <-time.After(500 * time.Millisecond):
 
102
 
 
103
                }
 
104
                line, err := stdout.ReadString(byte('\n'))
 
105
                if err == nil || err == io.EOF {
 
106
                        fmt.Print(line)
 
107
                }
 
108
                if err != nil && err != io.EOF {
 
109
                        return
 
110
                }
 
111
 
 
112
        }
 
113
}
 
114
 
 
115
const (
 
116
        jujuUpgradeScript = `
 
117
/var/lib/juju/tools/machine-{{.MachineNumber}}/jujud upgrade-mongo --series {{.Series}} --machinetag 'machine-{{.MachineNumber}}'
 
118
`
 
119
        jujuUpgradeScriptMembers = `
 
120
/var/lib/juju/tools/machine-{{.MachineNumber}}/jujud upgrade-mongo --series {{.Series}} --machinetag 'machine-{{.MachineNumber}}' --members '{{.Members}}'
 
121
`
 
122
        jujuSlaveUpgradeScript = `
 
123
/var/lib/juju/tools/machine-{{.MachineNumber}}/jujud upgrade-mongo --series {{.Series}} --machinetag 'machine-{{.MachineNumber}}' --slave
 
124
`
 
125
)
 
126
 
 
127
type upgradeScriptParams struct {
 
128
        MachineNumber string
 
129
        Series        string
 
130
        Members       string
 
131
}
 
132
 
 
133
func (c *upgradeMongoCommand) Run(ctx *cmd.Context) error {
 
134
        if err := c.Log.Start(ctx); err != nil {
 
135
                return err
 
136
        }
 
137
 
 
138
        migratables, err := c.migratableMachines()
 
139
        if err != nil {
 
140
                return errors.Annotate(err, "cannot determine status servers")
 
141
        }
 
142
 
 
143
        addrs := make([]string, len(migratables.rsMembers))
 
144
        for i, rsm := range migratables.rsMembers {
 
145
                addrs[i] = rsm.Address
 
146
        }
 
147
        var members string
 
148
        if len(addrs) > 0 {
 
149
                members = strings.Join(addrs, ",")
 
150
        }
 
151
 
 
152
        var stdout, stderr bytes.Buffer
 
153
        var closer chan int
 
154
        closer = make(chan int, 1)
 
155
        defer func() { closer <- 1 }()
 
156
        go bufferPrinter(&stdout, closer, false)
 
157
 
 
158
        t := template.New("").Funcs(template.FuncMap{
 
159
                "shquote": utils.ShQuote,
 
160
        })
 
161
        var tmpl *template.Template
 
162
        if members == "" {
 
163
                tmpl = template.Must(t.Parse(jujuUpgradeScript))
 
164
        } else {
 
165
                tmpl = template.Must(t.Parse(jujuUpgradeScriptMembers))
 
166
        }
 
167
        var buf bytes.Buffer
 
168
        upgradeParams := upgradeScriptParams{
 
169
                migratables.master.machine.Id(),
 
170
                migratables.master.series,
 
171
                members,
 
172
        }
 
173
        if err = tmpl.Execute(&buf, upgradeParams); err != nil {
 
174
                return errors.Annotate(err, "cannot build a script to perform the remote upgrade")
 
175
        }
 
176
 
 
177
        if err := runViaJujuSSH(migratables.master.ip.Value, buf.String(), &stdout, &stderr); err != nil {
 
178
                return errors.Annotate(err, "migration to mongo 3 unsuccesful, your database is left in the same state.")
 
179
        }
 
180
        ts := template.New("")
 
181
        tmpl = template.Must(ts.Parse(jujuSlaveUpgradeScript))
 
182
        for _, m := range migratables.machines {
 
183
                if m.ip.Value == migratables.master.ip.Value {
 
184
                        continue
 
185
                }
 
186
                var buf bytes.Buffer
 
187
                upgradeParams := upgradeScriptParams{
 
188
                        m.machine.Id(),
 
189
                        m.series,
 
190
                        "",
 
191
                }
 
192
                if err := tmpl.Execute(&buf, upgradeParams); err != nil {
 
193
                        return errors.Annotate(err, "cannot build a script to perform the remote upgrade")
 
194
                }
 
195
                if err := runViaJujuSSH(m.ip.Value, buf.String(), &stdout, &stderr); err != nil {
 
196
                        return errors.Annotatef(err, "cannot migrate slave machine on %q", m.ip.Value)
 
197
                }
 
198
        }
 
199
        return nil
 
200
}
 
201
 
 
202
type migratable struct {
 
203
        machine names.MachineTag
 
204
        ip      network.Address
 
205
        result  int
 
206
        series  string
 
207
}
 
208
 
 
209
type upgradeMongoParams struct {
 
210
        master    migratable
 
211
        machines  []migratable
 
212
        rsMembers []replicaset.Member
 
213
}
 
214
 
 
215
func (c *upgradeMongoCommand) getHAClient() (MongoUpgradeClient, error) {
 
216
        if c.haClient != nil {
 
217
                return c.haClient, nil
 
218
        }
 
219
 
 
220
        root, err := c.NewAPIRoot()
 
221
        if err != nil {
 
222
                return nil, errors.Annotate(err, "cannot get API connection")
 
223
        }
 
224
 
 
225
        // NewClient does not return an error, so we'll return nil
 
226
        return highavailability.NewClient(root), nil
 
227
}
 
228
 
 
229
func (c *upgradeMongoCommand) migratableMachines() (upgradeMongoParams, error) {
 
230
        haClient, err := c.getHAClient()
 
231
        if err != nil {
 
232
                return upgradeMongoParams{}, err
 
233
        }
 
234
 
 
235
        defer haClient.Close()
 
236
        results, err := haClient.MongoUpgradeMode(mongo.Mongo32wt)
 
237
        if err != nil {
 
238
                return upgradeMongoParams{}, errors.Annotate(err, "cannot enter mongo upgrade mode")
 
239
        }
 
240
        result := upgradeMongoParams{}
 
241
 
 
242
        result.master = migratable{
 
243
                ip:      results.Master.PublicAddress,
 
244
                machine: names.NewMachineTag(results.Master.Tag),
 
245
                series:  results.Master.Series,
 
246
        }
 
247
        result.machines = make([]migratable, len(results.Members))
 
248
        for i, member := range results.Members {
 
249
                result.machines[i] = migratable{
 
250
                        ip:      member.PublicAddress,
 
251
                        machine: names.NewMachineTag(member.Tag),
 
252
                        series:  member.Series,
 
253
                }
 
254
        }
 
255
        result.rsMembers = make([]replicaset.Member, len(results.RsMembers))
 
256
        for i, rsMember := range results.RsMembers {
 
257
                result.rsMembers[i] = rsMember
 
258
        }
 
259
 
 
260
        return result, nil
 
261
}