github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/hookstate/ctlcmd/is_connected_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 ctlcmd_test
    21  
    22  import (
    23  	"fmt"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"github.com/snapcore/snapd/dirs"
    28  	"github.com/snapcore/snapd/overlord/hookstate"
    29  	"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
    30  	"github.com/snapcore/snapd/overlord/hookstate/hooktest"
    31  	"github.com/snapcore/snapd/overlord/snapstate"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  	"github.com/snapcore/snapd/snap"
    34  	"github.com/snapcore/snapd/snap/snaptest"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  type isConnectedSuite struct {
    39  	testutil.BaseTest
    40  	st          *state.State
    41  	mockHandler *hooktest.MockHandler
    42  }
    43  
    44  var _ = Suite(&isConnectedSuite{})
    45  
    46  func (s *isConnectedSuite) SetUpTest(c *C) {
    47  	s.BaseTest.SetUpTest(c)
    48  	dirs.SetRootDir(c.MkDir())
    49  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    50  	s.st = state.New(nil)
    51  	s.mockHandler = hooktest.NewMockHandler()
    52  }
    53  
    54  var isConnectedTests = []struct {
    55  	args                []string
    56  	stdout, stderr, err string
    57  	exitCode            int
    58  }{{
    59  	args: []string{"is-connected"},
    60  	err:  "the required argument `<plug|slot>` was not provided",
    61  }, {
    62  	args: []string{"is-connected", "plug1"},
    63  }, {
    64  	args: []string{"is-connected", "slot1"},
    65  }, {
    66  	// reported as not connected because of undesired flag
    67  	args:     []string{"is-connected", "plug2"},
    68  	exitCode: 1,
    69  }, {
    70  	// reported as not connected because of hotplug-gone flag
    71  	args:     []string{"is-connected", "plug3"},
    72  	exitCode: 1,
    73  }, {
    74  	args: []string{"is-connected", "slot2"},
    75  	err:  `snap "snap1" has no plug or slot named "slot2"`,
    76  }, {
    77  	args: []string{"is-connected", "foo"},
    78  	err:  `snap "snap1" has no plug or slot named "foo"`,
    79  }, {
    80  	// snap1:plug1 does not use an allowed interface
    81  	args: []string{"is-connected", "--pid", "1002", "plug1"},
    82  	err:  `cannot use --pid check with snap1:plug1`,
    83  }, {
    84  	// snap1:slot1 does not use an allowed interface
    85  	args: []string{"is-connected", "--pid", "1002", "slot1"},
    86  	err:  `cannot use --pid check with snap1:slot1`,
    87  }, {
    88  	// snap1:cc slot is not connected to snap2
    89  	args:     []string{"is-connected", "--pid", "1002", "cc"},
    90  	exitCode: 1,
    91  }, {
    92  	// snap1:cc slot is connected to snap3
    93  	args:     []string{"is-connected", "--pid", "1003", "cc"},
    94  	exitCode: 0,
    95  }, {
    96  	// snap1:cc slot is not connected to a non-snap pid
    97  	args:     []string{"is-connected", "--pid", "42", "cc"},
    98  	exitCode: ctlcmd.NotASnapCode,
    99  }, {
   100  	// snap1:cc slot is connected to a classic snap5
   101  	args:     []string{"is-connected", "--pid", "1005", "cc"},
   102  	exitCode: 0,
   103  }, {
   104  	// snap1:audio-record slot is not connected to classic snap5
   105  	args:     []string{"is-connected", "--pid", "1005", "audio-record"},
   106  	exitCode: ctlcmd.ClassicSnapCode,
   107  }, {
   108  	// snap1:plug1 does not use an allowed interface
   109  	args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "plug1"},
   110  	err:  `cannot use --apparmor-label check with snap1:plug1`,
   111  }, {
   112  	// snap1:slot1 does not use an allowed interface
   113  	args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "slot1"},
   114  	err:  `cannot use --apparmor-label check with snap1:slot1`,
   115  }, {
   116  	// snap1:cc slot is not connected to snap2
   117  	args:     []string{"is-connected", "--apparmor-label", "snap.snap2.app", "cc"},
   118  	exitCode: 1,
   119  }, {
   120  	// snap1:cc slot is connected to snap3
   121  	args:     []string{"is-connected", "--apparmor-label", "snap.snap3.app", "cc"},
   122  	exitCode: 0,
   123  }, {
   124  	// snap1:cc slot is not connected to a non-snap pid
   125  	args:     []string{"is-connected", "--apparmor-label", "/usr/bin/evince", "cc"},
   126  	exitCode: ctlcmd.NotASnapCode,
   127  }, {
   128  	// snap1:cc slot is connected to a classic snap5
   129  	args:     []string{"is-connected", "--apparmor-label", "snap.snap5.app", "cc"},
   130  	exitCode: 0,
   131  }, {
   132  	// snap1:audio-record slot is not connected to classic snap5
   133  	args:     []string{"is-connected", "--apparmor-label", "snap.snap5.app", "audio-record"},
   134  	exitCode: ctlcmd.ClassicSnapCode,
   135  }}
   136  
   137  func mockInstalledSnap(c *C, st *state.State, snapYaml string) {
   138  	info := snaptest.MockSnapCurrent(c, snapYaml, &snap.SideInfo{Revision: snap.R(1)})
   139  	snapstate.Set(st, info.InstanceName(), &snapstate.SnapState{
   140  		Active: true,
   141  		Sequence: []*snap.SideInfo{
   142  			{
   143  				RealName: info.SnapName(),
   144  				Revision: info.Revision,
   145  				SnapID:   info.InstanceName() + "-id",
   146  			},
   147  		},
   148  		Current: info.Revision,
   149  	})
   150  }
   151  
   152  func (s *isConnectedSuite) testIsConnected(c *C, context *hookstate.Context) {
   153  	mockInstalledSnap(c, s.st, `name: snap1
   154  plugs:
   155    plug1:
   156      interface: x11
   157    plug2:
   158      interface: x11
   159    plug3:
   160      interface: x11
   161  slots:
   162    slot1:
   163      interface: x11
   164    cc:
   165      interface: cups-control
   166    audio-record:
   167      interface: audio-record`)
   168  	mockInstalledSnap(c, s.st, `name: snap2
   169  slots:
   170    slot2:
   171      interface: x11`)
   172  	mockInstalledSnap(c, s.st, `name: snap3
   173  plugs:
   174    plug4:
   175      interface: x11
   176    cc:
   177      interface: cups-control
   178  slots:
   179    slot3:
   180      interface: x11`)
   181  	mockInstalledSnap(c, s.st, `name: snap4
   182  slots:
   183    slot4:
   184      interface: x11`)
   185  	mockInstalledSnap(c, s.st, `name: snap5
   186  confinement: classic
   187  plugs:
   188    cc:
   189      interface: cups-control`)
   190  	restore := ctlcmd.MockCgroupSnapNameFromPid(func(pid int) (string, error) {
   191  		switch {
   192  		case 1000 < pid && pid < 1100:
   193  			return fmt.Sprintf("snap%d", pid-1000), nil
   194  		default:
   195  			return "", fmt.Errorf("Not a snap")
   196  		}
   197  	})
   198  	defer restore()
   199  
   200  	s.st.Set("conns", map[string]interface{}{
   201  		"snap1:plug1 snap2:slot2": map[string]interface{}{},
   202  		"snap1:plug2 snap3:slot3": map[string]interface{}{"undesired": true},
   203  		"snap1:plug3 snap4:slot4": map[string]interface{}{"hotplug-gone": true},
   204  		"snap3:plug4 snap1:slot1": map[string]interface{}{},
   205  		"snap3:cc snap1:cc":       map[string]interface{}{},
   206  		"snap5:cc snap1:cc":       map[string]interface{}{},
   207  	})
   208  
   209  	s.st.Unlock()
   210  	defer s.st.Lock()
   211  
   212  	for _, test := range isConnectedTests {
   213  		stdout, stderr, err := ctlcmd.Run(context, test.args, 0)
   214  		comment := Commentf("%s", test.args)
   215  		if test.exitCode > 0 {
   216  			c.Check(err, DeepEquals, &ctlcmd.UnsuccessfulError{ExitCode: test.exitCode}, comment)
   217  		} else {
   218  			if test.err == "" {
   219  				c.Check(err, IsNil, comment)
   220  			} else {
   221  				c.Check(err, ErrorMatches, test.err, comment)
   222  			}
   223  		}
   224  
   225  		c.Check(string(stdout), Equals, test.stdout, comment)
   226  		c.Check(string(stderr), Equals, "", comment)
   227  	}
   228  }
   229  
   230  func (s *isConnectedSuite) TestIsConnectedFromHook(c *C) {
   231  	s.st.Lock()
   232  	defer s.st.Unlock()
   233  
   234  	task := s.st.NewTask("test-task", "my test task")
   235  	setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1), Hook: "test-hook"}
   236  
   237  	mockContext, err := hookstate.NewContext(task, s.st, setup, s.mockHandler, "")
   238  	c.Check(err, IsNil)
   239  
   240  	s.testIsConnected(c, mockContext)
   241  }
   242  
   243  func (s *isConnectedSuite) TestIsConnectedFromApp(c *C) {
   244  	s.st.Lock()
   245  	defer s.st.Unlock()
   246  
   247  	// ephemeral context
   248  	setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1)}
   249  	mockContext, err := hookstate.NewContext(nil, s.st, setup, s.mockHandler, "")
   250  	c.Check(err, IsNil)
   251  
   252  	// sanity
   253  	c.Assert(mockContext.IsEphemeral(), Equals, true)
   254  
   255  	s.testIsConnected(c, mockContext)
   256  }
   257  
   258  func (s *isConnectedSuite) TestNoContextError(c *C) {
   259  	stdout, stderr, err := ctlcmd.Run(nil, []string{"is-connected", "foo"}, 0)
   260  	c.Check(err, ErrorMatches, `cannot check connection status without a context`)
   261  	c.Check(string(stdout), Equals, "")
   262  	c.Check(string(stderr), Equals, "")
   263  }
   264  
   265  func (s *isConnectedSuite) TestGetRegularUser(c *C) {
   266  	s.st.Lock()
   267  
   268  	mockInstalledSnap(c, s.st, `name: snap1
   269  plugs:
   270    plug1:
   271      interface: x11`)
   272  
   273  	s.st.Set("conns", map[string]interface{}{
   274  		"snap1:plug1 snap2:slot2": map[string]interface{}{},
   275  	})
   276  
   277  	setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1)}
   278  
   279  	s.st.Unlock()
   280  
   281  	mockContext, err := hookstate.NewContext(nil, s.st, setup, s.mockHandler, "")
   282  	c.Assert(err, IsNil)
   283  	stdout, stderr, err := ctlcmd.Run(mockContext, []string{"is-connected", "plug1"}, 1000)
   284  	c.Check(err, IsNil)
   285  	c.Check(string(stdout), Equals, "")
   286  	c.Check(string(stderr), Equals, "")
   287  }