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  }