github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/migration/migration.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migration 5 6 import ( 7 "io" 8 "io/ioutil" 9 "net/url" 10 "os" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/version" 15 "gopkg.in/juju/charm.v6-unstable" 16 "gopkg.in/mgo.v2" 17 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" 22 ) 23 24 var logger = loggo.GetLogger("juju.migration") 25 26 // StateExporter describes interface on state required to export a 27 // model. 28 type StateExporter interface { 29 // Export generates an abstract representation of a model. 30 Export() (description.Model, error) 31 } 32 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() 39 if err != nil { 40 return nil, errors.Trace(err) 41 } 42 bytes, err := description.Serialize(model) 43 if err != nil { 44 return nil, errors.Trace(err) 45 } 46 return bytes, nil 47 } 48 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) 54 if err != nil { 55 return nil, nil, errors.Trace(err) 56 } 57 58 dbModel, dbState, err := st.Import(model) 59 if err != nil { 60 return nil, nil, errors.Trace(err) 61 } 62 return dbModel, dbState, nil 63 } 64 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) 69 } 70 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 73 // controller. 74 type UploadBackend interface { 75 Charm(*charm.URL) (*state.Charm, error) 76 ModelUUID() string 77 MongoSession() *mgo.Session 78 ToolsStorage() (binarystorage.StorageCloser, error) 79 } 80 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) 85 } 86 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) 91 } 92 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) 97 } 98 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 { 103 Charms []string 104 CharmDownloader CharmDownloader 105 CharmUploader CharmUploader 106 107 Tools map[version.Binary]string 108 ToolsDownloader ToolsDownloader 109 ToolsUploader ToolsUploader 110 } 111 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") 116 } 117 if c.CharmUploader == nil { 118 return errors.NotValidf("missing CharmUploader") 119 } 120 if c.ToolsDownloader == nil { 121 return errors.NotValidf("missing ToolsDownloader") 122 } 123 if c.ToolsUploader == nil { 124 return errors.NotValidf("missing ToolsUploader") 125 } 126 return nil 127 } 128 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) 134 } 135 if err := uploadCharms(config); err != nil { 136 return errors.Trace(err) 137 } 138 if err := uploadTools(config); err != nil { 139 return errors.Trace(err) 140 } 141 return nil 142 } 143 144 func streamThroughTempFile(r io.Reader) (_ io.ReadSeeker, cleanup func(), err error) { 145 tempFile, err := ioutil.TempFile("", "juju-migrate-binary") 146 if err != nil { 147 return nil, nil, errors.Trace(err) 148 } 149 defer func() { 150 if err != nil { 151 os.Remove(tempFile.Name()) 152 } 153 }() 154 _, err = io.Copy(tempFile, r) 155 if err != nil { 156 return nil, nil, errors.Trace(err) 157 } 158 tempFile.Seek(0, 0) 159 rmTempFile := func() { 160 filename := tempFile.Name() 161 tempFile.Close() 162 os.Remove(filename) 163 } 164 165 return tempFile, rmTempFile, nil 166 } 167 168 func uploadCharms(config UploadBinariesConfig) error { 169 for _, charmURL := range config.Charms { 170 logger.Debugf("sending charm %s to target", charmURL) 171 172 curl, err := charm.ParseURL(charmURL) 173 if err != nil { 174 return errors.Annotate(err, "bad charm URL") 175 } 176 177 reader, err := config.CharmDownloader.OpenCharm(curl) 178 if err != nil { 179 return errors.Annotate(err, "cannot open charm") 180 } 181 defer reader.Close() 182 183 content, cleanup, err := streamThroughTempFile(reader) 184 if err != nil { 185 return errors.Trace(err) 186 } 187 defer cleanup() 188 189 if _, err := config.CharmUploader.UploadCharm(curl, content); err != nil { 190 return errors.Annotate(err, "cannot upload charm") 191 } 192 } 193 return nil 194 } 195 196 func uploadTools(config UploadBinariesConfig) error { 197 for v, uri := range config.Tools { 198 logger.Debugf("sending tools to target: %s", v) 199 200 reader, err := config.ToolsDownloader.OpenURI(uri, nil) 201 if err != nil { 202 return errors.Annotate(err, "cannot open charm") 203 } 204 defer reader.Close() 205 206 content, cleanup, err := streamThroughTempFile(reader) 207 if err != nil { 208 return errors.Trace(err) 209 } 210 defer cleanup() 211 212 if _, err := config.ToolsUploader.UploadTools(content, v); err != nil { 213 return errors.Annotate(err, "cannot upload tools") 214 } 215 } 216 return nil 217 }