1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
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"
31
func (c *upgradeMongoCommand) SetFlags(f *gnuflag.FlagSet) {
32
f.BoolVar(&c.local, "local", false, "this is a local provider")
40
// Main is the entry point for this plugins.
41
func Main(args []string) {
42
ctx, err := cmd.DefaultContext()
44
fmt.Fprintf(os.Stderr, "could not obtain context for command: %v\n", err)
47
if err := juju.InitJujuXDGDataHome(); err != nil {
48
fmt.Fprintf(os.Stderr, "error: %s\n", err)
51
os.Exit(cmd.Main(modelcmd.Wrap(&upgradeMongoCommand{}), ctx, args[1:]))
54
const upgradeDoc = `This command upgrades the version of mongo used to store the Juju model from 2.4 to 3.x`
56
// MongoUpgradeClient defines the methods
57
// on the client api that mongo upgrade will call.
58
type MongoUpgradeClient interface {
60
MongoUpgradeMode(mongo.Version) (params.MongoUpgradeResults, error)
61
ResumeHAReplicationAfterUpgrade([]replicaset.Member) error
64
type upgradeMongoCommand struct {
65
modelcmd.ModelCommandBase
68
haClient MongoUpgradeClient
71
func (c *upgradeMongoCommand) Info() *cmd.Info {
73
Name: "juju-upgrade-database",
74
Purpose: "Upgrade from mongo 2.4 to 3.x",
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)}...)
87
return errors.Annotatef(err, "ssh command failed: (%q)", stderr.String())
92
// bufferPrinter is intended to print the output of a remote script
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) {
101
case <-time.After(500 * time.Millisecond):
104
line, err := stdout.ReadString(byte('\n'))
105
if err == nil || err == io.EOF {
108
if err != nil && err != io.EOF {
116
jujuUpgradeScript = `
117
/var/lib/juju/tools/machine-{{.MachineNumber}}/jujud upgrade-mongo --series {{.Series}} --machinetag 'machine-{{.MachineNumber}}'
119
jujuUpgradeScriptMembers = `
120
/var/lib/juju/tools/machine-{{.MachineNumber}}/jujud upgrade-mongo --series {{.Series}} --machinetag 'machine-{{.MachineNumber}}' --members '{{.Members}}'
122
jujuSlaveUpgradeScript = `
123
/var/lib/juju/tools/machine-{{.MachineNumber}}/jujud upgrade-mongo --series {{.Series}} --machinetag 'machine-{{.MachineNumber}}' --slave
127
type upgradeScriptParams struct {
133
func (c *upgradeMongoCommand) Run(ctx *cmd.Context) error {
134
if err := c.Log.Start(ctx); err != nil {
138
migratables, err := c.migratableMachines()
140
return errors.Annotate(err, "cannot determine status servers")
143
addrs := make([]string, len(migratables.rsMembers))
144
for i, rsm := range migratables.rsMembers {
145
addrs[i] = rsm.Address
149
members = strings.Join(addrs, ",")
152
var stdout, stderr bytes.Buffer
154
closer = make(chan int, 1)
155
defer func() { closer <- 1 }()
156
go bufferPrinter(&stdout, closer, false)
158
t := template.New("").Funcs(template.FuncMap{
159
"shquote": utils.ShQuote,
161
var tmpl *template.Template
163
tmpl = template.Must(t.Parse(jujuUpgradeScript))
165
tmpl = template.Must(t.Parse(jujuUpgradeScriptMembers))
168
upgradeParams := upgradeScriptParams{
169
migratables.master.machine.Id(),
170
migratables.master.series,
173
if err = tmpl.Execute(&buf, upgradeParams); err != nil {
174
return errors.Annotate(err, "cannot build a script to perform the remote upgrade")
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.")
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 {
187
upgradeParams := upgradeScriptParams{
192
if err := tmpl.Execute(&buf, upgradeParams); err != nil {
193
return errors.Annotate(err, "cannot build a script to perform the remote upgrade")
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)
202
type migratable struct {
203
machine names.MachineTag
209
type upgradeMongoParams struct {
211
machines []migratable
212
rsMembers []replicaset.Member
215
func (c *upgradeMongoCommand) getHAClient() (MongoUpgradeClient, error) {
216
if c.haClient != nil {
217
return c.haClient, nil
220
root, err := c.NewAPIRoot()
222
return nil, errors.Annotate(err, "cannot get API connection")
225
// NewClient does not return an error, so we'll return nil
226
return highavailability.NewClient(root), nil
229
func (c *upgradeMongoCommand) migratableMachines() (upgradeMongoParams, error) {
230
haClient, err := c.getHAClient()
232
return upgradeMongoParams{}, err
235
defer haClient.Close()
236
results, err := haClient.MongoUpgradeMode(mongo.Mongo32wt)
238
return upgradeMongoParams{}, errors.Annotate(err, "cannot enter mongo upgrade mode")
240
result := upgradeMongoParams{}
242
result.master = migratable{
243
ip: results.Master.PublicAddress,
244
machine: names.NewMachineTag(results.Master.Tag),
245
series: results.Master.Series,
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,
255
result.rsMembers = make([]replicaset.Member, len(results.RsMembers))
256
for i, rsMember := range results.RsMembers {
257
result.rsMembers[i] = rsMember