Skip to content

Commit

Permalink
Merge pull request #259 from masnax/detect-system
Browse files Browse the repository at this point in the history
Reuse existing MicroCeph and MicroOVN clusters
  • Loading branch information
masnax authored May 3, 2024
2 parents a8e8dcd + 3424df4 commit d5a3819
Show file tree
Hide file tree
Showing 17 changed files with 709 additions and 104 deletions.
29 changes: 29 additions & 0 deletions doc/how-to/initialise.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,35 @@ Complete the following steps to initialise MicroCloud:

See an example of the full initialisation process in the :ref:`Get started with MicroCloud <initialisation-process>` tutorial.

Excluding MicroCeph or MicroOVN from MicroCloud
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If the MicroOVN or MicroCeph snap is not installed on the system that runs :command:`microcloud init`, you will be prompted with the following question::

MicroCeph not found. Continue anyway? (yes/no) [default=yes]:

MicroOVN not found. Continue anyway? (yes/no) [default=yes]:

If you choose ``yes``, only existing services will be configured on all systems.
If you choose ``no``, the setup will be cancelled.

All other systems must have at least the same set of snaps installed as the system that runs :command:`microcloud init`, otherwise they will not be available to select from the list of systems.
Any questions associated to these systems will be skipped. For example, if MicroCeph is not installed, you will not be prompted for distributed storage configuration.

Reusing an existing MicroCeph or MicroOVN with MicroCloud
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If some of the systems are already part of a MicroCeph or MicroOVN cluster, you can choose to reuse this cluster when initialising MicroCloud when prompted with the following question::

"micro01" is already part of a MicroCeph cluster. Do you want to add this cluster to MicroCloud? (add/skip) [default=add]:

"micro01" is already part of a MicroOVN cluster. Do you want to add this cluster to MicroCloud? (add/skip) [default=add]:

If you choose ``add``, MicroCloud will add the remaining systems selected for initialisation to the pre-existing cluster.
If you choose ``skip``, the respective service will not be set up at all.

If more than one MicroCeph or MicroOVN cluster exists among the systems, the MicroCloud initialisation will be cancelled.

.. _howto-initialise-preseed:

Non-interactive configuration
Expand Down
43 changes: 43 additions & 0 deletions microcloud/api/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/canonical/lxd/lxd/response"
"github.com/canonical/lxd/lxd/util"
"github.com/canonical/lxd/shared/logger"
"github.com/canonical/microcluster/rest"
"github.com/canonical/microcluster/state"
"github.com/gorilla/mux"

"github.com/canonical/microcloud/microcloud/api/types"
"github.com/canonical/microcloud/microcloud/service"
Expand Down Expand Up @@ -55,6 +57,47 @@ var ServicesCmd = func(sh *service.Handler) rest.Endpoint {
}
}

// ServiceTokensCmd represents the /1.0/services/serviceType/tokens API on MicroCloud.
var ServiceTokensCmd = func(sh *service.Handler) rest.Endpoint {
return rest.Endpoint{
AllowedBeforeInit: true,
Name: "services/{serviceType}/tokens",
Path: "services/{serviceType}/tokens",

Post: rest.EndpointAction{Handler: authHandler(sh, serviceTokensPost), AllowUntrusted: true, ProxyTarget: true},
}
}

// serviceTokensPost issues a token for service using the MicroCloud proxy.
// Normally a token request to a service would be restricted to trusted systems,
// so this endpoint validates the mDNS auth token and then proxies the request to the local unix socket of the remote system.
func serviceTokensPost(s *state.State, r *http.Request) response.Response {
serviceType, err := url.PathUnescape(mux.Vars(r)["serviceType"])
if err != nil {
return response.SmartError(err)
}

// Parse the request.
req := types.ServiceTokensPost{}

err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return response.BadRequest(err)
}

sh, err := service.NewHandler(s.Name(), req.ClusterAddress, s.OS.StateDir, false, false, types.ServiceType(serviceType))
if err != nil {
return response.SmartError(err)
}

token, err := sh.Services[types.ServiceType(serviceType)].IssueToken(s.Context, req.JoinerName)
if err != nil {
return response.SmartError(fmt.Errorf("Failed to issue %s token for peer %q: %w", serviceType, req.JoinerName, err))
}

return response.SyncResponse(true, token)
}

// servicesPut updates the cluster status of the MicroCloud peer.
func servicesPut(state *state.State, r *http.Request) response.Response {
// Parse the request.
Expand Down
6 changes: 6 additions & 0 deletions microcloud/api/types/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ type ServiceToken struct {
Service ServiceType `json:"service" yaml:"service"`
JoinToken string `json:"join_token" yaml:"join_token"`
}

// ServiceTokensPost represents a request to issue a join token for a MicroCloud service.
type ServiceTokensPost struct {
ClusterAddress string `json:"cluster_address" yaml:"cluster_address"`
JoinerName string `json:"joiner_name" yaml:"joiner_name"`
}
14 changes: 14 additions & 0 deletions microcloud/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ func JoinServices(ctx context.Context, c *client.Client, data types.ServicesPut)

return nil
}

// RemoteIssueToken issues a token on the remote MicroCloud, trusted by the mDNS auth secret.
func RemoteIssueToken(ctx context.Context, c *client.Client, serviceType types.ServiceType, data types.ServiceTokensPost) (string, error) {
queryCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()

var token string
err := c.Query(queryCtx, "POST", api.NewURL().Path("services", string(serviceType), "tokens"), data, &token)
if err != nil {
return "", fmt.Errorf("Failed to issue remote token: %w", err)
}

return token, nil
}
42 changes: 42 additions & 0 deletions microcloud/cmd/microcloud/ask.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sort"
"strings"

"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/api"
cli "github.com/canonical/lxd/shared/cmd"
"github.com/canonical/lxd/shared/logger"
Expand Down Expand Up @@ -822,3 +823,44 @@ func (c *CmdControl) askNetwork(sh *service.Handler, systems map[string]InitSyst

return nil
}

// askClustered checks whether any of the selected systems have already initialized any expected services.
// If a service is already initialized on some systems, we will offer to add the remaining systems, or skip that service.
// If multiple systems have separately initialized the same service, we will abort initialization.
// Preseed yamls will have a flag that sets whether to reuse the cluster.
// In auto setup, we will expect no initialized services so that we can be opinionated about how we configure the cluster without user input.
func (c *CmdControl) askClustered(s *service.Handler, autoSetup bool, systems map[string]InitSystem) error {
expectedServices := make(map[types.ServiceType]service.Service, len(s.Services))
for k, v := range s.Services {
expectedServices[k] = v
}

for serviceType := range expectedServices {
initializedSystem, _, err := checkClustered(s, autoSetup, serviceType, systems)
if err != nil {
return err
}

if initializedSystem != "" {
question := fmt.Sprintf("%q is already part of a %s cluster. Do you want to add this cluster to Microcloud? (add/skip) [default=add]", initializedSystem, serviceType)
validator := func(s string) error {
if !shared.ValueInSlice[string](s, []string{"add", "skip"}) {
return fmt.Errorf("Invalid input, expected one of (add,skip) but got %q", s)
}

return nil
}

addOrSkip, err := c.asker.AskString(question, "add", validator)
if err != nil {
return err
}

if addOrSkip != "add" {
delete(s.Services, serviceType)
}
}
}

return nil
}
Loading

0 comments on commit d5a3819

Please sign in to comment.