github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/usersession/client/client_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package client_test
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"net"
    26  	"net/http"
    27  	"os"
    28  	"path/filepath"
    29  	"testing"
    30  	"time"
    31  
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/testutil"
    36  	"github.com/snapcore/snapd/usersession/client"
    37  )
    38  
    39  var (
    40  	timeout = testutil.HostScaledTimeout(80 * time.Millisecond)
    41  )
    42  
    43  func Test(t *testing.T) { TestingT(t) }
    44  
    45  type clientSuite struct {
    46  	testutil.BaseTest
    47  
    48  	cli *client.Client
    49  
    50  	server  *http.Server
    51  	handler http.Handler
    52  }
    53  
    54  var _ = Suite(&clientSuite{})
    55  
    56  func (s *clientSuite) SetUpTest(c *C) {
    57  	s.BaseTest.SetUpTest(c)
    58  	dirs.SetRootDir(c.MkDir())
    59  
    60  	s.handler = nil
    61  
    62  	s.server = &http.Server{Handler: s}
    63  	for _, uid := range []int{1000, 42} {
    64  		sock := fmt.Sprintf("%s/%d/snapd-session-agent.socket", dirs.XdgRuntimeDirBase, uid)
    65  		err := os.MkdirAll(filepath.Dir(sock), 0755)
    66  		c.Assert(err, IsNil)
    67  		l, err := net.Listen("unix", sock)
    68  		c.Assert(err, IsNil)
    69  		go func(l net.Listener) {
    70  			err := s.server.Serve(l)
    71  			c.Check(err, Equals, http.ErrServerClosed)
    72  		}(l)
    73  	}
    74  
    75  	s.cli = client.New()
    76  }
    77  
    78  func (s *clientSuite) TearDownTest(c *C) {
    79  	s.BaseTest.TearDownTest(c)
    80  	dirs.SetRootDir("")
    81  
    82  	err := s.server.Shutdown(context.Background())
    83  	c.Check(err, IsNil)
    84  }
    85  
    86  func (s *clientSuite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    87  	if s.handler == nil {
    88  		w.WriteHeader(500)
    89  		return
    90  	}
    91  	s.handler.ServeHTTP(w, r)
    92  }
    93  
    94  func (s *clientSuite) TestBadJsonResponse(c *C) {
    95  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    96  		w.Header().Set("Content-Type", "application/json")
    97  		w.WriteHeader(200)
    98  		w.Write([]byte(`{"type":`))
    99  	})
   100  	si, err := s.cli.SessionInfo(context.Background())
   101  	c.Check(si, DeepEquals, map[int]client.SessionInfo{})
   102  	c.Check(err, ErrorMatches, `cannot decode "{\\"type\\":": unexpected EOF`)
   103  }
   104  
   105  func (s *clientSuite) TestAgentTimeout(c *C) {
   106  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   107  		// Delay one of the agents from responding, but don't
   108  		// stick around if the client disconnects.
   109  		if r.Host == "1000" {
   110  			select {
   111  			case <-r.Context().Done():
   112  				return
   113  			case <-time.After(5 * time.Second):
   114  				c.Fatal("Request context was not cancelled")
   115  			}
   116  		}
   117  		w.Header().Set("Content-Type", "application/json")
   118  		w.WriteHeader(200)
   119  		w.Write([]byte(`{
   120    "type": "sync",
   121    "result": {
   122      "version": "42"
   123    }
   124  }`))
   125  	})
   126  
   127  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   128  	defer cancel()
   129  	si, err := s.cli.SessionInfo(ctx)
   130  
   131  	// An error is reported, and we receive information about the
   132  	// agent that replied on time.
   133  	c.Assert(err, ErrorMatches, `Get \"?http://1000/v1/session-info\"?: context deadline exceeded`)
   134  	c.Check(si, DeepEquals, map[int]client.SessionInfo{
   135  		42: {Version: "42"},
   136  	})
   137  }
   138  
   139  func (s *clientSuite) TestSessionInfo(c *C) {
   140  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   141  		w.Header().Set("Content-Type", "application/json")
   142  		w.WriteHeader(200)
   143  		w.Write([]byte(`{
   144    "type": "sync",
   145    "result": {
   146      "version": "42"
   147    }
   148  }`))
   149  	})
   150  	si, err := s.cli.SessionInfo(context.Background())
   151  	c.Assert(err, IsNil)
   152  	c.Check(si, DeepEquals, map[int]client.SessionInfo{
   153  		42:   {Version: "42"},
   154  		1000: {Version: "42"},
   155  	})
   156  }
   157  
   158  func (s *clientSuite) TestSessionInfoError(c *C) {
   159  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   160  		w.Header().Set("Content-Type", "application/json")
   161  		w.WriteHeader(500)
   162  		w.Write([]byte(`{
   163    "type": "error",
   164    "result": {
   165      "message": "something bad happened"
   166    }
   167  }`))
   168  	})
   169  	si, err := s.cli.SessionInfo(context.Background())
   170  	c.Check(si, DeepEquals, map[int]client.SessionInfo{})
   171  	c.Check(err, ErrorMatches, "something bad happened")
   172  	c.Check(err, DeepEquals, &client.Error{
   173  		Kind:    "",
   174  		Message: "something bad happened",
   175  		Value:   nil,
   176  	})
   177  }
   178  
   179  func (s *clientSuite) TestSessionInfoWrongResultType(c *C) {
   180  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   181  		w.Header().Set("Content-Type", "application/json")
   182  		w.WriteHeader(200)
   183  		w.Write([]byte(`{
   184    "type": "sync",
   185    "result": ["a", "list"]
   186  }`))
   187  	})
   188  	si, err := s.cli.SessionInfo(context.Background())
   189  	c.Check(si, DeepEquals, map[int]client.SessionInfo{})
   190  	c.Check(err, ErrorMatches, `json: cannot unmarshal array into Go value of type client.SessionInfo`)
   191  }
   192  
   193  func (s *clientSuite) TestServicesDaemonReload(c *C) {
   194  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   195  		w.Header().Set("Content-Type", "application/json")
   196  		w.WriteHeader(200)
   197  		w.Write([]byte(`{
   198    "type": "sync",
   199    "result": null
   200  }`))
   201  	})
   202  	err := s.cli.ServicesDaemonReload(context.Background())
   203  	c.Assert(err, IsNil)
   204  }
   205  
   206  func (s *clientSuite) TestServicesDaemonReloadError(c *C) {
   207  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   208  		w.Header().Set("Content-Type", "application/json")
   209  		w.WriteHeader(500)
   210  		w.Write([]byte(`{
   211    "type": "error",
   212    "result": {
   213      "message": "something bad happened"
   214    }
   215  }`))
   216  	})
   217  	err := s.cli.ServicesDaemonReload(context.Background())
   218  	c.Check(err, ErrorMatches, "something bad happened")
   219  	c.Check(err, DeepEquals, &client.Error{
   220  		Kind:    "",
   221  		Message: "something bad happened",
   222  		Value:   nil,
   223  	})
   224  }
   225  
   226  func (s *clientSuite) TestServicesStart(c *C) {
   227  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   228  		w.Header().Set("Content-Type", "application/json")
   229  		w.WriteHeader(200)
   230  		w.Write([]byte(`{
   231    "type": "sync",
   232    "result": null
   233  }`))
   234  	})
   235  	startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service", "service2.service"})
   236  	c.Assert(err, IsNil)
   237  	c.Check(startFailures, HasLen, 0)
   238  	c.Check(stopFailures, HasLen, 0)
   239  }
   240  
   241  func (s *clientSuite) TestServicesStartFailure(c *C) {
   242  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   243  		w.Header().Set("Content-Type", "application/json")
   244  		w.WriteHeader(500)
   245  		w.Write([]byte(`{
   246    "type": "error",
   247    "result": {
   248      "kind": "service-control",
   249      "message": "failed to start services",
   250      "value": {
   251        "start-errors": {
   252          "service2.service": "failed to start"
   253        }
   254      }
   255    }
   256  }`))
   257  	})
   258  	startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service", "service2.service"})
   259  	c.Assert(err, ErrorMatches, "failed to start services")
   260  	c.Check(startFailures, HasLen, 2)
   261  	c.Check(stopFailures, HasLen, 0)
   262  
   263  	failure0 := startFailures[0]
   264  	failure1 := startFailures[1]
   265  	if failure0.Uid == 1000 {
   266  		failure0, failure1 = failure1, failure0
   267  	}
   268  	c.Check(failure0, DeepEquals, client.ServiceFailure{
   269  		Uid:     42,
   270  		Service: "service2.service",
   271  		Error:   "failed to start",
   272  	})
   273  	c.Check(failure1, DeepEquals, client.ServiceFailure{
   274  		Uid:     1000,
   275  		Service: "service2.service",
   276  		Error:   "failed to start",
   277  	})
   278  }
   279  
   280  func (s *clientSuite) TestServicesStartOneAgentFailure(c *C) {
   281  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   282  		w.Header().Set("Content-Type", "application/json")
   283  
   284  		// Only produce failure from one agent
   285  		if r.Host != "42" {
   286  			w.WriteHeader(200)
   287  			w.Write([]byte(`{"type": "sync","result": null}`))
   288  			return
   289  		}
   290  
   291  		w.WriteHeader(500)
   292  		w.Write([]byte(`{
   293    "type": "error",
   294    "result": {
   295      "kind": "service-control",
   296      "message": "failed to start services",
   297      "value": {
   298        "start-errors": {
   299          "service2.service": "failed to start"
   300        }
   301      }
   302    }
   303  }`))
   304  	})
   305  	startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service", "service2.service"})
   306  	c.Assert(err, ErrorMatches, "failed to start services")
   307  	c.Check(startFailures, DeepEquals, []client.ServiceFailure{
   308  		{
   309  			Uid:     42,
   310  			Service: "service2.service",
   311  			Error:   "failed to start",
   312  		},
   313  	})
   314  	c.Check(stopFailures, HasLen, 0)
   315  }
   316  
   317  func (s *clientSuite) TestServicesStartBadErrors(c *C) {
   318  	errorValue := "null"
   319  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   320  		w.Header().Set("Content-Type", "application/json")
   321  
   322  		// Only produce failure from one agent
   323  		if r.Host != "42" {
   324  			w.WriteHeader(200)
   325  			w.Write([]byte(`{"type": "sync","result": null}`))
   326  			return
   327  		}
   328  
   329  		w.WriteHeader(500)
   330  		w.Write([]byte(fmt.Sprintf(`{
   331    "type": "error",
   332    "result": {
   333      "kind": "service-control",
   334      "message": "failed to stop services",
   335      "value": %s
   336    }
   337  }`, errorValue)))
   338  	})
   339  
   340  	// Error value is not a map
   341  	errorValue = "[]"
   342  	startFailures, stopFailures, err := s.cli.ServicesStart(context.Background(), []string{"service1.service"})
   343  	c.Check(err, ErrorMatches, "failed to stop services")
   344  	c.Check(startFailures, HasLen, 0)
   345  	c.Check(stopFailures, HasLen, 0)
   346  
   347  	// Error value is a map, but missing start-errors/stop-errors keys
   348  	errorValue = "{}"
   349  	startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"})
   350  	c.Check(err, ErrorMatches, "failed to stop services")
   351  	c.Check(startFailures, HasLen, 0)
   352  	c.Check(stopFailures, HasLen, 0)
   353  
   354  	// start-errors/stop-errors are not maps
   355  	errorValue = `{
   356    "start-errors": [],
   357    "stop-errors": 42
   358  }`
   359  	startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"})
   360  	c.Check(err, ErrorMatches, "failed to stop services")
   361  	c.Check(startFailures, HasLen, 0)
   362  	c.Check(stopFailures, HasLen, 0)
   363  
   364  	// start-error/stop-error values are not strings
   365  	errorValue = `{
   366    "start-errors": {
   367      "service1.service": 42
   368    },
   369    "stop-errors": {
   370      "service1.service": {}
   371    }
   372  }`
   373  	startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"})
   374  	c.Check(err, ErrorMatches, "failed to stop services")
   375  	c.Check(startFailures, HasLen, 0)
   376  	c.Check(stopFailures, HasLen, 0)
   377  
   378  	// When some valid service failures are mixed in with bad
   379  	// ones, report the valid failure along with the error
   380  	// message.
   381  	errorValue = `{
   382    "start-errors": {
   383      "service1.service": "failure one",
   384      "service2.service": 42
   385    }
   386  }`
   387  	startFailures, stopFailures, err = s.cli.ServicesStart(context.Background(), []string{"service1.service"})
   388  	c.Check(err, ErrorMatches, "failed to stop services")
   389  	c.Check(startFailures, DeepEquals, []client.ServiceFailure{
   390  		{
   391  			Uid:     42,
   392  			Service: "service1.service",
   393  			Error:   "failure one",
   394  		},
   395  	})
   396  	c.Check(stopFailures, HasLen, 0)
   397  }
   398  
   399  func (s *clientSuite) TestServicesStop(c *C) {
   400  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   401  		w.Header().Set("Content-Type", "application/json")
   402  		w.WriteHeader(200)
   403  		w.Write([]byte(`{
   404    "type": "sync",
   405    "result": null
   406  }`))
   407  	})
   408  	failures, err := s.cli.ServicesStop(context.Background(), []string{"service1.service", "service2.service"})
   409  	c.Assert(err, IsNil)
   410  	c.Check(failures, HasLen, 0)
   411  }
   412  
   413  func (s *clientSuite) TestServicesStopFailure(c *C) {
   414  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   415  		w.Header().Set("Content-Type", "application/json")
   416  		w.WriteHeader(500)
   417  		w.Write([]byte(`{
   418    "type": "error",
   419    "result": {
   420      "kind": "service-control",
   421      "message": "failed to stop services",
   422      "value": {
   423        "stop-errors": {
   424          "service2.service": "failed to stop"
   425        }
   426      }
   427    }
   428  }`))
   429  	})
   430  	failures, err := s.cli.ServicesStop(context.Background(), []string{"service1.service", "service2.service"})
   431  	c.Assert(err, ErrorMatches, "failed to stop services")
   432  	c.Check(failures, HasLen, 2)
   433  	failure0 := failures[0]
   434  	failure1 := failures[1]
   435  	if failure0.Uid == 1000 {
   436  		failure0, failure1 = failure1, failure0
   437  	}
   438  	c.Check(failure0, DeepEquals, client.ServiceFailure{
   439  		Uid:     42,
   440  		Service: "service2.service",
   441  		Error:   "failed to stop",
   442  	})
   443  	c.Check(failure1, DeepEquals, client.ServiceFailure{
   444  		Uid:     1000,
   445  		Service: "service2.service",
   446  		Error:   "failed to stop",
   447  	})
   448  }
   449  
   450  func (s *clientSuite) TestPendingRefreshNotification(c *C) {
   451  	s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   452  		c.Assert(r.URL.Path, Equals, "/v1/notifications/pending-refresh")
   453  		w.Header().Set("Content-Type", "application/json")
   454  		w.WriteHeader(200)
   455  		w.Write([]byte(`{"type": "sync"}`))
   456  	})
   457  	err := s.cli.PendingRefreshNotification(context.Background(), &client.PendingSnapRefreshInfo{})
   458  	c.Assert(err, IsNil)
   459  }