Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dev] Include exposed endpoints in diff bundle output #12025

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 53 additions & 23 deletions apiserver/facades/client/client/status.go
Expand Up @@ -186,14 +186,17 @@ func (c *Client) FullStatus(args params.StatusParams) (params.FullStatus, error)
}
context.providerType = cfg.Type()

if context.spaceInfos, err = c.api.stateAccessor.AllSpaceInfos(); err != nil {
return noStatus, errors.Annotate(err, "cannot obtain space information")
}
if context.model, err = c.api.stateAccessor.Model(); err != nil {
return noStatus, errors.Annotate(err, "could not fetch model")
}
if context.status, err = context.model.LoadModelStatus(); err != nil {
return noStatus, errors.Annotate(err, "could not load model status values")
}
if context.allAppsUnitsCharmBindings, err =
fetchAllApplicationsAndUnits(c.api.stateAccessor, context.model); err != nil {
fetchAllApplicationsAndUnits(c.api.stateAccessor, context.model, context.spaceInfos); err != nil {
return noStatus, errors.Annotate(err, "could not fetch applications and units")
}
if context.consumerRemoteApplications, err =
Expand Down Expand Up @@ -230,7 +233,7 @@ func (c *Client) FullStatus(args params.StatusParams) (params.FullStatus, error)
}
// These may be empty when machines have not finished deployment.
if context.ipAddresses, context.spaces, context.linkLayerDevices, err =
fetchNetworkInterfaces(c.api.stateAccessor); err != nil {
fetchNetworkInterfaces(c.api.stateAccessor, context.spaceInfos); err != nil {
return noStatus, errors.Annotate(err, "could not fetch IP addresses and link layer devices")
}
if context.relations, context.relationsById, err = fetchRelations(c.api.stateAccessor); err != nil {
Expand Down Expand Up @@ -550,6 +553,9 @@ type statusContext struct {
leaders map[string]string
branches map[string]cache.Branch

// Information about all spaces.
spaceInfos network.SpaceInfos

primaryHAMachine *names.MachineTag
}

Expand Down Expand Up @@ -629,7 +635,7 @@ func fetchControllerNodes(st Backend) (map[string]state.ControllerNode, error) {
//
// All are required to determine a machine's network interfaces configuration,
// so we want all or none.
func fetchNetworkInterfaces(st Backend) (map[string][]*state.Address, map[string]map[string]set.Strings, map[string][]*state.LinkLayerDevice, error) {
func fetchNetworkInterfaces(st Backend, spaceInfos network.SpaceInfos) (map[string][]*state.Address, map[string]map[string]set.Strings, map[string][]*state.LinkLayerDevice, error) {
ipAddresses := make(map[string][]*state.Address)
spacesPerMachine := make(map[string]map[string]set.Strings)
subnets, err := st.AllSubnets()
Expand All @@ -641,10 +647,6 @@ func fetchNetworkInterfaces(st Backend) (map[string][]*state.Address, map[string
subnetsByCIDR[subnet.CIDR()] = subnet
}

spaceInfos, err := st.AllSpaceInfos()
if err != nil {
return nil, nil, nil, err
}
// For every machine, track what devices have addresses so we can filter linklayerdevices later
devicesWithAddresses := make(map[string]set.Strings)
ipAddrs, err := st.AllIPAddresses()
Expand Down Expand Up @@ -714,10 +716,7 @@ func fetchNetworkInterfaces(st Backend) (map[string][]*state.Address, map[string

// fetchAllApplicationsAndUnits returns a map from application name to application,
// a map from application name to unit name to unit, and a map from base charm URL to latest URL.
func fetchAllApplicationsAndUnits(
st Backend,
model *state.Model,
) (applicationStatusInfo, error) {
func fetchAllApplicationsAndUnits(st Backend, model *state.Model, spaceInfos network.SpaceInfos) (applicationStatusInfo, error) {
appMap := make(map[string]*state.Application)
unitMap := make(map[string]map[string]*state.Unit)
latestCharms := make(map[charm.URL]*state.Charm)
Expand All @@ -742,11 +741,6 @@ func fetchAllApplicationsAndUnits(
}
}

allSpaceInfos, err := st.AllSpaceInfos()
if err != nil {
return applicationStatusInfo{}, errors.Trace(err)
}

endpointBindings, err := model.AllEndpointBindings()
if err != nil {
return applicationStatusInfo{}, err
Expand All @@ -755,7 +749,7 @@ func fetchAllApplicationsAndUnits(
for app, bindings := range endpointBindings {
// If the only binding is the default, and it's set to the
// default space, no need to print.
bindingMap, err := bindings.MapWithSpaceNames(allSpaceInfos)
bindingMap, err := bindings.MapWithSpaceNames(spaceInfos)
if err != nil {
return applicationStatusInfo{}, err
}
Expand Down Expand Up @@ -1174,13 +1168,19 @@ func (context *statusContext) processApplication(application *state.Application)
charmProfileName = lxdprofile.Name(context.model.Name(), application.Name(), applicationCharm.Revision())
}

mappedExposedEndpoints, err := context.mapExposedEndpointsFromState(application.ExposedEndpoints())
if err != nil {
return params.ApplicationStatus{Err: apiservererrors.ServerError(err)}
}

var processedStatus = params.ApplicationStatus{
Charm: applicationCharm.URL().String(),
Series: application.Series(),
Exposed: application.IsExposed(),
Life: processLife(application),
CharmVersion: applicationCharm.Version(),
CharmProfile: charmProfileName,
Charm: applicationCharm.URL().String(),
Series: application.Series(),
Exposed: application.IsExposed(),
ExposedEndpoints: mappedExposedEndpoints,
Life: processLife(application),
CharmVersion: applicationCharm.Version(),
CharmProfile: charmProfileName,
}

if latestCharm, ok := context.allAppsUnitsCharmBindings.latestCharms[*applicationCharm.URL().WithRevision(-1)]; ok && latestCharm != nil {
Expand Down Expand Up @@ -1273,6 +1273,36 @@ func (context *statusContext) processApplication(application *state.Application)
return processedStatus
}

func (context *statusContext) mapExposedEndpointsFromState(exposedEndpoints map[string]state.ExposedEndpoint) (map[string]params.ExposedEndpoint, error) {
if len(exposedEndpoints) == 0 {
return nil, nil
}

res := make(map[string]params.ExposedEndpoint, len(exposedEndpoints))
for endpointName, exposeDetails := range exposedEndpoints {
mappedParam := params.ExposedEndpoint{
ExposeToCIDRs: exposeDetails.ExposeToCIDRs,
}

if len(exposeDetails.ExposeToSpaceIDs) != 0 {
spaceNames := make([]string, len(exposeDetails.ExposeToSpaceIDs))
for i, spaceID := range exposeDetails.ExposeToSpaceIDs {
sp := context.spaceInfos.GetByID(spaceID)
if sp == nil {
return nil, errors.NotFoundf("space with ID %q", spaceID)
}

spaceNames[i] = string(sp.Name)
}
mappedParam.ExposeToSpaces = spaceNames
}

res[endpointName] = mappedParam
}

return res, nil
}

func (context *statusContext) processRemoteApplications() map[string]params.RemoteApplicationStatus {
applicationsMap := make(map[string]params.RemoteApplicationStatus)
for _, app := range context.consumerRemoteApplications {
Expand Down
26 changes: 26 additions & 0 deletions apiserver/facades/client/client/status_test.go
Expand Up @@ -425,6 +425,32 @@ func (s *statusUnitTestSuite) TestMeterStatusWithCredentials(c *gc.C) {
}
}

func (s *statusUnitTestSuite) TestApplicationWithExposedEndpoints(c *gc.C) {
meteredCharm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "metered", URL: "cs:quantal/metered"})
service := s.Factory.MakeApplication(c, &factory.ApplicationParams{Charm: meteredCharm})
err := service.MergeExposeSettings(map[string]state.ExposedEndpoint{
"": {
ExposeToSpaceIDs: []string{network.AlphaSpaceId},
ExposeToCIDRs: []string{"10.0.0.0/24", "192.168.0.0/24"},
},
})
c.Assert(err, jc.ErrorIsNil)

client := s.APIState.Client()
status, err := client.Status(nil)
c.Assert(err, jc.ErrorIsNil)
c.Assert(status, gc.NotNil)
serviceStatus, ok := status.Applications[service.Name()]
c.Assert(ok, gc.Equals, true)

c.Assert(serviceStatus.ExposedEndpoints, gc.DeepEquals, map[string]params.ExposedEndpoint{
"": {
ExposeToSpaces: []string{network.AlphaSpaceName},
ExposeToCIDRs: []string{"10.0.0.0/24", "192.168.0.0/24"},
},
})
}

func addUnitWithVersion(c *gc.C, application *state.Application, version string) *state.Unit {
unit, err := application.AddUnit(state.AddUnitParams{})
c.Assert(err, jc.ErrorIsNil)
Expand Down
26 changes: 26 additions & 0 deletions apiserver/facades/schema.json
Expand Up @@ -11475,6 +11475,14 @@
"exposed": {
"type": "boolean"
},
"exposed-endpoints": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/ExposedEndpoint"
}
}
},
"int": {
"type": "integer"
},
Expand Down Expand Up @@ -11929,6 +11937,24 @@
"results"
]
},
"ExposedEndpoint": {
"type": "object",
"properties": {
"expose-to-cidrs": {
"type": "array",
"items": {
"type": "string"
}
},
"expose-to-spaces": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"FindToolsParams": {
"type": "object",
"properties": {
Expand Down
31 changes: 16 additions & 15 deletions apiserver/params/status.go
Expand Up @@ -140,21 +140,22 @@ type LXDProfile struct {

// ApplicationStatus holds status info about an application.
type ApplicationStatus struct {
Err *Error `json:"err,omitempty"`
Charm string `json:"charm"`
Series string `json:"series"`
Exposed bool `json:"exposed"`
Life life.Value `json:"life"`
Relations map[string][]string `json:"relations"`
CanUpgradeTo string `json:"can-upgrade-to"`
SubordinateTo []string `json:"subordinate-to"`
Units map[string]UnitStatus `json:"units"`
MeterStatuses map[string]MeterStatus `json:"meter-statuses"`
Status DetailedStatus `json:"status"`
WorkloadVersion string `json:"workload-version"`
CharmVersion string `json:"charm-version"`
CharmProfile string `json:"charm-profile"`
EndpointBindings map[string]string `json:"endpoint-bindings"`
Err *Error `json:"err,omitempty"`
Charm string `json:"charm"`
Series string `json:"series"`
Exposed bool `json:"exposed"`
ExposedEndpoints map[string]ExposedEndpoint `json:"exposed-endpoints,omitempty"`
Life life.Value `json:"life"`
Relations map[string][]string `json:"relations"`
CanUpgradeTo string `json:"can-upgrade-to"`
SubordinateTo []string `json:"subordinate-to"`
Units map[string]UnitStatus `json:"units"`
MeterStatuses map[string]MeterStatus `json:"meter-statuses"`
Status DetailedStatus `json:"status"`
WorkloadVersion string `json:"workload-version"`
CharmVersion string `json:"charm-version"`
CharmProfile string `json:"charm-profile"`
EndpointBindings map[string]string `json:"endpoint-bindings"`

// The following are for CAAS models.
Scale int `json:"int,omitempty"`
Expand Down
9 changes: 9 additions & 0 deletions cmd/juju/application/bundle/bundle.go
Expand Up @@ -66,6 +66,15 @@ func BuildModelRepresentation(
Series: appStatus.Series,
SubordinateTo: appStatus.SubordinateTo,
}
if len(appStatus.ExposedEndpoints) != 0 {
app.ExposedEndpoints = make(map[string]bundlechanges.ExposedEndpoint)
for endpoint, exposeDetails := range appStatus.ExposedEndpoints {
app.ExposedEndpoints[endpoint] = bundlechanges.ExposedEndpoint{
ExposeToSpaces: exposeDetails.ExposeToSpaces,
ExposeToCIDRs: exposeDetails.ExposeToCIDRs,
}
}
}
for unitName, unit := range appStatus.Units {
app.Units = append(app.Units, bundlechanges.Unit{
Name: unitName,
Expand Down
59 changes: 59 additions & 0 deletions cmd/juju/application/bundle/bundle_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/golang/mock/gomock"
"github.com/juju/bundlechanges/v3"
"github.com/juju/charm/v8"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
Expand Down Expand Up @@ -253,6 +254,64 @@ func (s *composeAndVerifyRepSuite) setupOverlayFile(c *gc.C) {
jc.ErrorIsNil)
}

func (s *buildModelRepSuite) TestBuildModelRepresentationApplicationsWithExposedEndpoints(c *gc.C) {
defer s.setupMocks(c).Finish()
s.expectGetAnnotations(c, []string{"machine-0", "application-wordpress"})
s.expectGetConstraintsWordpress()
s.expectEmptySequences()

s.modelExtractor.EXPECT().GetConfig(model.GenerationMaster, "wordpress").Return(nil, nil)

status := &params.FullStatus{
Model: params.ModelStatusInfo{
Name: "default",
},
Machines: map[string]params.MachineStatus{
"0": {Series: "bionic"},
},
Applications: map[string]params.ApplicationStatus{
"wordpress": {
Series: "bionic",
Life: life.Alive,
Units: map[string]params.UnitStatus{
"0": {Machine: "0"},
},
ExposedEndpoints: map[string]params.ExposedEndpoint{
"": {
ExposeToCIDRs: []string{"10.0.0.0/24"},
},
"website": {
ExposeToSpaces: []string{"inner", "outer"},
ExposeToCIDRs: []string{"192.168.0.0/24"},
},
},
},
},
}
machines := map[string]string{}

obtainedModel, err := BuildModelRepresentation(status, s.modelExtractor, false, machines)
c.Assert(err, jc.ErrorIsNil)
c.Assert(obtainedModel.Applications, gc.HasLen, 1)
obtainedWordpress, ok := obtainedModel.Applications["wordpress"]
c.Assert(ok, jc.IsTrue)

c.Assert(obtainedWordpress.ExposedEndpoints, gc.DeepEquals, map[string]bundlechanges.ExposedEndpoint{
"": {
ExposeToCIDRs: []string{"10.0.0.0/24"},
},
"website": {
ExposeToSpaces: []string{"inner", "outer"},
ExposeToCIDRs: []string{"192.168.0.0/24"},
},
})

c.Assert(obtainedModel.Machines, gc.HasLen, 1)
c.Assert(obtainedModel.Relations, gc.HasLen, 0)
c.Assert(obtainedModel.Sequence, gc.HasLen, 0)
c.Assert(obtainedModel.MachineMap, gc.HasLen, 0)
}

func (s *composeAndVerifyRepSuite) setupMocks(c *gc.C) *gomock.Controller {
ctrl := gomock.NewController(c)
s.bundleDataSource = mocks.NewMockBundleDataSource(ctrl)
Expand Down