github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/service/windows/service.go (about)

     1  // Copyright 2015 Cloudbase Solutions
     2  // Copyright 2015 Canonical Ltd.
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package windows
     6  
     7  import (
     8  	"fmt"
     9  	"runtime"
    10  	"strings"
    11  	"syscall"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/utils/shell"
    16  
    17  	"github.com/juju/juju/service/common"
    18  )
    19  
    20  var (
    21  	logger   = loggo.GetLogger("juju.worker.deployer.service")
    22  	renderer = &shell.PowershellRenderer{}
    23  
    24  	// c_ERROR_SERVICE_DOES_NOT_EXIST is returned by the OS when trying to open
    25  	// an inexistent service
    26  	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684330%28v=vs.85%29.aspx
    27  	c_ERROR_SERVICE_DOES_NOT_EXIST syscall.Errno = 0x424
    28  
    29  	// c_ERROR_SERVICE_EXISTS is returned by the operating system if the service
    30  	// we are trying to create, already exists
    31  	c_ERROR_SERVICE_EXISTS syscall.Errno = 0x431
    32  
    33  	// c_ERROR_ACCESS_DENIED is returned by the operating system if access is denied
    34  	// to that service.
    35  	c_ERROR_ACCESS_DENIED syscall.Errno = 0x5
    36  
    37  	// This is the user under which juju services start. We chose to use a
    38  	// normal user for this purpose because some installers require a normal
    39  	// user with a proper user profile to actually run. This user is created
    40  	// via userdata, and should exist on all juju bootstrapped systems.
    41  	// Required privileges for this user are:
    42  	// SeAssignPrimaryTokenPrivilege
    43  	// SeServiceLogonRight
    44  	jujudUser = ".\\jujud"
    45  )
    46  
    47  // IsRunning returns whether or not windows is the local init system.
    48  func IsRunning() (bool, error) {
    49  	return runtime.GOOS == "windows", nil
    50  }
    51  
    52  // ListServices returns the name of all installed services on the
    53  // local host.
    54  func ListServices() ([]string, error) {
    55  	return listServices()
    56  }
    57  
    58  // ListCommand returns a command that will list the services on a host.
    59  func ListCommand() string {
    60  	return `(Get-Service).Name`
    61  }
    62  
    63  // ServiceManager exposes methods needed to manage a windows service
    64  type ServiceManager interface {
    65  	// Start starts a service.
    66  	Start(name string) error
    67  	// Stop stops a service.
    68  	Stop(name string) error
    69  	// Delete deletes a service.
    70  	Delete(name string) error
    71  	// Create creates a service with the given config.
    72  	Create(name string, conf common.Conf) error
    73  	// Running returns the status of a service.
    74  	Running(name string) (bool, error)
    75  	// Exists checks whether the config of the installed service matches the
    76  	// config supplied to this function
    77  	Exists(name string, conf common.Conf) (bool, error)
    78  	// ChangeServicePassword can change the password of a service
    79  	// as long as it belongs to the user defined in this package
    80  	ChangeServicePassword(name, newPassword string) error
    81  }
    82  
    83  // Service represents a service running on the current system
    84  type Service struct {
    85  	common.Service
    86  	manager ServiceManager
    87  }
    88  
    89  func newService(name string, conf common.Conf, manager ServiceManager) *Service {
    90  	return &Service{
    91  		Service: common.Service{
    92  			Name: name,
    93  			Conf: conf,
    94  		},
    95  		manager: manager,
    96  	}
    97  }
    98  
    99  // NewService returns a new Service type
   100  func NewService(name string, conf common.Conf) (*Service, error) {
   101  	m, err := NewServiceManager()
   102  	if err != nil {
   103  		return nil, errors.Trace(err)
   104  	}
   105  	return newService(name, conf, m), nil
   106  }
   107  
   108  // Name implements service.Service.
   109  func (s *Service) Name() string {
   110  	return s.Service.Name
   111  }
   112  
   113  // Conf implements service.Service.
   114  func (s *Service) Conf() common.Conf {
   115  	return s.Service.Conf
   116  }
   117  
   118  // Validate checks the service for invalid values.
   119  func (s *Service) Validate() error {
   120  	if err := s.Service.Validate(renderer); err != nil {
   121  		return errors.Trace(err)
   122  	}
   123  
   124  	if s.Service.Conf.Transient {
   125  		return errors.NotSupportedf("transient services")
   126  	}
   127  
   128  	if s.Service.Conf.AfterStopped != "" {
   129  		return errors.NotSupportedf("Conf.AfterStopped")
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  func (s *Service) Running() (bool, error) {
   136  	if ok, err := s.Installed(); err != nil {
   137  		return false, errors.Trace(err)
   138  	} else if !ok {
   139  		return false, nil
   140  	}
   141  	return s.manager.Running(s.Name())
   142  }
   143  
   144  // Installed returns whether the service is installed
   145  func (s *Service) Installed() (bool, error) {
   146  	services, err := ListServices()
   147  	if err != nil {
   148  		return false, errors.Trace(err)
   149  	}
   150  	for _, val := range services {
   151  		if s.Name() == val {
   152  			return true, nil
   153  		}
   154  	}
   155  	return false, nil
   156  }
   157  
   158  // Exists returns whether the service configuration reflects the
   159  // desired state
   160  func (s *Service) Exists() (bool, error) {
   161  	return s.manager.Exists(s.Name(), s.Conf())
   162  }
   163  
   164  // Start starts the service.
   165  func (s *Service) Start() error {
   166  	logger.Infof("Starting service %q", s.Service.Name)
   167  	running, err := s.Running()
   168  	if err != nil {
   169  		return errors.Trace(err)
   170  	}
   171  	if running {
   172  		logger.Infof("Service %q already running", s.Service.Name)
   173  		return nil
   174  	}
   175  	err = s.manager.Start(s.Name())
   176  	return err
   177  }
   178  
   179  // Stop stops the service.
   180  func (s *Service) Stop() error {
   181  	running, err := s.Running()
   182  	if err != nil {
   183  		return errors.Trace(err)
   184  	}
   185  	if !running {
   186  		return nil
   187  	}
   188  	err = s.manager.Stop(s.Name())
   189  	return err
   190  }
   191  
   192  // Remove deletes the service.
   193  func (s *Service) Remove() error {
   194  	installed, err := s.Installed()
   195  	if err != nil {
   196  		return err
   197  	}
   198  	if !installed {
   199  		return nil
   200  	}
   201  
   202  	err = s.Stop()
   203  	if err != nil {
   204  		return errors.Trace(err)
   205  	}
   206  	err = s.manager.Delete(s.Name())
   207  	return err
   208  }
   209  
   210  // Install installs and starts the service.
   211  func (s *Service) Install() error {
   212  	err := s.Validate()
   213  	if err != nil {
   214  		return errors.Trace(err)
   215  	}
   216  	installed, err := s.Installed()
   217  	if err != nil {
   218  		return errors.Trace(err)
   219  	}
   220  	if installed {
   221  		return errors.Errorf("Service %s already installed", s.Service.Name)
   222  	}
   223  
   224  	logger.Infof("Installing Service %v", s.Name)
   225  	err = s.manager.Create(s.Name(), s.Conf())
   226  	if err != nil {
   227  		return errors.Trace(err)
   228  	}
   229  	return nil
   230  }
   231  
   232  // InstallCommands returns shell commands to install the service.
   233  func (s *Service) InstallCommands() ([]string, error) {
   234  	cmd := fmt.Sprintf(serviceInstallCommands[1:],
   235  		renderer.Quote(s.Service.Name),
   236  		renderer.Quote(s.Service.Conf.Desc),
   237  		renderer.Quote(s.Service.Conf.ExecStart),
   238  		renderer.Quote(s.Service.Name),
   239  		renderer.Quote(s.Service.Name),
   240  	)
   241  	return strings.Split(cmd, "\n"), nil
   242  }
   243  
   244  // StartCommands returns shell commands to start the service.
   245  func (s *Service) StartCommands() ([]string, error) {
   246  	cmd := fmt.Sprintf(`Start-Service %s`, renderer.Quote(s.Service.Name))
   247  	return []string{cmd}, nil
   248  }
   249  
   250  const serviceInstallCommands = `
   251  New-Service -Credential $jujuCreds -Name %s -DependsOn Winmgmt -DisplayName %s %s
   252  sc.exe failure %s reset=5 actions=restart/1000
   253  sc.exe failureflag %s 1`