Skip to content

Commit

Permalink
Add support for k8s operators behind a "k8s-operators" feature flag.
Browse files Browse the repository at this point in the history
An operator charm is one with deployment mode = "operator".
It does not call k8s-spec-set - instead the charm pod is the workload.
  • Loading branch information
wallyworld committed Mar 27, 2020
1 parent 24ebc66 commit 6f8e580
Show file tree
Hide file tree
Showing 29 changed files with 481 additions and 112 deletions.
4 changes: 2 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@

[[constraint]]
name = "gopkg.in/juju/charm.v6"
revision = "5217f4ff9d6d681861601ad653e151ffb2f05b90"
revision = "c390a4cc19a3efac72d6fcce80ab2984d54f1f3f"

[[constraint]]
revision = "4a4d9c6d92fd547ab98053fde7874b3c51e4f0f1"
Expand Down
2 changes: 1 addition & 1 deletion agent/agentbootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ func initControllerCloudService(
// this should never happen.
return errors.Errorf("environ %T does not implement ServiceGetterSetter interface", env)
}
svc, err := broker.GetService(k8sprovider.JujuControllerStackName, true)
svc, err := broker.GetService(k8sprovider.JujuControllerStackName, caas.ModeWorkload, true)
if err != nil {
return errors.Trace(err)
}
Expand Down
17 changes: 17 additions & 0 deletions api/caasunitprovisioner/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/juju/juju/api/base"
apiwatcher "github.com/juju/juju/api/watcher"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/caas"
"github.com/juju/juju/core/application"
"github.com/juju/juju/core/constraints"
"github.com/juju/juju/core/devices"
Expand Down Expand Up @@ -119,6 +120,22 @@ func (c *Client) ApplicationScale(applicationName string) (int, error) {
return results.Results[0].Result, nil
}

// DeploymentMode returns the deployment mode for the specified application's charm.
func (c *Client) DeploymentMode(applicationName string) (caas.DeploymentMode, error) {
var results params.StringResults
args := params.Entities{
Entities: []params.Entity{{Tag: names.NewApplicationTag(applicationName).String()}},
}
err := c.facade.FacadeCall("DeploymentMode", args, &results)
if err != nil {
return "", errors.Trace(err)
}
if len(results.Results) != len(args.Entities) {
return "", errors.Errorf("expected %d result(s), got %d", len(args.Entities), len(results.Results))
}
return caas.DeploymentMode(results.Results[0].Result), nil
}

// WatchPodSpec returns a NotifyWatcher that notifies of
// changes to the pod spec of the specified CAAS application in
// the current model.
Expand Down
27 changes: 27 additions & 0 deletions api/caasunitprovisioner/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
basetesting "github.com/juju/juju/api/base/testing"
"github.com/juju/juju/api/caasunitprovisioner"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/caas"
"github.com/juju/juju/core/application"
"github.com/juju/juju/core/constraints"
"github.com/juju/juju/core/devices"
Expand Down Expand Up @@ -265,6 +266,32 @@ func (s *unitprovisionerSuite) TestApplicationScale(c *gc.C) {
c.Assert(scale, gc.Equals, 5)
}

func (s *unitprovisionerSuite) TestDeploymentMode(c *gc.C) {
apiCaller := basetesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
c.Check(objType, gc.Equals, "CAASUnitProvisioner")
c.Check(version, gc.Equals, 0)
c.Check(id, gc.Equals, "")
c.Check(request, gc.Equals, "DeploymentMode")
c.Assert(arg, jc.DeepEquals, params.Entities{
Entities: []params.Entity{{
Tag: "application-gitlab",
}},
})
c.Assert(result, gc.FitsTypeOf, &params.StringResults{})
*(result.(*params.StringResults)) = params.StringResults{
Results: []params.StringResult{{
Result: "workload",
}},
}
return nil
})

client := caasunitprovisioner.NewClient(apiCaller)
mode, err := client.DeploymentMode("gitlab")
c.Assert(err, jc.ErrorIsNil)
c.Assert(mode, gc.Equals, caas.ModeWorkload)
}

