github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/service/manager.go (about)

     1  /*
     2   * Copyright (C) 2018 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package service
    19  
    20  import (
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/gofrs/uuid"
    25  	"github.com/pkg/errors"
    26  	"github.com/rs/zerolog/log"
    27  
    28  	"github.com/mysteriumnetwork/node/core/location/locationstate"
    29  	"github.com/mysteriumnetwork/node/core/policy"
    30  	"github.com/mysteriumnetwork/node/core/policy/localcopy"
    31  	"github.com/mysteriumnetwork/node/core/service/servicestate"
    32  	"github.com/mysteriumnetwork/node/identity"
    33  	"github.com/mysteriumnetwork/node/market"
    34  	"github.com/mysteriumnetwork/node/p2p"
    35  	"github.com/mysteriumnetwork/node/services/datatransfer"
    36  	"github.com/mysteriumnetwork/node/services/dvpn"
    37  	"github.com/mysteriumnetwork/node/services/scraping"
    38  	"github.com/mysteriumnetwork/node/services/wireguard"
    39  	"github.com/mysteriumnetwork/node/session/connectivity"
    40  	"github.com/mysteriumnetwork/node/utils/netutil"
    41  	"github.com/mysteriumnetwork/node/utils/reftracker"
    42  )
    43  
    44  var (
    45  	// ErrorLocation error indicates that action (i.e. disconnect)
    46  	ErrorLocation = errors.New("failed to detect service location")
    47  	// ErrUnsupportedServiceType indicates that manager tried to create an unsupported service type
    48  	ErrUnsupportedServiceType = errors.New("unsupported service type")
    49  	// ErrUnsupportedAccessPolicy indicates that manager tried to create service with unsupported access policy
    50  	ErrUnsupportedAccessPolicy = errors.New("unsupported access policy")
    51  )
    52  
    53  const (
    54  	channelIdleTimeout = 1 * time.Minute
    55  )
    56  
    57  // Service interface represents pluggable Mysterium service
    58  type Service interface {
    59  	Serve(instance *Instance) error
    60  	Stop() error
    61  	ConfigProvider
    62  }
    63  
    64  // DiscoveryFactory initiates instance which is able announce service discoverability
    65  type DiscoveryFactory func() Discovery
    66  
    67  // Discovery registers the service to the discovery api periodically
    68  type Discovery interface {
    69  	Start(ownIdentity identity.Identity, proposal func() market.ServiceProposal)
    70  	Stop()
    71  	Wait()
    72  }
    73  
    74  // LocationResolver detects location for service proposal.
    75  type locationResolver interface {
    76  	DetectLocation() (locationstate.Location, error)
    77  }
    78  
    79  // WaitForNATHole blocks until NAT hole is punched towards consumer through local NAT or until hole punching failed
    80  type WaitForNATHole func() error
    81  
    82  // NewManager creates new instance of pluggable instances manager
    83  func NewManager(
    84  	serviceRegistry *Registry,
    85  	discoveryFactory DiscoveryFactory,
    86  	eventPublisher Publisher,
    87  	policyOracle *localcopy.Oracle,
    88  	policyProvider policy.Provider,
    89  	p2pListener p2p.Listener,
    90  	sessionManager func(service *Instance, channel p2p.Channel) *SessionManager,
    91  	statusStorage connectivity.StatusStorage,
    92  	location locationResolver,
    93  ) *Manager {
    94  	return &Manager{
    95  		serviceRegistry:  serviceRegistry,
    96  		servicePool:      NewPool(eventPublisher),
    97  		discoveryFactory: discoveryFactory,
    98  		eventPublisher:   eventPublisher,
    99  		policyOracle:     policyOracle,
   100  		policyProvider:   policyProvider,
   101  		p2pListener:      p2pListener,
   102  		sessionManager:   sessionManager,
   103  		statusStorage:    statusStorage,
   104  		location:         location,
   105  	}
   106  }
   107  
   108  // Manager entrypoint which knows how to start pluggable Mysterium instances
   109  type Manager struct {
   110  	serviceRegistry *Registry
   111  	servicePool     *Pool
   112  
   113  	discoveryFactory DiscoveryFactory
   114  	eventPublisher   Publisher
   115  	policyOracle     *localcopy.Oracle
   116  	policyProvider   policy.Provider
   117  
   118  	p2pListener    p2p.Listener
   119  	sessionManager func(service *Instance, channel p2p.Channel) *SessionManager
   120  	statusStorage  connectivity.StatusStorage
   121  	location       locationResolver
   122  }
   123  
   124  // Start starts an instance of the given service type if knows one in service registry.
   125  // It passes the options to the start method of the service.
   126  // If an error occurs in the underlying service, the error is then returned.
   127  func (manager *Manager) Start(providerID identity.Identity, serviceType string, policyIDs []string, options Options) (id ID, err error) {
   128  	log.Debug().Fields(map[string]interface{}{
   129  		"providerID":  providerID.Address,
   130  		"serviceType": serviceType,
   131  		"policyIDs":   policyIDs,
   132  		"options":     options,
   133  	}).Msg("Starting service")
   134  	service, err := manager.serviceRegistry.Create(serviceType, options)
   135  	if err != nil {
   136  		return id, err
   137  	}
   138  
   139  	accessPolicies := manager.policyOracle.Policies(policyIDs)
   140  	var policyProvider policy.Provider
   141  	if len(policyIDs) == 1 && policyIDs[0] == "mysterium" {
   142  		policyProvider = manager.policyProvider
   143  	} else {
   144  		policyRules := localcopy.NewRepository()
   145  		if len(policyIDs) > 0 {
   146  			if err = manager.policyOracle.SubscribePolicies(accessPolicies, policyRules); err != nil {
   147  				log.Warn().Err(err).Msg("Can't find given access policyOracle")
   148  				return id, ErrUnsupportedAccessPolicy
   149  			}
   150  		}
   151  		policyProvider = policyRules
   152  	}
   153  
   154  	location, err := manager.location.DetectLocation()
   155  	if err != nil {
   156  		return "", err
   157  	}
   158  
   159  	proposal := market.NewProposal(providerID.Address, serviceType, market.NewProposalOpts{
   160  		Location:       market.NewLocation(location),
   161  		AccessPolicies: accessPolicies,
   162  		Contacts:       []market.Contact{manager.p2pListener.GetContact()},
   163  	})
   164  
   165  	discovery := manager.discoveryFactory()
   166  
   167  	id, err = generateID()
   168  	if err != nil {
   169  		return id, err
   170  	}
   171  
   172  	instance := &Instance{
   173  		ID:             id,
   174  		ProviderID:     providerID,
   175  		Type:           serviceType,
   176  		state:          servicestate.Starting,
   177  		Options:        options,
   178  		service:        service,
   179  		Proposal:       proposal,
   180  		policyProvider: policyProvider,
   181  		discovery:      discovery,
   182  		eventPublisher: manager.eventPublisher,
   183  		location:       manager.location,
   184  	}
   185  
   186  	discovery.Start(providerID, instance.proposalWithCurrentLocation)
   187  
   188  	channelHandlers := func(ch p2p.Channel) {
   189  		chID := "channel:" + ch.ID()
   190  		log.Info().Msgf("tracking p2p.Channel: %q", chID)
   191  		reftracker.Singleton().Put(chID, channelIdleTimeout, func() {
   192  			log.Debug().Msgf("collecting unused p2p.Channel %q", chID)
   193  			ch.Close()
   194  		})
   195  		instance.addP2PChannel(ch)
   196  		mng := manager.sessionManager(instance, ch)
   197  		subscribeSessionCreate(mng, ch)
   198  		subscribeSessionStatus(ch, manager.statusStorage)
   199  		subscribeSessionAcknowledge(mng, ch)
   200  		subscribeSessionDestroy(mng, ch)
   201  		subscribeSessionPayments(mng, ch)
   202  	}
   203  	stopP2PListener, err := manager.p2pListener.Listen(providerID, serviceType, channelHandlers)
   204  	if err != nil {
   205  		return id, fmt.Errorf("could not subscribe to p2p channels: %w", err)
   206  	}
   207  
   208  	manager.servicePool.Add(instance)
   209  
   210  	go func() {
   211  		instance.setState(servicestate.Running)
   212  
   213  		serveErr := service.Serve(instance)
   214  		if serveErr != nil {
   215  			log.Error().Err(serveErr).Msg("Service serve failed")
   216  		}
   217  
   218  		stopP2PListener()
   219  
   220  		stopErr := manager.servicePool.Stop(id)
   221  		if stopErr != nil {
   222  			log.Error().Err(stopErr).Msg("Service stop failed")
   223  		}
   224  
   225  		discovery.Wait()
   226  	}()
   227  
   228  	netutil.LogNetworkStats()
   229  
   230  	return id, nil
   231  }
   232  
   233  func generateID() (ID, error) {
   234  	uid, err := uuid.NewV4()
   235  	if err != nil {
   236  		return ID(""), err
   237  	}
   238  	return ID(uid.String()), nil
   239  }
   240  
   241  // List returns array of service instances.
   242  func (manager *Manager) List(includeAll bool) []*Instance {
   243  	runningInstances := manager.servicePool.List()
   244  	if !includeAll {
   245  		return runningInstances
   246  	}
   247  
   248  	added := map[string]bool{
   249  		wireguard.ServiceType:    false,
   250  		scraping.ServiceType:     false,
   251  		datatransfer.ServiceType: false,
   252  		dvpn.ServiceType:         false,
   253  	}
   254  
   255  	result := make([]*Instance, 0, len(added))
   256  	for _, instance := range runningInstances {
   257  		result = append(result, instance)
   258  		added[instance.Type] = true
   259  	}
   260  
   261  	for serviceType, alreadyAdded := range added {
   262  		if alreadyAdded {
   263  			continue
   264  		}
   265  
   266  		result = append(result, &Instance{
   267  			Type:  serviceType,
   268  			state: servicestate.NotRunning,
   269  		})
   270  	}
   271  
   272  	return result
   273  }
   274  
   275  // Kill stops all services.
   276  func (manager *Manager) Kill() error {
   277  	return manager.servicePool.StopAll()
   278  }
   279  
   280  // Stop stops the service.
   281  func (manager *Manager) Stop(id ID) error {
   282  	err := manager.servicePool.Stop(id)
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // Service returns a service instance by requested id.
   291  func (manager *Manager) Service(id ID) *Instance {
   292  	return manager.servicePool.Instance(id)
   293  }