Skip to content

Commit

Permalink
Support a simplified dump-model.
Browse files Browse the repository at this point in the history
  • Loading branch information
howbazaar committed Jun 8, 2017
1 parent 2202d0f commit 92f008c
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 52 deletions.
10 changes: 10 additions & 0 deletions api/base/testing/apicaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ func (APICallerFunc) ConnectControllerStream(path string, attrs url.Values, head
return nil, errors.NotImplementedf("controller stream connection")
}

// BestVersionCaller is an APICallerFunc that has a particular best version.
type BestVersionCaller struct {
APICallerFunc
BestVersion int
}

func (c BestVersionCaller) BestFacadeVersion(facade string) int {
return c.BestVersion
}

// CallChecker is an APICaller implementation that checks
// calls as they are made.
type CallChecker struct {
Expand Down
2 changes: 1 addition & 1 deletion api/facadeversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ var facadeVersions = map[string]int{
"MigrationStatusWatcher": 1,
"MigrationTarget": 1,
"ModelConfig": 1,
"ModelManager": 2,
"ModelManager": 3,
"NotifyWatcher": 1,
"Payloads": 1,
"PayloadsHookContext": 1,
Expand Down
36 changes: 35 additions & 1 deletion api/modelmanager/modelmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/juju/errors"
"github.com/juju/loggo"
"gopkg.in/juju/names.v2"
"gopkg.in/yaml.v2"

"github.com/juju/juju/api/base"
"github.com/juju/juju/api/common"
Expand Down Expand Up @@ -197,7 +198,40 @@ func (c *Client) ModelInfo(tags []names.ModelTag) ([]params.ModelInfoResult, err
}

// DumpModel returns the serialized database agnostic model representation.
func (c *Client) DumpModel(model names.ModelTag) (map[string]interface{}, error) {
func (c *Client) DumpModel(model names.ModelTag, simplified bool) (map[string]interface{}, error) {
if bestVer := c.BestAPIVersion(); bestVer < 3 {
logger.Debugf("calling older dump model on v%d", bestVer)
return c.dumpModelV2(model)
}

var results params.StringResults
entities := params.DumpModelRequest{
Entities: []params.Entity{{Tag: model.String()}},
Simplified: simplified,
}

err := c.facade.FacadeCall("DumpModels", entities, &results)
if err != nil {
return nil, errors.Trace(err)
}
if count := len(results.Results); count != 1 {
return nil, errors.Errorf("unexpected result count: %d", count)
}
result := results.Results[0]
if result.Error != nil {
return nil, result.Error
}
// Parse back into a map.
var asMap map[string]interface{}
err = yaml.Unmarshal([]byte(result.Result), &asMap)
if err != nil {
return nil, errors.Trace(err)
}

return asMap, nil
}

func (c *Client) dumpModelV2(model names.ModelTag) (map[string]interface{}, error) {
var results params.MapResults
entities := params.Entities{
Entities: []params.Entity{{Tag: model.String()}},
Expand Down
96 changes: 75 additions & 21 deletions api/modelmanager/modelmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,45 +244,99 @@ type dumpModelSuite struct {

var _ = gc.Suite(&dumpModelSuite{})

func (s *dumpModelSuite) TestDumpModel(c *gc.C) {
func (s *dumpModelSuite) TestDumpModelV3(c *gc.C) {
expected := map[string]interface{}{
"model-uuid": "some-uuid",
"other-key": "special",
}
results := params.StringResults{Results: []params.StringResult{{
Result: "model-uuid: some-uuid\nother-key: special\n",
}}}
apiCaller := basetesting.BestVersionCaller{
BestVersion: 3,
APICallerFunc: basetesting.APICallerFunc(
func(objType string, version int, id, request string, args, result interface{}) error {
c.Check(objType, gc.Equals, "ModelManager")
c.Check(request, gc.Equals, "DumpModels")
c.Check(version, gc.Equals, 3)
c.Assert(args, gc.DeepEquals, params.DumpModelRequest{
Entities: []params.Entity{{testing.ModelTag.String()}},
Simplified: true})
res, ok := result.(*params.StringResults)
c.Assert(ok, jc.IsTrue)
*res = results
return nil
}),
}
client := modelmanager.NewClient(apiCaller)
out, err := client.DumpModel(testing.ModelTag, true)
c.Assert(err, jc.ErrorIsNil)
c.Assert(out, jc.DeepEquals, expected)
}

func (s *dumpModelSuite) TestDumpModelV2(c *gc.C) {
expected := map[string]interface{}{
"model-uuid": "some-uuid",
"other-key": "special",
}
results := params.MapResults{Results: []params.MapResult{{
Result: expected,
}}}
apiCaller := basetesting.APICallerFunc(
func(objType string, version int, id, request string, args, result interface{}) error {
c.Check(objType, gc.Equals, "ModelManager")
c.Check(request, gc.Equals, "DumpModels")
in, ok := args.(params.Entities)
c.Assert(ok, jc.IsTrue)
c.Assert(in, gc.DeepEquals, params.Entities{[]params.Entity{{testing.ModelTag.String()}}})
res, ok := result.(*params.MapResults)
c.Assert(ok, jc.IsTrue)
*res = results
return nil
})
apiCaller := basetesting.BestVersionCaller{
BestVersion: 2,
APICallerFunc: basetesting.APICallerFunc(
func(objType string, version int, id, request string, args, result interface{}) error {
c.Check(objType, gc.Equals, "ModelManager")
c.Check(request, gc.Equals, "DumpModels")
c.Check(version, gc.Equals, 2)
c.Assert(args, gc.DeepEquals, params.Entities{[]params.Entity{{testing.ModelTag.String()}}})
res, ok := result.(*params.MapResults)
c.Assert(ok, jc.IsTrue)
*res = results
return nil
}),
}
client := modelmanager.NewClient(apiCaller)
out, err := client.DumpModel(testing.ModelTag)
out, err := client.DumpModel(testing.ModelTag, false)
c.Assert(err, jc.ErrorIsNil)
c.Assert(out, jc.DeepEquals, expected)
}

func (s *dumpModelSuite) TestDumpModelError(c *gc.C) {
results := params.MapResults{Results: []params.MapResult{{
func (s *dumpModelSuite) TestDumpModelErrorV3(c *gc.C) {
results := params.StringResults{Results: []params.StringResult{{
Error: &params.Error{Message: "fake error"},
}}}
apiCaller := basetesting.APICallerFunc(
func(objType string, version int, id, request string, args, result interface{}) error {
res, ok := result.(*params.MapResults)
apiCaller := basetesting.BestVersionCaller{
BestVersion: 3,
APICallerFunc: basetesting.APICallerFunc(func(objType string, version int, id, request string, args, result interface{}) error {
res, ok := result.(*params.StringResults)
c.Assert(ok, jc.IsTrue)
*res = results
return nil
})
}),
}
client := modelmanager.NewClient(apiCaller)
out, err := client.DumpModel(testing.ModelTag, false)
c.Assert(err, gc.ErrorMatches, "fake error")
c.Assert(out, gc.IsNil)
}

func (s *dumpModelSuite) TestDumpModelErrorV2(c *gc.C) {
results := params.MapResults{Results: []params.MapResult{{
Error: &params.Error{Message: "fake error"},
}}}
apiCaller := basetesting.BestVersionCaller{
BestVersion: 2,
APICallerFunc: basetesting.APICallerFunc(
func(objType string, version int, id, request string, args, result interface{}) error {
res, ok := result.(*params.MapResults)
c.Assert(ok, jc.IsTrue)
*res = results
return nil
}),
}
client := modelmanager.NewClient(apiCaller)
out, err := client.DumpModel(testing.ModelTag)
out, err := client.DumpModel(testing.ModelTag, false)
c.Assert(err, gc.ErrorMatches, "fake error")
c.Assert(out, gc.IsNil)
}
Expand Down
3 changes: 2 additions & 1 deletion apiserver/allfacades.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ func AllFacades() *facade.Registry {
reg("MigrationTarget", 1, migrationtarget.NewFacade)

reg("ModelConfig", 1, modelconfig.NewFacade)
reg("ModelManager", 2, modelmanager.NewFacade)
reg("ModelManager", 2, modelmanager.NewFacadeV2)
reg("ModelManager", 3, modelmanager.NewFacadeV3)

reg("Payloads", 1, payloads.NewFacade)
regHookContext(
Expand Down
1 change: 1 addition & 0 deletions apiserver/common/modelmanagerinterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type ModelManagerBackend interface {
ControllerUUID() string
ControllerTag() names.ControllerTag
Export() (description.Model, error)
ExportPartial(state.ExportConfig) (description.Model, error)
SetUserAccess(subject names.UserTag, target names.Tag, access permission.Access) (permission.UserAccess, error)
SetModelMeterStatus(string, string) error
ReloadSpaces(environ environs.Environ) error
Expand Down
4 changes: 4 additions & 0 deletions apiserver/modelmanager/modelinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@ func (st *mockState) Export() (description.Model, error) {
return &fakeModelDescription{UUID: st.model.UUID()}, nil
}

func (st *mockState) ExportPartial(state.ExportConfig) (description.Model, error) {
return st.Export()
}

func (st *mockState) ModelUUID() string {
st.MethodCall(st, "ModelUUID")
return st.model.UUID()
Expand Down
87 changes: 79 additions & 8 deletions apiserver/modelmanager/modelmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sort"
"time"

"github.com/juju/description"
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/txn"
Expand All @@ -27,7 +28,6 @@ import (
"github.com/juju/juju/controller/modelmanager"
"github.com/juju/juju/environs"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/migration"
"github.com/juju/juju/permission"
"github.com/juju/juju/state"
"github.com/juju/juju/state/stateenvirons"
Expand All @@ -36,8 +36,19 @@ import (

var logger = loggo.GetLogger("juju.apiserver.modelmanager")

// ModelManager defines the methods on the modelmanager API endpoint.
type ModelManager interface {
// ModelManagerV3 defines the methods on the version 2 facade for the
// modelmanager API endpoint.
type ModelManagerV3 interface {
CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error)
DumpModels(args params.DumpModelRequest) params.StringResults
DumpModelsDB(args params.Entities) params.MapResults
ListModels(user params.Entity) (params.UserModelList, error)
DestroyModels(args params.Entities) (params.ErrorResults, error)
}

// ModelManagerV2 defines the methods on the version 2 facade for the
// modelmanager API endpoint.
type ModelManagerV2 interface {
CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error)
DumpModels(args params.Entities) params.MapResults
DumpModelsDB(args params.Entities) params.MapResults
Expand All @@ -57,14 +68,32 @@ type ModelManagerAPI struct {
isAdmin bool
}

var _ ModelManager = (*ModelManagerAPI)(nil)
// ModelManagerAPIV2 provides a way to wrap the different calls between
// version 2 and version 3 of the model manager API
type ModelManagerAPIV2 struct {
*ModelManagerAPI
}

var (
_ ModelManagerV3 = (*ModelManagerAPI)(nil)
_ ModelManagerV2 = (*ModelManagerAPIV2)(nil)
)

// NewFacade is used for API registration.
func NewFacade(st *state.State, _ facade.Resources, auth facade.Authorizer) (*ModelManagerAPI, error) {
func NewFacadeV3(st *state.State, _ facade.Resources, auth facade.Authorizer) (*ModelManagerAPI, error) {
configGetter := stateenvirons.EnvironConfigGetter{st}
return NewModelManagerAPI(common.NewModelManagerBackend(st), configGetter, auth)
}

// NewFacade is used for API registration.
func NewFacadeV2(st *state.State, resources facade.Resources, auth facade.Authorizer) (*ModelManagerAPIV2, error) {
v3, err := NewFacadeV3(st, resources, auth)
if err != nil {
return nil, err
}
return &ModelManagerAPIV2{v3}, nil
}

// NewModelManagerAPI creates a new api server endpoint for managing
// models.
func NewModelManagerAPI(
Expand Down Expand Up @@ -337,7 +366,7 @@ func (m *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.Model
return m.getModelInfo(model.ModelTag())
}

func (m *ModelManagerAPI) dumpModel(args params.Entity) (map[string]interface{}, error) {
func (m *ModelManagerAPI) dumpModel(args params.Entity, simplified bool) ([]byte, error) {
modelTag, err := names.ParseModelTag(args.Tag)
if err != nil {
return nil, errors.Trace(err)
Expand All @@ -353,6 +382,7 @@ func (m *ModelManagerAPI) dumpModel(args params.Entity) (map[string]interface{},

st := m.state
if st.ModelTag() != modelTag {
// XXX state pool
st, err = m.state.ForModel(modelTag)
if err != nil {
if errors.IsNotFound(err) {
Expand All @@ -363,7 +393,32 @@ func (m *ModelManagerAPI) dumpModel(args params.Entity) (map[string]interface{},
defer st.Close()
}

bytes, err := migration.ExportModel(st)
var exportConfig state.ExportConfig
if simplified {
exportConfig.SkipActions = true
exportConfig.SkipAnnotations = true
exportConfig.SkipCloudImageMetadata = true
exportConfig.SkipCredentials = true
exportConfig.SkipIPAddresses = true
exportConfig.SkipSettings = true
exportConfig.SkipSSHHostKeys = true
exportConfig.SkipStatusHistory = true
exportConfig.SkipLinkLayerDevices = true
}

model, err := st.ExportPartial(exportConfig)
if err != nil {
return nil, errors.Trace(err)
}
bytes, err := description.Serialize(model)
if err != nil {
return nil, errors.Trace(err)
}
return bytes, nil
}

func (m *ModelManagerAPIV2) dumpModel(args params.Entity) (map[string]interface{}, error) {
bytes, err := m.ModelManagerAPI.dumpModel(args, false)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down Expand Up @@ -412,10 +467,26 @@ func (m *ModelManagerAPI) dumpModelDB(args params.Entity) (map[string]interface{
return st.DumpAll()
}

func (m *ModelManagerAPI) DumpModels(args params.DumpModelRequest) params.StringResults {
results := params.StringResults{
Results: make([]params.StringResult, len(args.Entities)),
}
for i, entity := range args.Entities {
bytes, err := m.dumpModel(entity, args.Simplified)
if err != nil {
results.Results[i].Error = common.ServerError(err)
continue
}
// We know here that the bytes are valid YAML.
results.Results[i].Result = string(bytes)
}
return results
}

// DumpModels will export the models into the database agnostic
// representation. The user needs to either be a controller admin, or have
// admin privileges on the model itself.
func (m *ModelManagerAPI) DumpModels(args params.Entities) params.MapResults {
func (m *ModelManagerAPIV2) DumpModels(args params.Entities) params.MapResults {
results := params.MapResults{
Results: make([]params.MapResult, len(args.Entities)),
}
Expand Down
Loading

0 comments on commit 92f008c

Please sign in to comment.