func (s *unitprovisionerSuite) TestWatchPodSpec(c *gc.C) {
apiCaller := basetesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
c.Check(objType, gc.Equals, "CAASUnitProvisioner")
Expand Down
24 changes: 23 additions & 1 deletion apiserver/facades/client/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/caas"
k8s "github.com/juju/juju/caas/kubernetes/provider"
"github.com/juju/juju/controller"
"github.com/juju/juju/core/application"
"github.com/juju/juju/core/constraints"
"github.com/juju/juju/core/crossmodel"
Expand All @@ -40,6 +41,7 @@ import (
"github.com/juju/juju/core/permission"
"github.com/juju/juju/core/status"
"github.com/juju/juju/environs"
"github.com/juju/juju/feature"
"github.com/juju/juju/state"
"github.com/juju/juju/state/stateenvirons"
"github.com/juju/juju/storage"
Expand Down Expand Up @@ -502,12 +504,20 @@ func splitApplicationAndCharmConfigFromYAML(modelType state.ModelType, inYaml, a

func caasPrecheck(
ch Charm,
controllerCfg controller.Config,
model Model,
args params.ApplicationDeploy,
storagePoolManager poolmanager.PoolManager,
registry storage.ProviderRegistry,
caasBroker caasBrokerInterface,
) error {
if ch.Meta().Deployment != nil && ch.Meta().Deployment.DeploymentMode == charm.ModeOperator {
if !controllerCfg.Features().Contains(feature.K8sOperators) {
return errors.Errorf(
"feature flag %q is required for deploying k8s operator charms", feature.K8sOperators,
)
}
}
if len(args.AttachStorage) > 0 {
return errors.Errorf(
"AttachStorage may not be specified for k8s models",
Expand Down Expand Up @@ -610,7 +620,11 @@ func deployApplication(

modelType := model.Type()
if modelType != state.ModelTypeIAAS {
if err := caasPrecheck(ch, model, args, storagePoolManager, registry, caasBroker); err != nil {
cfg, err := backend.ControllerConfig()
if err != nil {
return errors.Trace(err)
}
if err := caasPrecheck(ch, cfg, model, args, storagePoolManager, registry, caasBroker); err != nil {
return errors.Trace(err)
}
}
Expand Down Expand Up @@ -1728,6 +1742,14 @@ func (api *APIBase) ScaleApplications(args params.ScaleApplicationsParams) (para
} else if err != nil {
return nil, errors.Trace(err)
}
ch, _, err := app.Charm()
if err != nil {
return nil, errors.Trace(err)
}
if ch.Meta().Deployment != nil && ch.Meta().Deployment.DeploymentMode == charm.ModeOperator {
return nil, errors.New("cannot scale an operator charm")
}

var info params.ScaleApplicationInfo
if arg.ScaleChange != 0 {
newScale, err := app.ChangeScale(arg.ScaleChange)
Expand Down
53 changes: 51 additions & 2 deletions apiserver/facades/client/application/application_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,31 @@ func (s *ApplicationSuite) TestSetCAASCharmInvalid(c *gc.C) {
c.Assert(msg, gc.Matches, "Juju on k8s does not support updating deployment info.*")
}

func (s *ApplicationSuite) TestDeployCAASOperatorProtectedByFlag(c *gc.C) {
s.model.modelType = state.ModelTypeCAAS
s.setAPIUser(c, names.NewUserTag("admin"))
s.backend.charm = &mockCharm{
meta: &charm.Meta{
Deployment: &charm.Deployment{
DeploymentMode: charm.ModeOperator,
},
},
}
args := params.ApplicationsDeploy{
Applications: []params.ApplicationDeploy{{
ApplicationName: "foo",
CharmURL: "local:foo-0",
NumUnits: 1,
}},
}
result, err := s.api.Deploy(args)
c.Assert(err, jc.ErrorIsNil)
err = result.OneError()
c.Assert(err, gc.NotNil)
msg := strings.Replace(err.Error(), "\n", "", -1)
c.Assert(msg, gc.Matches, `feature flag "k8s-operators" is required for deploying k8s operator charms`)
}

func (s *ApplicationSuite) TestSetCharmConfigSettings(c *gc.C) {
err := s.api.SetCharm(params.ApplicationSetCharm{
ApplicationName: "postgresql",
Expand Down Expand Up @@ -970,7 +995,31 @@ func (s *ApplicationSuite) TestScaleApplicationsCAASModel(c *gc.C) {
}},
})
app := s.backend.applications["postgresql"]
app.CheckCall(c, 0, "Scale", 5)
app.CheckCall(c, 1, "Scale", 5)
}

func (s *ApplicationSuite) TestScaleApplicationsNotAllowedForOperator(c *gc.C) {
s.model.modelType = state.ModelTypeCAAS
s.setAPIUser(c, names.NewUserTag("admin"))
s.backend.applications["postgresql"].charm = &mockCharm{
meta: &charm.Meta{
Deployment: &charm.Deployment{
DeploymentMode: charm.ModeOperator,
},
},
}
args := params.ScaleApplicationsParams{
Applications: []params.ScaleApplicationParams{{
ApplicationTag: "application-postgresql",
Scale: 5,
}},
}
result, err := s.api.ScaleApplications(args)
c.Assert(err, jc.ErrorIsNil)
c.Assert(result.Results, gc.HasLen, 1)
c.Assert(result.Results[0].Error, gc.NotNil)
msg := strings.Replace(result.Results[0].Error.Error(), "\n", "", -1)
c.Assert(msg, gc.Matches, `cannot scale an operator charm`)
}

func (s *ApplicationSuite) TestScaleApplicationsBlocked(c *gc.C) {
Expand Down Expand Up @@ -1001,7 +1050,7 @@ func (s *ApplicationSuite) TestScaleApplicationsCAASModelScaleChange(c *gc.C) {
}},
})
app := s.backend.applications["postgresql"]
app.CheckCall(c, 0, "ChangeScale", 5)
app.CheckCall(c, 1, "ChangeScale", 5)
}

func (s *ApplicationSuite) TestScaleApplicationsCAASModelScaleArgCheck(c *gc.C) {
Expand Down
2 changes: 2 additions & 0 deletions apiserver/facades/client/application/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"gopkg.in/juju/names.v3"

"github.com/juju/juju/apiserver/common/storagecommon"
"github.com/juju/juju/controller"
"github.com/juju/juju/core/application"
"github.com/juju/juju/core/constraints"
"github.com/juju/juju/core/crossmodel"
Expand Down Expand Up @@ -45,6 +46,7 @@ type Backend interface {
UnitsInError() ([]Unit, error)
SaveController(info crossmodel.ControllerInfo, modelUUID string) (ExternalController, error)
ControllerTag() names.ControllerTag
ControllerConfig() (controller.Config, error)
Resources() (Resources, error)
OfferConnectionForRelation(string) (OfferConnection, error)
SaveEgressNetworks(relationKey string, cidrs []string) (state.RelationNetworks, error)
Expand Down
5 changes: 5 additions & 0 deletions apiserver/facades/client/application/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/juju/juju/apiserver/common/storagecommon"
"github.com/juju/juju/apiserver/facades/client/application"
"github.com/juju/juju/caas"
"github.com/juju/juju/controller"
coreapplication "github.com/juju/juju/core/application"
"github.com/juju/juju/core/constraints"
"github.com/juju/juju/core/crossmodel"
Expand Down Expand Up @@ -385,6 +386,10 @@ func (m *mockBackend) GetBlockForType(t state.BlockType) (state.Block, bool, err
return nil, false, nil
}

func (m *mockBackend) ControllerConfig() (controller.Config, error) {
return controller.NewConfig(coretesting.ControllerTag.Id(), coretesting.CACert, map[string]interface{}{})
}

func (m *mockBackend) Charm(curl *charm.URL) (application.Charm, error) {
m.MethodCall(m, "Charm", curl)
if err := m.NextErr(); err != nil {
Expand Down
42 changes: 41 additions & 1 deletion apiserver/facades/controller/caasunitprovisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/juju/collections/set"
"github.com/juju/errors"
"github.com/juju/loggo"
"gopkg.in/juju/charm.v6"
"gopkg.in/juju/names.v3"

"github.com/juju/juju/apiserver/common"
Expand Down Expand Up @@ -210,7 +211,7 @@ func (f *Facade) ApplicationsScale(args params.Entities) (params.IntResults, err
}
results.Results[i].Result = scale
}
logger.Debugf("provisioning info result: %#v", results)
logger.Debugf("application scale result: %#v", results)
return results, nil
}

Expand All @@ -226,6 +227,45 @@ func (f *Facade) applicationScale(tagString string) (int, error) {
return app.GetScale(), nil
}

// DeploymentMode returns the deployment mode of the given applications' charms.
func (f *Facade) DeploymentMode(args params.Entities) (params.StringResults, error) {
results := params.StringResults{
Results: make([]params.StringResult, len(args.Entities)),
}
for i, arg := range args.Entities {
mode, err := f.applicationDeploymentMode(arg.Tag)
if err != nil {
results.Results[i].Error = common.ServerError(err)
continue
}
results.Results[i].Result = mode
}
return results, nil
}

func (f *Facade) applicationDeploymentMode(tagString string) (string, error) {
appTag, err := names.ParseApplicationTag(tagString)
if err != nil {
return "", errors.Trace(err)
}
app, err := f.state.Application(appTag.Id())
if err != nil {
return "", errors.Trace(err)
}
ch, _, err := app.Charm()
if err != nil {
return "", errors.Trace(err)
}
var mode charm.DeploymentMode
if d := ch.Meta().Deployment; d != nil {
mode = d.DeploymentMode
}
if mode == "" {
mode = charm.ModeWorkload
}
return string(mode), nil
}

// ProvisioningInfo returns the provisioning info for specified applications in this model.
func (f *Facade) ProvisioningInfo(args params.Entities) (params.KubernetesProvisioningInfoResults, error) {
model, err := f.state.Model()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,33 @@ func (s *CAASProvisionerSuite) TestApplicationScale(c *gc.C) {
s.st.CheckCallNames(c, "Application")
}

func (s *CAASProvisionerSuite) TestDeploymentMode(c *gc.C) {
s.st.application.charm = &mockCharm{
meta: charm.Meta{
Deployment: &charm.Deployment{
DeploymentMode: charm.ModeWorkload,
},
},
}
results, err := s.facade.DeploymentMode(params.Entities{
Entities: []params.Entity{
{Tag: "application-gitlab"},
{Tag: "unit-gitlab-0"},
},
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(results, jc.DeepEquals, params.StringResults{
Results: []params.StringResult{{
Result: "workload",
}, {
Error: &params.Error{
Message: `"unit-gitlab-0" is not a valid application tag`,
},
}},
})
s.st.CheckCallNames(c, "Application")
}

func (s *CAASProvisionerSuite) TestLife(c *gc.C) {
results, err := s.facade.Life(params.Entities{
Entities: []params.Entity{
Expand Down
Loading

0 comments on commit 6f8e580

Please sign in to comment.