forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
restore.go
145 lines (124 loc) · 4.7 KB
/
restore.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package backups
import (
"fmt"
"os"
"github.com/juju/errors"
"gopkg.in/juju/names.v2"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/service"
"github.com/juju/juju/service/common"
"github.com/juju/juju/state"
"github.com/juju/juju/state/backups"
)
var bootstrapNode = names.NewMachineTag("0")
// Restore implements the server side of Backups.Restore.
func (a *API) Restore(p params.RestoreArgs) error {
logger.Infof("Starting server side restore")
// Get hold of a backup file Reader
backup, closer := newBackups(a.backend)
defer closer.Close()
// Obtain the address of current machine, where we will be performing restore.
machine, err := a.backend.Machine(a.machineID)
if err != nil {
return errors.Trace(err)
}
addr, err := machine.PrivateAddress()
if err != nil {
return errors.Annotatef(err, "error fetching internal address for machine %q", machine)
}
publicAddress, err := machine.PublicAddress()
if err != nil {
return errors.Annotatef(err, "error fetching public address for machine %q", machine)
}
info := a.backend.RestoreInfo()
// Signal to current state and api server that restore will begin
err = info.SetStatus(state.RestoreInProgress)
if err != nil {
return errors.Annotatef(err, "cannot set the server to %q mode", state.RestoreInProgress)
}
// Any abnormal termination of this function will mark restore as failed,
// successful termination will call Exit and never run this.
defer info.SetStatus(state.RestoreFailed)
instanceId, err := machine.InstanceId()
if err != nil {
return errors.Annotate(err, "cannot obtain instance id for machine to be restored")
}
logger.Infof("beginning server side restore of backup %q", p.BackupId)
// Restore
restoreArgs := backups.RestoreArgs{
PrivateAddress: addr.Value,
PublicAddress: publicAddress.Value,
NewInstId: instanceId,
NewInstTag: machine.Tag(),
NewInstSeries: machine.Series(),
}
session := a.backend.MongoSession().Copy()
defer session.Close()
// Don't go if HA isn't ready.
err = waitUntilReady(session, 60)
if err != nil {
return errors.Annotatef(err, "HA not ready; try again later")
}
oldTagString, err := backup.Restore(p.BackupId, restoreArgs)
if err != nil {
return errors.Annotate(err, "restore failed")
}
// A backup can be made of any component of an ha array.
// The files in a backup don't contain purely relativized paths.
// If the backup is made of the bootstrap node (machine 0) the
// recently created machine will have the same paths and therefore
// the startup scripts will fit the new juju. If the backup belongs
// to a different machine, we need to create a new set of startup
// scripts and exit with 0 (so that the current script does not try
// to restart the old juju, which will no longer be there).
if oldTagString != nil && oldTagString != bootstrapNode {
srvName := fmt.Sprintf("jujud-%s", oldTagString)
srv, err := service.DiscoverService(srvName, common.Conf{})
if err != nil {
return errors.Annotatef(err, "cannot find %q service", srvName)
}
if err := srv.Start(); err != nil {
return errors.Annotatef(err, "cannot start %q service", srvName)
}
// We dont want machine-0 to restart since the new one has a different tag.
// We started the new one above.
os.Exit(0)
}
// After restoring, the api server needs a forced restart, tomb will not work
// this is because we change all of juju configuration files and mongo too.
// Exiting with 0 would prevent upstart to respawn the process
// NOTE(fwereade): the apiserver needs to be restarted, yes, but
// this approach is completely broken. The only place it's ever
// ok to use os.Exit is in a main() func that's *so* simple as to
// be reasonably left untested.
//
// And passing os.Exit in wouldn't make this any better either,
// just using it subverts the expectations of *everything* else
// running in the process.
os.Exit(1)
return nil
}
// PrepareRestore implements the server side of Backups.PrepareRestore.
func (a *API) PrepareRestore() error {
info := a.backend.RestoreInfo()
logger.Infof("entering restore preparation mode")
return info.SetStatus(state.RestorePending)
}
// FinishRestore implements the server side of Backups.FinishRestore.
func (a *API) FinishRestore() error {
info := a.backend.RestoreInfo()
currentStatus, err := info.Status()
if err != nil {
return errors.Trace(err)
}
if currentStatus != state.RestoreFinished {
if err := info.SetStatus(state.RestoreFailed); err != nil {
return errors.Trace(err)
}
return errors.Errorf("Restore did not finish successfully")
}
logger.Infof("Successfully restored")
return info.SetStatus(state.RestoreChecked)
}