Skip to content

Commit

Permalink
New http client for lxd connections.
Browse files Browse the repository at this point in the history
A change introduced to Juju introduced the concept of re-using the HTTP
client for LXD connections to multiple different servers. The LXD client
however when passed it's own http server modifies the transport for the
http client. This causes the same http client to keep having it's
transport modified for different connections.

The result is that https validation fails for some lxd remotes because
the transport is configured for a different server.

Fixes LP2003135
  • Loading branch information
tlm committed Apr 5, 2023
1 parent cf1872b commit 7728ffa
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 33 deletions.
10 changes: 4 additions & 6 deletions apiserver/facades/client/modelupgrader/upgrader_test.go
Expand Up @@ -4,8 +4,6 @@
package modelupgrader_test

import (
"net/http"

"github.com/golang/mock/gomock"
"github.com/juju/errors"
"github.com/juju/names/v4"
Expand Down Expand Up @@ -146,7 +144,7 @@ func (s *modelUpgradeSuite) assertUpgradeModelForControllerModelJuju3(c *gc.C, d
server := upgradevalidationmocks.NewMockServer(ctrl)
serverFactory := upgradevalidationmocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down Expand Up @@ -320,7 +318,7 @@ func (s *modelUpgradeSuite) TestUpgradeModelForControllerModelJuju3Failed(c *gc.
server := upgradevalidationmocks.NewMockServer(ctrl)
serverFactory := upgradevalidationmocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down Expand Up @@ -497,7 +495,7 @@ func (s *modelUpgradeSuite) assertUpgradeModelJuju3(c *gc.C, dryRun bool) {
server := upgradevalidationmocks.NewMockServer(ctrl)
serverFactory := upgradevalidationmocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down Expand Up @@ -603,7 +601,7 @@ func (s *modelUpgradeSuite) TestUpgradeModelJuju3Failed(c *gc.C) {
server := upgradevalidationmocks.NewMockServer(ctrl)
serverFactory := upgradevalidationmocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down
3 changes: 1 addition & 2 deletions migration/precheck_test.go
Expand Up @@ -4,7 +4,6 @@
package migration_test

import (
"net/http"
"strings"

"github.com/golang/mock/gomock"
Expand Down Expand Up @@ -92,7 +91,7 @@ func (s *SourcePrecheckSuite) TestTargetController3Failed(c *gc.C) {
server := upgradevalidationmocks.NewMockServer(ctrl)
serverFactory := upgradevalidationmocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down
4 changes: 4 additions & 0 deletions provider/lxd/export_test.go
Expand Up @@ -5,6 +5,7 @@ package lxd

import (
"errors"
"net/http"

"github.com/juju/clock"

Expand Down Expand Up @@ -56,6 +57,9 @@ func NewServerFactoryWithMocks(localServerFunc func() (Server, error),
newRemoteServerFunc: remoteServerFunc,
interfaceAddress: interfaceAddress,
clock: clock,
newHTTPClientFunc: NewHTTPClientFunc(func() *http.Client {
return &http.Client{}
}),
}
}

Expand Down
11 changes: 7 additions & 4 deletions provider/lxd/provider.go
Expand Up @@ -6,6 +6,7 @@ package lxd
import (
stdcontext "context"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -116,11 +117,13 @@ var cloudSchema = &jsonschema.Schema{

// NewProvider returns a new LXD EnvironProvider.
func NewProvider() environs.CloudEnvironProvider {
httpClient := jujuhttp.NewClient(
jujuhttp.WithLogger(logger.ChildWithLabels("http", corelogger.HTTP)),
)
configReader := lxcConfigReader{}
factory := NewServerFactory(httpClient.Client())
factory := NewServerFactory(NewHTTPClientFunc(func() *http.Client {
return jujuhttp.NewClient(
jujuhttp.WithLogger(logger.ChildWithLabels("http", corelogger.HTTP)),
).Client()
}))

credentials := environProviderCredentials{
certReadWriter: certificateReadWriter{},
certGenerator: certificateGenerator{},
Expand Down
19 changes: 13 additions & 6 deletions provider/lxd/server.go
Expand Up @@ -130,6 +130,10 @@ func (interfaceAddress) InterfaceAddress(interfaceName string) (string, error) {
return utils.GetAddressForInterface(interfaceName)
}

// NewHTTPClientFunc is responsible for generating a new http client every time
// it is called.
type NewHTTPClientFunc func() *http.Client

type serverFactory struct {
newLocalServerFunc func() (Server, error)
newRemoteServerFunc func(lxd.ServerSpec) (Server, error)
Expand All @@ -138,20 +142,23 @@ type serverFactory struct {
interfaceAddress InterfaceAddress
clock clock.Clock
mutex sync.Mutex
httpClient *http.Client
newHTTPClientFunc NewHTTPClientFunc
}

// NewServerFactory creates a new ServerFactory with sane defaults.
func NewServerFactory(httpClient *http.Client) ServerFactory {
// A NewHTTPClientFunc is taken as an argument to address LP2003135. Previously
// we reused the same http client for all LXD connections. This can't happen
// as the LXD client code modifies the HTTP server.
func NewServerFactory(newHttpFn NewHTTPClientFunc) ServerFactory {
return &serverFactory{
newLocalServerFunc: func() (Server, error) {
return lxd.NewLocalServer()
},
newRemoteServerFunc: func(spec lxd.ServerSpec) (Server, error) {
return lxd.NewRemoteServer(spec)
},
interfaceAddress: interfaceAddress{},
httpClient: httpClient,
interfaceAddress: interfaceAddress{},
newHTTPClientFunc: newHttpFn,
}
}

Expand Down Expand Up @@ -209,7 +216,7 @@ func (s *serverFactory) RemoteServer(spec environscloudspec.CloudSpec) (Server,

serverSpec := lxd.NewServerSpec(spec.Endpoint, serverCert, clientCert).
WithProxy(proxy.DefaultConfig.GetProxy).
WithHTTPClient(s.httpClient)
WithHTTPClient(s.newHTTPClientFunc())

svr, err := s.newRemoteServerFunc(serverSpec)
if err == nil {
Expand All @@ -236,7 +243,7 @@ func (s *serverFactory) InsecureRemoteServer(spec environscloudspec.CloudSpec) (
serverSpec := lxd.NewInsecureServerSpec(spec.Endpoint).
WithClientCertificate(clientCert).
WithSkipGetServer(true).
WithHTTPClient(s.httpClient)
WithHTTPClient(s.newHTTPClientFunc())

svr, err := s.newRemoteServerFunc(serverSpec)
return svr, errors.Trace(err)
Expand Down
4 changes: 1 addition & 3 deletions upgrades/upgradevalidation/migrate_test.go
Expand Up @@ -4,8 +4,6 @@
package upgradevalidation_test

import (
"net/http"

"github.com/golang/mock/gomock"
jc "github.com/juju/testing/checkers"
"github.com/juju/version/v2"
Expand All @@ -30,7 +28,7 @@ func (s *upgradeValidationSuite) TestValidatorsForModelMigrationSourceJuju3(c *g
server := mocks.NewMockServer(ctrl)
serverFactory := mocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down
6 changes: 2 additions & 4 deletions upgrades/upgradevalidation/upgrade_test.go
Expand Up @@ -4,8 +4,6 @@
package upgradevalidation_test

import (
"net/http"

"github.com/golang/mock/gomock"
"github.com/juju/names/v4"
"github.com/juju/replicaset/v2"
Expand Down Expand Up @@ -42,7 +40,7 @@ func (s *upgradeValidationSuite) TestValidatorsForControllerUpgradeJuju3(c *gc.C
server := mocks.NewMockServer(ctrl)
serverFactory := mocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down Expand Up @@ -236,7 +234,7 @@ func (s *upgradeValidationSuite) TestValidatorsForModelUpgradeJuju3(c *gc.C) {
server := mocks.NewMockServer(ctrl)
serverFactory := mocks.NewMockServerFactory(ctrl)
s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down
10 changes: 6 additions & 4 deletions upgrades/upgradevalidation/validation.go
Expand Up @@ -5,6 +5,7 @@ package upgradevalidation

import (
"fmt"
"net/http"
"sort"
"strings"

Expand Down Expand Up @@ -298,10 +299,11 @@ func getCheckForLXDVersion(
if !lxdnames.IsDefaultCloud(cloudspec.Type) {
return nil, nil
}
httpClient := jujuhttp.NewClient(
jujuhttp.WithLogger(logger.ChildWithLabels("http", corelogger.HTTP)),
)
server, err := NewServerFactory(httpClient.Client()).RemoteServer(cloudspec)
server, err := NewServerFactory(lxd.NewHTTPClientFunc(func() *http.Client {
return jujuhttp.NewClient(
jujuhttp.WithLogger(logger.ChildWithLabels("http", corelogger.HTTP)),
).Client()
})).RemoteServer(cloudspec)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down
7 changes: 3 additions & 4 deletions upgrades/upgradevalidation/validation_test.go
Expand Up @@ -5,7 +5,6 @@ package upgradevalidation_test

import (
"fmt"
"net/http"

"github.com/golang/mock/gomock"
"github.com/juju/errors"
Expand Down Expand Up @@ -373,7 +372,7 @@ func (s *upgradeValidationSuite) assertGetCheckForLXDVersion(c *gc.C, cloudType
serverFactory := mocks.NewMockServerFactory(ctrl)

s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down Expand Up @@ -406,7 +405,7 @@ func (s *upgradeValidationSuite) TestGetCheckForLXDVersionSkippedForNonLXDCloud(
serverFactory := mocks.NewMockServerFactory(ctrl)

s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand All @@ -426,7 +425,7 @@ func (s *upgradeValidationSuite) TestGetCheckForLXDVersionFailed(c *gc.C) {
serverFactory := mocks.NewMockServerFactory(ctrl)

s.PatchValue(&upgradevalidation.NewServerFactory,
func(httpClient *http.Client) lxd.ServerFactory {
func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory {
return serverFactory
},
)
Expand Down

0 comments on commit 7728ffa

Please sign in to comment.