github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_interfaces_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 daemon_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"strings"
    29  
    30  	"gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/client"
    33  	"github.com/snapcore/snapd/daemon"
    34  	"github.com/snapcore/snapd/interfaces"
    35  	"github.com/snapcore/snapd/interfaces/builtin"
    36  	"github.com/snapcore/snapd/interfaces/ifacetest"
    37  	"github.com/snapcore/snapd/overlord/ifacestate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  )
    40  
    41  var _ = check.Suite(&interfacesSuite{})
    42  
    43  type interfacesSuite struct {
    44  	apiBaseSuite
    45  }
    46  
    47  func mockIface(c *check.C, d *daemon.Daemon, iface interfaces.Interface) {
    48  	err := d.Overlord().InterfaceManager().Repository().AddInterface(iface)
    49  	c.Assert(err, check.IsNil)
    50  }
    51  
    52  // inverseCaseMapper implements SnapMapper to use lower case internally and upper case externally.
    53  type inverseCaseMapper struct {
    54  	ifacestate.IdentityMapper // Embed the identity mapper to reuse empty state mapping functions.
    55  }
    56  
    57  func (m *inverseCaseMapper) RemapSnapFromRequest(snapName string) string {
    58  	return strings.ToLower(snapName)
    59  }
    60  
    61  func (m *inverseCaseMapper) RemapSnapToResponse(snapName string) string {
    62  	return strings.ToUpper(snapName)
    63  }
    64  
    65  func (m *inverseCaseMapper) SystemSnapName() string {
    66  	return "core"
    67  }
    68  
    69  // Tests for POST /v2/interfaces
    70  
    71  const (
    72  	consumerYaml = `
    73  name: consumer
    74  version: 1
    75  apps:
    76   app:
    77  plugs:
    78   plug:
    79    interface: test
    80    key: value
    81    label: label
    82  `
    83  
    84  	producerYaml = `
    85  name: producer
    86  version: 1
    87  apps:
    88   app:
    89  slots:
    90   slot:
    91    interface: test
    92    key: value
    93    label: label
    94  `
    95  
    96  	coreProducerYaml = `
    97  name: core
    98  version: 1
    99  slots:
   100   slot:
   101    interface: test
   102    key: value
   103    label: label
   104  `
   105  
   106  	differentProducerYaml = `
   107  name: producer
   108  version: 1
   109  apps:
   110   app:
   111  slots:
   112   slot:
   113    interface: different
   114    key: value
   115    label: label
   116  `
   117  )
   118  
   119  func (s *interfacesSuite) TestConnectPlugSuccess(c *check.C) {
   120  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   121  	defer restore()
   122  	// Install an inverse case mapper to exercise the interface mapping at the same time.
   123  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
   124  	defer restore()
   125  
   126  	d := s.daemon(c)
   127  
   128  	s.mockSnap(c, consumerYaml)
   129  	s.mockSnap(c, producerYaml)
   130  
   131  	d.Overlord().Loop()
   132  	defer d.Overlord().Stop()
   133  
   134  	action := &client.InterfaceAction{
   135  		Action: "connect",
   136  		Plugs:  []client.Plug{{Snap: "CONSUMER", Name: "plug"}},
   137  		Slots:  []client.Slot{{Snap: "PRODUCER", Name: "slot"}},
   138  	}
   139  	text, err := json.Marshal(action)
   140  	c.Assert(err, check.IsNil)
   141  	buf := bytes.NewBuffer(text)
   142  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   143  	c.Assert(err, check.IsNil)
   144  	rec := httptest.NewRecorder()
   145  	s.req(c, req, nil).ServeHTTP(rec, req)
   146  	c.Check(rec.Code, check.Equals, 202)
   147  	var body map[string]interface{}
   148  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   149  	c.Check(err, check.IsNil)
   150  	id := body["change"].(string)
   151  
   152  	st := d.Overlord().State()
   153  	st.Lock()
   154  	chg := st.Change(id)
   155  	st.Unlock()
   156  	c.Assert(chg, check.NotNil)
   157  
   158  	<-chg.Ready()
   159  
   160  	st.Lock()
   161  	err = chg.Err()
   162  	st.Unlock()
   163  	c.Assert(err, check.IsNil)
   164  
   165  	repo := d.Overlord().InterfaceManager().Repository()
   166  	ifaces := repo.Interfaces()
   167  	c.Assert(ifaces.Connections, check.HasLen, 1)
   168  	c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
   169  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   170  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   171  	}})
   172  }
   173  
   174  func (s *interfacesSuite) TestConnectPlugFailureInterfaceMismatch(c *check.C) {
   175  	d := s.daemon(c)
   176  
   177  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   178  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "different"})
   179  	s.mockSnap(c, consumerYaml)
   180  	s.mockSnap(c, differentProducerYaml)
   181  
   182  	action := &client.InterfaceAction{
   183  		Action: "connect",
   184  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   185  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   186  	}
   187  	text, err := json.Marshal(action)
   188  	c.Assert(err, check.IsNil)
   189  	buf := bytes.NewBuffer(text)
   190  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   191  	c.Assert(err, check.IsNil)
   192  	rec := httptest.NewRecorder()
   193  	s.req(c, req, nil).ServeHTTP(rec, req)
   194  	c.Check(rec.Code, check.Equals, 400)
   195  	var body map[string]interface{}
   196  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   197  	c.Check(err, check.IsNil)
   198  	c.Check(body, check.DeepEquals, map[string]interface{}{
   199  		"result": map[string]interface{}{
   200  			"message": "cannot connect consumer:plug (\"test\" interface) to producer:slot (\"different\" interface)",
   201  		},
   202  		"status":      "Bad Request",
   203  		"status-code": 400.0,
   204  		"type":        "error",
   205  	})
   206  	repo := d.Overlord().InterfaceManager().Repository()
   207  	ifaces := repo.Interfaces()
   208  	c.Assert(ifaces.Connections, check.HasLen, 0)
   209  }
   210  
   211  func (s *interfacesSuite) TestConnectPlugFailureNoSuchPlug(c *check.C) {
   212  	d := s.daemon(c)
   213  
   214  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   215  	// there is no consumer, no plug defined
   216  	s.mockSnap(c, producerYaml)
   217  	s.mockSnap(c, consumerYaml)
   218  
   219  	action := &client.InterfaceAction{
   220  		Action: "connect",
   221  		Plugs:  []client.Plug{{Snap: "consumer", Name: "missingplug"}},
   222  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   223  	}
   224  	text, err := json.Marshal(action)
   225  	c.Assert(err, check.IsNil)
   226  	buf := bytes.NewBuffer(text)
   227  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   228  	c.Assert(err, check.IsNil)
   229  	rec := httptest.NewRecorder()
   230  	s.req(c, req, nil).ServeHTTP(rec, req)
   231  	c.Check(rec.Code, check.Equals, 400)
   232  
   233  	var body map[string]interface{}
   234  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   235  	c.Check(err, check.IsNil)
   236  	c.Check(body, check.DeepEquals, map[string]interface{}{
   237  		"result": map[string]interface{}{
   238  			"message": "snap \"consumer\" has no plug named \"missingplug\"",
   239  		},
   240  		"status":      "Bad Request",
   241  		"status-code": 400.0,
   242  		"type":        "error",
   243  	})
   244  
   245  	repo := d.Overlord().InterfaceManager().Repository()
   246  	ifaces := repo.Interfaces()
   247  	c.Assert(ifaces.Connections, check.HasLen, 0)
   248  }
   249  
   250  func (s *interfacesSuite) TestConnectAlreadyConnected(c *check.C) {
   251  	d := s.daemon(c)
   252  
   253  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   254  	// there is no consumer, no plug defined
   255  	s.mockSnap(c, producerYaml)
   256  	s.mockSnap(c, consumerYaml)
   257  
   258  	repo := d.Overlord().InterfaceManager().Repository()
   259  	connRef := &interfaces.ConnRef{
   260  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   261  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   262  	}
   263  
   264  	d.Overlord().Loop()
   265  	defer d.Overlord().Stop()
   266  
   267  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   268  	c.Assert(err, check.IsNil)
   269  	conns := map[string]interface{}{
   270  		"consumer:plug producer:slot": map[string]interface{}{
   271  			"auto": false,
   272  		},
   273  	}
   274  	st := d.Overlord().State()
   275  	st.Lock()
   276  	st.Set("conns", conns)
   277  	st.Unlock()
   278  
   279  	action := &client.InterfaceAction{
   280  		Action: "connect",
   281  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   282  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   283  	}
   284  	text, err := json.Marshal(action)
   285  	c.Assert(err, check.IsNil)
   286  	buf := bytes.NewBuffer(text)
   287  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   288  	c.Assert(err, check.IsNil)
   289  	rec := httptest.NewRecorder()
   290  	s.req(c, req, nil).ServeHTTP(rec, req)
   291  	c.Check(rec.Code, check.Equals, 202)
   292  	var body map[string]interface{}
   293  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   294  	c.Check(err, check.IsNil)
   295  	id := body["change"].(string)
   296  
   297  	st.Lock()
   298  	chg := st.Change(id)
   299  	c.Assert(chg.Tasks(), check.HasLen, 0)
   300  	c.Assert(chg.Status(), check.Equals, state.DoneStatus)
   301  	st.Unlock()
   302  }
   303  
   304  func (s *interfacesSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) {
   305  	d := s.daemon(c)
   306  
   307  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   308  	s.mockSnap(c, consumerYaml)
   309  	s.mockSnap(c, producerYaml)
   310  	// there is no producer, no slot defined
   311  
   312  	action := &client.InterfaceAction{
   313  		Action: "connect",
   314  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   315  		Slots:  []client.Slot{{Snap: "producer", Name: "missingslot"}},
   316  	}
   317  	text, err := json.Marshal(action)
   318  	c.Assert(err, check.IsNil)
   319  	buf := bytes.NewBuffer(text)
   320  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   321  	c.Assert(err, check.IsNil)
   322  	rec := httptest.NewRecorder()
   323  	s.req(c, req, nil).ServeHTTP(rec, req)
   324  	c.Check(rec.Code, check.Equals, 400)
   325  
   326  	var body map[string]interface{}
   327  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   328  	c.Check(err, check.IsNil)
   329  	c.Check(body, check.DeepEquals, map[string]interface{}{
   330  		"result": map[string]interface{}{
   331  			"message": "snap \"producer\" has no slot named \"missingslot\"",
   332  		},
   333  		"status":      "Bad Request",
   334  		"status-code": 400.0,
   335  		"type":        "error",
   336  	})
   337  
   338  	repo := d.Overlord().InterfaceManager().Repository()
   339  	ifaces := repo.Interfaces()
   340  	c.Assert(ifaces.Connections, check.HasLen, 0)
   341  }
   342  
   343  func (s *interfacesSuite) TestConnectPlugChangeConflict(c *check.C) {
   344  	d := s.daemon(c)
   345  
   346  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   347  	s.mockSnap(c, consumerYaml)
   348  	s.mockSnap(c, producerYaml)
   349  	// there is no producer, no slot defined
   350  
   351  	s.simulateConflict("consumer")
   352  
   353  	action := &client.InterfaceAction{
   354  		Action: "connect",
   355  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   356  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   357  	}
   358  	text, err := json.Marshal(action)
   359  	c.Assert(err, check.IsNil)
   360  	buf := bytes.NewBuffer(text)
   361  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   362  	c.Assert(err, check.IsNil)
   363  	rec := httptest.NewRecorder()
   364  	s.req(c, req, nil).ServeHTTP(rec, req)
   365  	c.Check(rec.Code, check.Equals, 409)
   366  
   367  	var body map[string]interface{}
   368  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   369  	c.Check(err, check.IsNil)
   370  	c.Check(body, check.DeepEquals, map[string]interface{}{
   371  		"status-code": 409.,
   372  		"status":      "Conflict",
   373  		"result": map[string]interface{}{
   374  			"message": `snap "consumer" has "manip" change in progress`,
   375  			"kind":    "snap-change-conflict",
   376  			"value": map[string]interface{}{
   377  				"change-kind": "manip",
   378  				"snap-name":   "consumer",
   379  			},
   380  		},
   381  		"type": "error"})
   382  }
   383  
   384  func (s *interfacesSuite) TestConnectCoreSystemAlias(c *check.C) {
   385  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   386  	defer revert()
   387  	d := s.daemon(c)
   388  
   389  	s.mockSnap(c, consumerYaml)
   390  	s.mockSnap(c, coreProducerYaml)
   391  
   392  	d.Overlord().Loop()
   393  	defer d.Overlord().Stop()
   394  
   395  	action := &client.InterfaceAction{
   396  		Action: "connect",
   397  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   398  		Slots:  []client.Slot{{Snap: "system", Name: "slot"}},
   399  	}
   400  	text, err := json.Marshal(action)
   401  	c.Assert(err, check.IsNil)
   402  	buf := bytes.NewBuffer(text)
   403  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   404  	c.Assert(err, check.IsNil)
   405  	rec := httptest.NewRecorder()
   406  	s.req(c, req, nil).ServeHTTP(rec, req)
   407  	c.Check(rec.Code, check.Equals, 202)
   408  	var body map[string]interface{}
   409  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   410  	c.Check(err, check.IsNil)
   411  	id := body["change"].(string)
   412  
   413  	st := d.Overlord().State()
   414  	st.Lock()
   415  	chg := st.Change(id)
   416  	st.Unlock()
   417  	c.Assert(chg, check.NotNil)
   418  
   419  	<-chg.Ready()
   420  
   421  	st.Lock()
   422  	err = chg.Err()
   423  	st.Unlock()
   424  	c.Assert(err, check.IsNil)
   425  
   426  	repo := d.Overlord().InterfaceManager().Repository()
   427  	ifaces := repo.Interfaces()
   428  	c.Assert(ifaces.Connections, check.HasLen, 1)
   429  	c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
   430  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   431  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"}}})
   432  }
   433  
   434  func (s *interfacesSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) {
   435  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   436  	defer restore()
   437  	// Install an inverse case mapper to exercise the interface mapping at the same time.
   438  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
   439  	defer restore()
   440  	d := s.daemon(c)
   441  
   442  	s.mockSnap(c, consumerYaml)
   443  	s.mockSnap(c, producerYaml)
   444  
   445  	repo := d.Overlord().InterfaceManager().Repository()
   446  	connRef := &interfaces.ConnRef{
   447  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   448  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   449  	}
   450  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   451  	c.Assert(err, check.IsNil)
   452  
   453  	st := d.Overlord().State()
   454  	st.Lock()
   455  	st.Set("conns", map[string]interface{}{
   456  		"consumer:plug producer:slot": map[string]interface{}{
   457  			"interface": "test",
   458  		},
   459  	})
   460  	st.Unlock()
   461  
   462  	d.Overlord().Loop()
   463  	defer d.Overlord().Stop()
   464  
   465  	action := &client.InterfaceAction{
   466  		Action: "disconnect",
   467  		Plugs:  []client.Plug{{Snap: plugSnap, Name: plugName}},
   468  		Slots:  []client.Slot{{Snap: slotSnap, Name: slotName}},
   469  	}
   470  	text, err := json.Marshal(action)
   471  	c.Assert(err, check.IsNil)
   472  	buf := bytes.NewBuffer(text)
   473  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   474  	c.Assert(err, check.IsNil)
   475  	rec := httptest.NewRecorder()
   476  	s.req(c, req, nil).ServeHTTP(rec, req)
   477  	c.Check(rec.Code, check.Equals, 202)
   478  	var body map[string]interface{}
   479  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   480  	c.Check(err, check.IsNil)
   481  	id := body["change"].(string)
   482  
   483  	st.Lock()
   484  	chg := st.Change(id)
   485  	st.Unlock()
   486  	c.Assert(chg, check.NotNil)
   487  
   488  	<-chg.Ready()
   489  
   490  	st.Lock()
   491  	err = chg.Err()
   492  	st.Unlock()
   493  	c.Assert(err, check.IsNil)
   494  
   495  	ifaces := repo.Interfaces()
   496  	c.Assert(ifaces.Connections, check.HasLen, 0)
   497  }
   498  
   499  func (s *interfacesSuite) TestDisconnectPlugSuccess(c *check.C) {
   500  	s.testDisconnect(c, "CONSUMER", "plug", "PRODUCER", "slot")
   501  }
   502  
   503  func (s *interfacesSuite) TestDisconnectPlugSuccessWithEmptyPlug(c *check.C) {
   504  	s.testDisconnect(c, "", "", "PRODUCER", "slot")
   505  }
   506  
   507  func (s *interfacesSuite) TestDisconnectPlugSuccessWithEmptySlot(c *check.C) {
   508  	s.testDisconnect(c, "CONSUMER", "plug", "", "")
   509  }
   510  
   511  func (s *interfacesSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
   512  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   513  	defer revert()
   514  	s.daemon(c)
   515  
   516  	// there is no consumer, no plug defined
   517  	s.mockSnap(c, producerYaml)
   518  
   519  	action := &client.InterfaceAction{
   520  		Action: "disconnect",
   521  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   522  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   523  	}
   524  	text, err := json.Marshal(action)
   525  	c.Assert(err, check.IsNil)
   526  	buf := bytes.NewBuffer(text)
   527  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   528  	c.Assert(err, check.IsNil)
   529  	rec := httptest.NewRecorder()
   530  	s.req(c, req, nil).ServeHTTP(rec, req)
   531  	c.Check(rec.Code, check.Equals, 400)
   532  	var body map[string]interface{}
   533  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   534  	c.Check(err, check.IsNil)
   535  	c.Check(body, check.DeepEquals, map[string]interface{}{
   536  		"result": map[string]interface{}{
   537  			"message": "snap \"consumer\" has no plug named \"plug\"",
   538  		},
   539  		"status":      "Bad Request",
   540  		"status-code": 400.0,
   541  		"type":        "error",
   542  	})
   543  }
   544  
   545  func (s *interfacesSuite) TestDisconnectPlugNothingToDo(c *check.C) {
   546  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   547  	defer revert()
   548  	s.daemon(c)
   549  
   550  	s.mockSnap(c, consumerYaml)
   551  	s.mockSnap(c, producerYaml)
   552  
   553  	action := &client.InterfaceAction{
   554  		Action: "disconnect",
   555  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   556  		Slots:  []client.Slot{{Snap: "", Name: ""}},
   557  	}
   558  	text, err := json.Marshal(action)
   559  	c.Assert(err, check.IsNil)
   560  	buf := bytes.NewBuffer(text)
   561  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   562  	c.Assert(err, check.IsNil)
   563  	rec := httptest.NewRecorder()
   564  	s.req(c, req, nil).ServeHTTP(rec, req)
   565  	c.Check(rec.Code, check.Equals, 400)
   566  	var body map[string]interface{}
   567  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   568  	c.Check(err, check.IsNil)
   569  	c.Check(body, check.DeepEquals, map[string]interface{}{
   570  		"result": map[string]interface{}{
   571  			"message": "nothing to do",
   572  			"kind":    "interfaces-unchanged",
   573  		},
   574  		"status":      "Bad Request",
   575  		"status-code": 400.0,
   576  		"type":        "error",
   577  	})
   578  }
   579  
   580  func (s *interfacesSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
   581  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   582  	defer revert()
   583  	s.daemon(c)
   584  
   585  	s.mockSnap(c, consumerYaml)
   586  	// there is no producer, no slot defined
   587  
   588  	action := &client.InterfaceAction{
   589  		Action: "disconnect",
   590  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   591  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   592  	}
   593  	text, err := json.Marshal(action)
   594  	c.Assert(err, check.IsNil)
   595  	buf := bytes.NewBuffer(text)
   596  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   597  	c.Assert(err, check.IsNil)
   598  	rec := httptest.NewRecorder()
   599  	s.req(c, req, nil).ServeHTTP(rec, req)
   600  
   601  	c.Check(rec.Code, check.Equals, 400)
   602  	var body map[string]interface{}
   603  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   604  	c.Check(err, check.IsNil)
   605  	c.Check(body, check.DeepEquals, map[string]interface{}{
   606  		"result": map[string]interface{}{
   607  			"message": "snap \"producer\" has no slot named \"slot\"",
   608  		},
   609  		"status":      "Bad Request",
   610  		"status-code": 400.0,
   611  		"type":        "error",
   612  	})
   613  }
   614  
   615  func (s *interfacesSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
   616  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   617  	defer revert()
   618  	s.daemon(c)
   619  
   620  	s.mockSnap(c, consumerYaml)
   621  	s.mockSnap(c, producerYaml)
   622  
   623  	action := &client.InterfaceAction{
   624  		Action: "disconnect",
   625  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   626  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   627  	}
   628  	text, err := json.Marshal(action)
   629  	c.Assert(err, check.IsNil)
   630  	buf := bytes.NewBuffer(text)
   631  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   632  	c.Assert(err, check.IsNil)
   633  	rec := httptest.NewRecorder()
   634  	s.req(c, req, nil).ServeHTTP(rec, req)
   635  
   636  	c.Check(rec.Code, check.Equals, 400)
   637  	var body map[string]interface{}
   638  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   639  	c.Check(err, check.IsNil)
   640  	c.Check(body, check.DeepEquals, map[string]interface{}{
   641  		"result": map[string]interface{}{
   642  			"message": "cannot disconnect consumer:plug from producer:slot, it is not connected",
   643  		},
   644  		"status":      "Bad Request",
   645  		"status-code": 400.0,
   646  		"type":        "error",
   647  	})
   648  }
   649  
   650  func (s *interfacesSuite) TestDisconnectForgetPlugFailureNotConnected(c *check.C) {
   651  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   652  	defer revert()
   653  	s.daemon(c)
   654  
   655  	s.mockSnap(c, consumerYaml)
   656  	s.mockSnap(c, producerYaml)
   657  
   658  	action := &client.InterfaceAction{
   659  		Action: "disconnect",
   660  		Forget: true,
   661  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   662  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   663  	}
   664  	text, err := json.Marshal(action)
   665  	c.Assert(err, check.IsNil)
   666  	buf := bytes.NewBuffer(text)
   667  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   668  	c.Assert(err, check.IsNil)
   669  	rec := httptest.NewRecorder()
   670  	s.req(c, req, nil).ServeHTTP(rec, req)
   671  
   672  	c.Check(rec.Code, check.Equals, 400)
   673  	var body map[string]interface{}
   674  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   675  	c.Check(err, check.IsNil)
   676  	c.Check(body, check.DeepEquals, map[string]interface{}{
   677  		"result": map[string]interface{}{
   678  			"message": "cannot forget connection consumer:plug from producer:slot, it was not connected",
   679  		},
   680  		"status":      "Bad Request",
   681  		"status-code": 400.0,
   682  		"type":        "error",
   683  	})
   684  }
   685  
   686  func (s *interfacesSuite) TestDisconnectConflict(c *check.C) {
   687  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   688  	defer revert()
   689  	d := s.daemon(c)
   690  
   691  	s.mockSnap(c, consumerYaml)
   692  	s.mockSnap(c, producerYaml)
   693  
   694  	repo := d.Overlord().InterfaceManager().Repository()
   695  	connRef := &interfaces.ConnRef{
   696  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   697  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   698  	}
   699  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   700  	c.Assert(err, check.IsNil)
   701  
   702  	st := d.Overlord().State()
   703  	st.Lock()
   704  	st.Set("conns", map[string]interface{}{
   705  		"consumer:plug producer:slot": map[string]interface{}{
   706  			"interface": "test",
   707  		},
   708  	})
   709  	st.Unlock()
   710  
   711  	s.simulateConflict("consumer")
   712  
   713  	action := &client.InterfaceAction{
   714  		Action: "disconnect",
   715  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   716  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   717  	}
   718  	text, err := json.Marshal(action)
   719  	c.Assert(err, check.IsNil)
   720  	buf := bytes.NewBuffer(text)
   721  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   722  	c.Assert(err, check.IsNil)
   723  	rec := httptest.NewRecorder()
   724  	s.req(c, req, nil).ServeHTTP(rec, req)
   725  
   726  	c.Check(rec.Code, check.Equals, 409)
   727  
   728  	var body map[string]interface{}
   729  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   730  	c.Check(err, check.IsNil)
   731  	c.Check(body, check.DeepEquals, map[string]interface{}{
   732  		"status-code": 409.,
   733  		"status":      "Conflict",
   734  		"result": map[string]interface{}{
   735  			"message": `snap "consumer" has "manip" change in progress`,
   736  			"kind":    "snap-change-conflict",
   737  			"value": map[string]interface{}{
   738  				"change-kind": "manip",
   739  				"snap-name":   "consumer",
   740  			},
   741  		},
   742  		"type": "error"})
   743  }
   744  
   745  func (s *interfacesSuite) TestDisconnectCoreSystemAlias(c *check.C) {
   746  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   747  	defer revert()
   748  	d := s.daemon(c)
   749  
   750  	s.mockSnap(c, consumerYaml)
   751  	s.mockSnap(c, coreProducerYaml)
   752  
   753  	repo := d.Overlord().InterfaceManager().Repository()
   754  	connRef := &interfaces.ConnRef{
   755  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   756  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"},
   757  	}
   758  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   759  	c.Assert(err, check.IsNil)
   760  
   761  	st := d.Overlord().State()
   762  	st.Lock()
   763  	st.Set("conns", map[string]interface{}{
   764  		"consumer:plug core:slot": map[string]interface{}{
   765  			"interface": "test",
   766  		},
   767  	})
   768  	st.Unlock()
   769  
   770  	d.Overlord().Loop()
   771  	defer d.Overlord().Stop()
   772  
   773  	action := &client.InterfaceAction{
   774  		Action: "disconnect",
   775  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   776  		Slots:  []client.Slot{{Snap: "system", Name: "slot"}},
   777  	}
   778  	text, err := json.Marshal(action)
   779  	c.Assert(err, check.IsNil)
   780  	buf := bytes.NewBuffer(text)
   781  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   782  	c.Assert(err, check.IsNil)
   783  	rec := httptest.NewRecorder()
   784  	s.req(c, req, nil).ServeHTTP(rec, req)
   785  	c.Check(rec.Code, check.Equals, 202)
   786  	var body map[string]interface{}
   787  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   788  	c.Check(err, check.IsNil)
   789  	id := body["change"].(string)
   790  
   791  	st.Lock()
   792  	chg := st.Change(id)
   793  	st.Unlock()
   794  	c.Assert(chg, check.NotNil)
   795  
   796  	<-chg.Ready()
   797  
   798  	st.Lock()
   799  	err = chg.Err()
   800  	st.Unlock()
   801  	c.Assert(err, check.IsNil)
   802  
   803  	ifaces := repo.Interfaces()
   804  	c.Assert(ifaces.Connections, check.HasLen, 0)
   805  }
   806  
   807  func (s *interfacesSuite) TestUnsupportedInterfaceRequest(c *check.C) {
   808  	s.daemon(c)
   809  	buf := bytes.NewBuffer([]byte(`garbage`))
   810  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   811  	c.Assert(err, check.IsNil)
   812  	rec := httptest.NewRecorder()
   813  	s.req(c, req, nil).ServeHTTP(rec, req)
   814  	c.Check(rec.Code, check.Equals, 400)
   815  	var body map[string]interface{}
   816  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   817  	c.Check(err, check.IsNil)
   818  	c.Check(body, check.DeepEquals, map[string]interface{}{
   819  		"result": map[string]interface{}{
   820  			"message": "cannot decode request body into an interface action: invalid character 'g' looking for beginning of value",
   821  		},
   822  		"status":      "Bad Request",
   823  		"status-code": 400.0,
   824  		"type":        "error",
   825  	})
   826  }
   827  
   828  func (s *interfacesSuite) TestMissingInterfaceAction(c *check.C) {
   829  	s.daemon(c)
   830  	action := &client.InterfaceAction{}
   831  	text, err := json.Marshal(action)
   832  	c.Assert(err, check.IsNil)
   833  	buf := bytes.NewBuffer(text)
   834  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   835  	c.Assert(err, check.IsNil)
   836  	rec := httptest.NewRecorder()
   837  	s.req(c, req, nil).ServeHTTP(rec, req)
   838  	c.Check(rec.Code, check.Equals, 400)
   839  	var body map[string]interface{}
   840  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   841  	c.Check(err, check.IsNil)
   842  	c.Check(body, check.DeepEquals, map[string]interface{}{
   843  		"result": map[string]interface{}{
   844  			"message": "interface action not specified",
   845  		},
   846  		"status":      "Bad Request",
   847  		"status-code": 400.0,
   848  		"type":        "error",
   849  	})
   850  }
   851  
   852  func (s *interfacesSuite) TestUnsupportedInterfaceAction(c *check.C) {
   853  	s.daemon(c)
   854  	action := &client.InterfaceAction{Action: "foo"}
   855  	text, err := json.Marshal(action)
   856  	c.Assert(err, check.IsNil)
   857  	buf := bytes.NewBuffer(text)
   858  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   859  	c.Assert(err, check.IsNil)
   860  	rec := httptest.NewRecorder()
   861  	s.req(c, req, nil).ServeHTTP(rec, req)
   862  	c.Check(rec.Code, check.Equals, 400)
   863  	var body map[string]interface{}
   864  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   865  	c.Check(err, check.IsNil)
   866  	c.Check(body, check.DeepEquals, map[string]interface{}{
   867  		"result": map[string]interface{}{
   868  			"message": "unsupported interface action: \"foo\"",
   869  		},
   870  		"status":      "Bad Request",
   871  		"status-code": 400.0,
   872  		"type":        "error",
   873  	})
   874  }
   875  
   876  // Tests for GET /v2/interfaces
   877  
   878  func (s *interfacesSuite) TestInterfacesLegacy(c *check.C) {
   879  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   880  	defer restore()
   881  	// Install an inverse case mapper to exercise the interface mapping at the same time.
   882  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
   883  	defer restore()
   884  
   885  	d := s.daemon(c)
   886  
   887  	var anotherConsumerYaml = `
   888  name: another-consumer-%s
   889  version: 1
   890  apps:
   891   app:
   892  plugs:
   893   plug:
   894    interface: test
   895    key: value
   896    label: label
   897  `
   898  	s.mockSnap(c, consumerYaml)
   899  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "def"))
   900  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "abc"))
   901  	s.mockSnap(c, producerYaml)
   902  
   903  	repo := d.Overlord().InterfaceManager().Repository()
   904  	connRef := &interfaces.ConnRef{
   905  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   906  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   907  	}
   908  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   909  	c.Assert(err, check.IsNil)
   910  
   911  	st := d.Overlord().State()
   912  	st.Lock()
   913  	st.Set("conns", map[string]interface{}{
   914  		"consumer:plug producer:slot": map[string]interface{}{
   915  			"interface": "test",
   916  			"auto":      true,
   917  		},
   918  		"another-consumer-def:plug producer:slot": map[string]interface{}{
   919  			"interface": "test",
   920  			"by-gadget": true,
   921  			"auto":      true,
   922  		},
   923  		"another-consumer-abc:plug producer:slot": map[string]interface{}{
   924  			"interface": "test",
   925  			"by-gadget": true,
   926  			"auto":      true,
   927  		},
   928  	})
   929  	st.Unlock()
   930  
   931  	req, err := http.NewRequest("GET", "/v2/interfaces", nil)
   932  	c.Assert(err, check.IsNil)
   933  	rec := httptest.NewRecorder()
   934  	s.req(c, req, nil).ServeHTTP(rec, req)
   935  	c.Check(rec.Code, check.Equals, 200)
   936  	var body map[string]interface{}
   937  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   938  	c.Check(err, check.IsNil)
   939  	c.Check(body, check.DeepEquals, map[string]interface{}{
   940  		"result": map[string]interface{}{
   941  			"plugs": []interface{}{
   942  				map[string]interface{}{
   943  					"snap":      "another-consumer-abc",
   944  					"plug":      "plug",
   945  					"interface": "test",
   946  					"attrs":     map[string]interface{}{"key": "value"},
   947  					"apps":      []interface{}{"app"},
   948  					"label":     "label",
   949  					"connections": []interface{}{
   950  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   951  					},
   952  				},
   953  				map[string]interface{}{
   954  					"snap":      "another-consumer-def",
   955  					"plug":      "plug",
   956  					"interface": "test",
   957  					"attrs":     map[string]interface{}{"key": "value"},
   958  					"apps":      []interface{}{"app"},
   959  					"label":     "label",
   960  					"connections": []interface{}{
   961  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   962  					},
   963  				},
   964  				map[string]interface{}{
   965  					"snap":      "consumer",
   966  					"plug":      "plug",
   967  					"interface": "test",
   968  					"attrs":     map[string]interface{}{"key": "value"},
   969  					"apps":      []interface{}{"app"},
   970  					"label":     "label",
   971  					"connections": []interface{}{
   972  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   973  					},
   974  				},
   975  			},
   976  			"slots": []interface{}{
   977  				map[string]interface{}{
   978  					"snap":      "producer",
   979  					"slot":      "slot",
   980  					"interface": "test",
   981  					"attrs":     map[string]interface{}{"key": "value"},
   982  					"apps":      []interface{}{"app"},
   983  					"label":     "label",
   984  					"connections": []interface{}{
   985  						map[string]interface{}{"snap": "another-consumer-abc", "plug": "plug"},
   986  						map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
   987  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
   988  					},
   989  				},
   990  			},
   991  		},
   992  		"status":      "OK",
   993  		"status-code": 200.0,
   994  		"type":        "sync",
   995  	})
   996  }
   997  
   998  func (s *interfacesSuite) TestInterfacesModern(c *check.C) {
   999  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  1000  	defer restore()
  1001  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  1002  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  1003  	defer restore()
  1004  
  1005  	d := s.daemon(c)
  1006  
  1007  	s.mockSnap(c, consumerYaml)
  1008  	s.mockSnap(c, producerYaml)
  1009  
  1010  	repo := d.Overlord().InterfaceManager().Repository()
  1011  	connRef := &interfaces.ConnRef{
  1012  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1013  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  1014  	}
  1015  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  1016  	c.Assert(err, check.IsNil)
  1017  
  1018  	req, err := http.NewRequest("GET", "/v2/interfaces?select=connected&doc=true&plugs=true&slots=true", nil)
  1019  	c.Assert(err, check.IsNil)
  1020  	rec := httptest.NewRecorder()
  1021  	s.req(c, req, nil).ServeHTTP(rec, req)
  1022  	c.Check(rec.Code, check.Equals, 200)
  1023  	var body map[string]interface{}
  1024  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  1025  	c.Check(err, check.IsNil)
  1026  	c.Check(body, check.DeepEquals, map[string]interface{}{
  1027  		"result": []interface{}{
  1028  			map[string]interface{}{
  1029  				"name": "test",
  1030  				"plugs": []interface{}{
  1031  					map[string]interface{}{
  1032  						"snap":  "consumer",
  1033  						"plug":  "plug",
  1034  						"label": "label",
  1035  						"attrs": map[string]interface{}{
  1036  							"key": "value",
  1037  						},
  1038  					}},
  1039  				"slots": []interface{}{
  1040  					map[string]interface{}{
  1041  						"snap":  "producer",
  1042  						"slot":  "slot",
  1043  						"label": "label",
  1044  						"attrs": map[string]interface{}{
  1045  							"key": "value",
  1046  						},
  1047  					},
  1048  				},
  1049  			},
  1050  		},
  1051  		"status":      "OK",
  1052  		"status-code": 200.0,
  1053  		"type":        "sync",
  1054  	})
  1055  }