1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"github.com/juju/loggo"
14
"github.com/juju/version"
15
"gopkg.in/juju/charm.v6-unstable"
18
"github.com/juju/juju/core/description"
19
"github.com/juju/juju/state"
20
"github.com/juju/juju/state/binarystorage"
21
"github.com/juju/juju/tools"
24
var logger = loggo.GetLogger("juju.migration")
26
// StateExporter describes interface on state required to export a
28
type StateExporter interface {
29
// Export generates an abstract representation of a model.
30
Export() (description.Model, error)
33
// ExportModel creates a description.Model representation of the
34
// active model for StateExporter (typically a *state.State), and
35
// returns the serialized version. It provides the symmetric
36
// functionality to ImportModel.
37
func ExportModel(st StateExporter) ([]byte, error) {
38
model, err := st.Export()
40
return nil, errors.Trace(err)
42
bytes, err := description.Serialize(model)
44
return nil, errors.Trace(err)
49
// ImportModel deserializes a model description from the bytes, transforms
50
// the model config based on information from the controller model, and then
51
// imports that as a new database model.
52
func ImportModel(st *state.State, bytes []byte) (*state.Model, *state.State, error) {
53
model, err := description.Deserialize(bytes)
55
return nil, nil, errors.Trace(err)
58
dbModel, dbState, err := st.Import(model)
60
return nil, nil, errors.Trace(err)
62
return dbModel, dbState, nil
65
// CharmDownlaoder defines a single method that is used to download a
66
// charm from the source controller in a migration.
67
type CharmDownloader interface {
68
OpenCharm(*charm.URL) (io.ReadCloser, error)
71
// UploadBackend define the methods on *state.State that are needed for
72
// uploading the tools and charms from the current controller to a different
74
type UploadBackend interface {
75
Charm(*charm.URL) (*state.Charm, error)
77
MongoSession() *mgo.Session
78
ToolsStorage() (binarystorage.StorageCloser, error)
81
// CharmUploader defines a single method that is used to upload a
82
// charm to the target controller in a migration.
83
type CharmUploader interface {
84
UploadCharm(*charm.URL, io.ReadSeeker) (*charm.URL, error)
87
// ToolsDownloader defines a single method that is used to download
88
// tools from the source controller in a migration.
89
type ToolsDownloader interface {
90
OpenURI(string, url.Values) (io.ReadCloser, error)
93
// ToolsUploader defines a single method that is used to upload tools
94
// to the target controller in a migration.
95
type ToolsUploader interface {
96
UploadTools(io.ReadSeeker, version.Binary, ...string) (tools.List, error)
99
// UploadBinariesConfig provides all the configuration that the
100
// UploadBinaries function needs to operate. To construct the config
101
// with the default helper functions, use `NewUploadBinariesConfig`.
102
type UploadBinariesConfig struct {
104
CharmDownloader CharmDownloader
105
CharmUploader CharmUploader
107
Tools map[version.Binary]string
108
ToolsDownloader ToolsDownloader
109
ToolsUploader ToolsUploader
112
// Validate makes sure that all the config values are non-nil.
113
func (c *UploadBinariesConfig) Validate() error {
114
if c.CharmDownloader == nil {
115
return errors.NotValidf("missing CharmDownloader")
117
if c.CharmUploader == nil {
118
return errors.NotValidf("missing CharmUploader")
120
if c.ToolsDownloader == nil {
121
return errors.NotValidf("missing ToolsDownloader")
123
if c.ToolsUploader == nil {
124
return errors.NotValidf("missing ToolsUploader")
129
// UploadBinaries will send binaries stored in the source blobstore to
130
// the target controller.
131
func UploadBinaries(config UploadBinariesConfig) error {
132
if err := config.Validate(); err != nil {
133
return errors.Trace(err)
135
if err := uploadCharms(config); err != nil {
136
return errors.Trace(err)
138
if err := uploadTools(config); err != nil {
139
return errors.Trace(err)
144
func streamThroughTempFile(r io.Reader) (_ io.ReadSeeker, cleanup func(), err error) {
145
tempFile, err := ioutil.TempFile("", "juju-migrate-binary")
147
return nil, nil, errors.Trace(err)
151
os.Remove(tempFile.Name())
154
_, err = io.Copy(tempFile, r)
156
return nil, nil, errors.Trace(err)
159
rmTempFile := func() {
160
filename := tempFile.Name()
165
return tempFile, rmTempFile, nil
168
func uploadCharms(config UploadBinariesConfig) error {
169
for _, charmUrl := range config.Charms {
170
logger.Debugf("sending charm %s to target", charmUrl)
172
curl, err := charm.ParseURL(charmUrl)
174
return errors.Annotate(err, "bad charm URL")
177
reader, err := config.CharmDownloader.OpenCharm(curl)
179
return errors.Annotate(err, "cannot open charm")
183
content, cleanup, err := streamThroughTempFile(reader)
185
return errors.Trace(err)
189
if _, err := config.CharmUploader.UploadCharm(curl, content); err != nil {
190
return errors.Annotate(err, "cannot upload charm")
196
func uploadTools(config UploadBinariesConfig) error {
197
for v, uri := range config.Tools {
198
logger.Debugf("sending tools to target: %s", v)
200
reader, err := config.ToolsDownloader.OpenURI(uri, nil)
202
return errors.Annotate(err, "cannot open charm")
206
content, cleanup, err := streamThroughTempFile(reader)
208
return errors.Trace(err)
212
if _, err := config.ToolsUploader.UploadTools(content, v); err != nil {
213
return errors.Annotate(err, "cannot upload tools")
219
// PrecheckBackend is implemented by *state.State but defined as an interface
220
// for easier testing.
221
type PrecheckBackend interface {
222
NeedsCleanup() (bool, error)
225
// Precheck checks the database state to make sure that the preconditions
226
// for model migration are met.
227
func Precheck(backend PrecheckBackend) error {
228
cleanupNeeded, err := backend.NeedsCleanup()
230
return errors.Annotate(err, "precheck cleanups")
233
return errors.New("precheck failed: cleanup needed")