mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
This commit replaces the use of "sanity" with more inclusive naming. When `sanity` is used in a more general sense either `validity` or `quick` is used.
1188 lines
34 KiB
Go
1188 lines
34 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2014-2021 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package daemon_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
|
|
"gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/client"
|
|
"github.com/snapcore/snapd/daemon"
|
|
"github.com/snapcore/snapd/interfaces"
|
|
"github.com/snapcore/snapd/interfaces/builtin"
|
|
"github.com/snapcore/snapd/interfaces/ifacetest"
|
|
"github.com/snapcore/snapd/overlord/ifacestate"
|
|
"github.com/snapcore/snapd/overlord/state"
|
|
)
|
|
|
|
var _ = check.Suite(&interfacesSuite{})
|
|
|
|
type interfacesSuite struct {
|
|
apiBaseSuite
|
|
}
|
|
|
|
func (s *interfacesSuite) SetUpTest(c *check.C) {
|
|
s.apiBaseSuite.SetUpTest(c)
|
|
|
|
s.expectWriteAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage-interfaces"})
|
|
}
|
|
|
|
func mockIface(c *check.C, d *daemon.Daemon, iface interfaces.Interface) {
|
|
err := d.Overlord().InterfaceManager().Repository().AddInterface(iface)
|
|
c.Assert(err, check.IsNil)
|
|
}
|
|
|
|
// inverseCaseMapper implements SnapMapper to use lower case internally and upper case externally.
|
|
type inverseCaseMapper struct {
|
|
ifacestate.IdentityMapper // Embed the identity mapper to reuse empty state mapping functions.
|
|
}
|
|
|
|
func (m *inverseCaseMapper) RemapSnapFromRequest(snapName string) string {
|
|
return strings.ToLower(snapName)
|
|
}
|
|
|
|
func (m *inverseCaseMapper) RemapSnapToResponse(snapName string) string {
|
|
return strings.ToUpper(snapName)
|
|
}
|
|
|
|
func (m *inverseCaseMapper) SystemSnapName() string {
|
|
return "core"
|
|
}
|
|
|
|
// Tests for POST /v2/interfaces
|
|
|
|
const (
|
|
consumerYaml = `
|
|
name: consumer
|
|
version: 1
|
|
apps:
|
|
app:
|
|
plugs:
|
|
plug:
|
|
interface: test
|
|
key: value
|
|
label: label
|
|
`
|
|
|
|
producerYaml = `
|
|
name: producer
|
|
version: 1
|
|
apps:
|
|
app:
|
|
slots:
|
|
slot:
|
|
interface: test
|
|
key: value
|
|
label: label
|
|
`
|
|
|
|
coreProducerYaml = `
|
|
name: core
|
|
version: 1
|
|
slots:
|
|
slot:
|
|
interface: test
|
|
key: value
|
|
label: label
|
|
`
|
|
|
|
differentProducerYaml = `
|
|
name: producer
|
|
version: 1
|
|
apps:
|
|
app:
|
|
slots:
|
|
slot:
|
|
interface: different
|
|
key: value
|
|
label: label
|
|
`
|
|
)
|
|
|
|
func (s *interfacesSuite) TestConnectPlugSuccess(c *check.C) {
|
|
restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer restore()
|
|
// Install an inverse case mapper to exercise the interface mapping at the same time.
|
|
restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
|
|
defer restore()
|
|
|
|
d := s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
d.Overlord().Loop()
|
|
defer d.Overlord().Stop()
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "CONSUMER", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "PRODUCER", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 202)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
id := body["change"].(string)
|
|
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
chg := st.Change(id)
|
|
st.Unlock()
|
|
c.Assert(chg, check.NotNil)
|
|
|
|
<-chg.Ready()
|
|
|
|
st.Lock()
|
|
err = chg.Err()
|
|
st.Unlock()
|
|
c.Assert(err, check.IsNil)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 1)
|
|
c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
|
|
}})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectPlugFailureInterfaceMismatch(c *check.C) {
|
|
d := s.daemon(c)
|
|
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "different"})
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, differentProducerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "cannot connect consumer:plug (\"test\" interface) to producer:slot (\"different\" interface)",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 0)
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectPlugFailureNoSuchPlug(c *check.C) {
|
|
d := s.daemon(c)
|
|
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
|
|
// there is no consumer, no plug defined
|
|
s.mockSnap(c, producerYaml)
|
|
s.mockSnap(c, consumerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "missingplug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"consumer\" has no plug named \"missingplug\"",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 0)
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectAlreadyConnected(c *check.C) {
|
|
d := s.daemon(c)
|
|
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
|
|
// there is no consumer, no plug defined
|
|
s.mockSnap(c, producerYaml)
|
|
s.mockSnap(c, consumerYaml)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
connRef := &interfaces.ConnRef{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
|
|
}
|
|
|
|
d.Overlord().Loop()
|
|
defer d.Overlord().Stop()
|
|
|
|
_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
|
|
c.Assert(err, check.IsNil)
|
|
conns := map[string]interface{}{
|
|
"consumer:plug producer:slot": map[string]interface{}{
|
|
"auto": false,
|
|
},
|
|
}
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
st.Set("conns", conns)
|
|
st.Unlock()
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 202)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
id := body["change"].(string)
|
|
|
|
st.Lock()
|
|
chg := st.Change(id)
|
|
c.Assert(chg.Tasks(), check.HasLen, 0)
|
|
c.Assert(chg.Status(), check.Equals, state.DoneStatus)
|
|
st.Unlock()
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) {
|
|
d := s.daemon(c)
|
|
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
// there is no producer, no slot defined
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "missingslot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"producer\" has no slot named \"missingslot\"",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 0)
|
|
}
|
|
|
|
func (s *interfacesSuite) testConnectFailureNoSnap(c *check.C, installedSnap string) {
|
|
// validity, either consumer or producer needs to be enabled
|
|
consumer := installedSnap == "consumer"
|
|
producer := installedSnap == "producer"
|
|
c.Assert(consumer || producer, check.Equals, true, check.Commentf("installed snap must be consumer or producer"))
|
|
|
|
d := s.daemon(c)
|
|
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
|
|
|
|
if consumer {
|
|
s.mockSnap(c, consumerYaml)
|
|
}
|
|
if producer {
|
|
s.mockSnap(c, producerYaml)
|
|
}
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
if producer {
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"consumer\" is not installed",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
} else {
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"producer\" is not installed",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectPlugFailureNoPlugSnap(c *check.C) {
|
|
s.testConnectFailureNoSnap(c, "producer")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectPlugFailureNoSlotSnap(c *check.C) {
|
|
s.testConnectFailureNoSnap(c, "consumer")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectPlugChangeConflict(c *check.C) {
|
|
d := s.daemon(c)
|
|
|
|
mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
// there is no producer, no slot defined
|
|
|
|
s.simulateConflict("consumer")
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 409)
|
|
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"status-code": 409.,
|
|
"status": "Conflict",
|
|
"result": map[string]interface{}{
|
|
"message": `snap "consumer" has "manip" change in progress`,
|
|
"kind": "snap-change-conflict",
|
|
"value": map[string]interface{}{
|
|
"change-kind": "manip",
|
|
"snap-name": "consumer",
|
|
},
|
|
},
|
|
"type": "error"})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestConnectCoreSystemAlias(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
d := s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, coreProducerYaml)
|
|
|
|
d.Overlord().Loop()
|
|
defer d.Overlord().Stop()
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "connect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "system", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 202)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
id := body["change"].(string)
|
|
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
chg := st.Change(id)
|
|
st.Unlock()
|
|
c.Assert(chg, check.NotNil)
|
|
|
|
<-chg.Ready()
|
|
|
|
st.Lock()
|
|
err = chg.Err()
|
|
st.Unlock()
|
|
c.Assert(err, check.IsNil)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 1)
|
|
c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"}}})
|
|
}
|
|
|
|
func (s *interfacesSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) {
|
|
restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer restore()
|
|
// Install an inverse case mapper to exercise the interface mapping at the same time.
|
|
restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
|
|
defer restore()
|
|
d := s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
connRef := &interfaces.ConnRef{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
|
|
}
|
|
_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
st.Set("conns", map[string]interface{}{
|
|
"consumer:plug producer:slot": map[string]interface{}{
|
|
"interface": "test",
|
|
},
|
|
})
|
|
st.Unlock()
|
|
|
|
d.Overlord().Loop()
|
|
defer d.Overlord().Stop()
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: plugSnap, Name: plugName}},
|
|
Slots: []client.Slot{{Snap: slotSnap, Name: slotName}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 202)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
id := body["change"].(string)
|
|
|
|
st.Lock()
|
|
chg := st.Change(id)
|
|
st.Unlock()
|
|
c.Assert(chg, check.NotNil)
|
|
|
|
<-chg.Ready()
|
|
|
|
st.Lock()
|
|
err = chg.Err()
|
|
st.Unlock()
|
|
c.Assert(err, check.IsNil)
|
|
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 0)
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugSuccess(c *check.C) {
|
|
s.testDisconnect(c, "CONSUMER", "plug", "PRODUCER", "slot")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugSuccessWithEmptyPlug(c *check.C) {
|
|
s.testDisconnect(c, "", "", "PRODUCER", "slot")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugSuccessWithEmptySlot(c *check.C) {
|
|
s.testDisconnect(c, "CONSUMER", "plug", "", "")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "missingplug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"consumer\" has no plug named \"missingplug\"",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) testDisconnectFailureNoSnap(c *check.C, installedSnap string) {
|
|
// validity, either consumer or producer needs to be enabled
|
|
consumer := installedSnap == "consumer"
|
|
producer := installedSnap == "producer"
|
|
c.Assert(consumer || producer, check.Equals, true, check.Commentf("installed snap must be consumer or producer"))
|
|
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
s.daemon(c)
|
|
|
|
if consumer {
|
|
s.mockSnap(c, consumerYaml)
|
|
}
|
|
if producer {
|
|
s.mockSnap(c, producerYaml)
|
|
}
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
|
|
if producer {
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"consumer\" is not installed",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
} else {
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"producer\" is not installed",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugFailureNoPlugSnap(c *check.C) {
|
|
s.testDisconnectFailureNoSnap(c, "producer")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugFailureNoSlotSnap(c *check.C) {
|
|
s.testDisconnectFailureNoSnap(c, "consumer")
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugNothingToDo(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "", Name: ""}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "nothing to do",
|
|
"kind": "interfaces-unchanged",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "missingslot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "snap \"producer\" has no slot named \"missingslot\"",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "cannot disconnect consumer:plug from producer:slot, it is not connected",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectForgetPlugFailureNotConnected(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Forget: true,
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "cannot forget connection consumer:plug from producer:slot, it was not connected",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectConflict(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
d := s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
connRef := &interfaces.ConnRef{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
|
|
}
|
|
_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
st.Set("conns", map[string]interface{}{
|
|
"consumer:plug producer:slot": map[string]interface{}{
|
|
"interface": "test",
|
|
},
|
|
})
|
|
st.Unlock()
|
|
|
|
s.simulateConflict("consumer")
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "producer", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
|
|
c.Check(rec.Code, check.Equals, 409)
|
|
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"status-code": 409.,
|
|
"status": "Conflict",
|
|
"result": map[string]interface{}{
|
|
"message": `snap "consumer" has "manip" change in progress`,
|
|
"kind": "snap-change-conflict",
|
|
"value": map[string]interface{}{
|
|
"change-kind": "manip",
|
|
"snap-name": "consumer",
|
|
},
|
|
},
|
|
"type": "error"})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestDisconnectCoreSystemAlias(c *check.C) {
|
|
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer revert()
|
|
d := s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, coreProducerYaml)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
connRef := &interfaces.ConnRef{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"},
|
|
}
|
|
_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
st.Set("conns", map[string]interface{}{
|
|
"consumer:plug core:slot": map[string]interface{}{
|
|
"interface": "test",
|
|
},
|
|
})
|
|
st.Unlock()
|
|
|
|
d.Overlord().Loop()
|
|
defer d.Overlord().Stop()
|
|
|
|
action := &client.InterfaceAction{
|
|
Action: "disconnect",
|
|
Plugs: []client.Plug{{Snap: "consumer", Name: "plug"}},
|
|
Slots: []client.Slot{{Snap: "system", Name: "slot"}},
|
|
}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 202)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
id := body["change"].(string)
|
|
|
|
st.Lock()
|
|
chg := st.Change(id)
|
|
st.Unlock()
|
|
c.Assert(chg, check.NotNil)
|
|
|
|
<-chg.Ready()
|
|
|
|
st.Lock()
|
|
err = chg.Err()
|
|
st.Unlock()
|
|
c.Assert(err, check.IsNil)
|
|
|
|
ifaces := repo.Interfaces()
|
|
c.Assert(ifaces.Connections, check.HasLen, 0)
|
|
}
|
|
|
|
func (s *interfacesSuite) TestUnsupportedInterfaceRequest(c *check.C) {
|
|
s.daemon(c)
|
|
buf := bytes.NewBuffer([]byte(`garbage`))
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "cannot decode request body into an interface action: invalid character 'g' looking for beginning of value",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestMissingInterfaceAction(c *check.C) {
|
|
s.daemon(c)
|
|
action := &client.InterfaceAction{}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "interface action not specified",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestUnsupportedInterfaceAction(c *check.C) {
|
|
s.daemon(c)
|
|
action := &client.InterfaceAction{Action: "foo"}
|
|
text, err := json.Marshal(action)
|
|
c.Assert(err, check.IsNil)
|
|
buf := bytes.NewBuffer(text)
|
|
req, err := http.NewRequest("POST", "/v2/interfaces", buf)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 400)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"message": "unsupported interface action: \"foo\"",
|
|
},
|
|
"status": "Bad Request",
|
|
"status-code": 400.0,
|
|
"type": "error",
|
|
})
|
|
}
|
|
|
|
// Tests for GET /v2/interfaces
|
|
|
|
func (s *interfacesSuite) TestInterfacesLegacy(c *check.C) {
|
|
restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer restore()
|
|
// Install an inverse case mapper to exercise the interface mapping at the same time.
|
|
restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
|
|
defer restore()
|
|
|
|
d := s.daemon(c)
|
|
|
|
var anotherConsumerYaml = `
|
|
name: another-consumer-%s
|
|
version: 1
|
|
apps:
|
|
app:
|
|
plugs:
|
|
plug:
|
|
interface: test
|
|
key: value
|
|
label: label
|
|
`
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "def"))
|
|
s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "abc"))
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
connRef := &interfaces.ConnRef{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
|
|
}
|
|
_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
st := d.Overlord().State()
|
|
st.Lock()
|
|
st.Set("conns", map[string]interface{}{
|
|
"consumer:plug producer:slot": map[string]interface{}{
|
|
"interface": "test",
|
|
"auto": true,
|
|
},
|
|
"another-consumer-def:plug producer:slot": map[string]interface{}{
|
|
"interface": "test",
|
|
"by-gadget": true,
|
|
"auto": true,
|
|
},
|
|
"another-consumer-abc:plug producer:slot": map[string]interface{}{
|
|
"interface": "test",
|
|
"by-gadget": true,
|
|
"auto": true,
|
|
},
|
|
})
|
|
st.Unlock()
|
|
|
|
req, err := http.NewRequest("GET", "/v2/interfaces", nil)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 200)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"plugs": []interface{}{
|
|
map[string]interface{}{
|
|
"snap": "another-consumer-abc",
|
|
"plug": "plug",
|
|
"interface": "test",
|
|
"attrs": map[string]interface{}{"key": "value"},
|
|
"apps": []interface{}{"app"},
|
|
"label": "label",
|
|
"connections": []interface{}{
|
|
map[string]interface{}{"snap": "producer", "slot": "slot"},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"snap": "another-consumer-def",
|
|
"plug": "plug",
|
|
"interface": "test",
|
|
"attrs": map[string]interface{}{"key": "value"},
|
|
"apps": []interface{}{"app"},
|
|
"label": "label",
|
|
"connections": []interface{}{
|
|
map[string]interface{}{"snap": "producer", "slot": "slot"},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"snap": "consumer",
|
|
"plug": "plug",
|
|
"interface": "test",
|
|
"attrs": map[string]interface{}{"key": "value"},
|
|
"apps": []interface{}{"app"},
|
|
"label": "label",
|
|
"connections": []interface{}{
|
|
map[string]interface{}{"snap": "producer", "slot": "slot"},
|
|
},
|
|
},
|
|
},
|
|
"slots": []interface{}{
|
|
map[string]interface{}{
|
|
"snap": "producer",
|
|
"slot": "slot",
|
|
"interface": "test",
|
|
"attrs": map[string]interface{}{"key": "value"},
|
|
"apps": []interface{}{"app"},
|
|
"label": "label",
|
|
"connections": []interface{}{
|
|
map[string]interface{}{"snap": "another-consumer-abc", "plug": "plug"},
|
|
map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
|
|
map[string]interface{}{"snap": "consumer", "plug": "plug"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"status": "OK",
|
|
"status-code": 200.0,
|
|
"type": "sync",
|
|
})
|
|
}
|
|
|
|
func (s *interfacesSuite) TestInterfacesModern(c *check.C) {
|
|
restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
|
|
defer restore()
|
|
// Install an inverse case mapper to exercise the interface mapping at the same time.
|
|
restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
|
|
defer restore()
|
|
|
|
d := s.daemon(c)
|
|
|
|
s.mockSnap(c, consumerYaml)
|
|
s.mockSnap(c, producerYaml)
|
|
|
|
repo := d.Overlord().InterfaceManager().Repository()
|
|
connRef := &interfaces.ConnRef{
|
|
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
|
|
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
|
|
}
|
|
_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
req, err := http.NewRequest("GET", "/v2/interfaces?select=connected&doc=true&plugs=true&slots=true", nil)
|
|
c.Assert(err, check.IsNil)
|
|
rec := httptest.NewRecorder()
|
|
s.req(c, req, nil).ServeHTTP(rec, req)
|
|
c.Check(rec.Code, check.Equals, 200)
|
|
var body map[string]interface{}
|
|
err = json.Unmarshal(rec.Body.Bytes(), &body)
|
|
c.Check(err, check.IsNil)
|
|
c.Check(body, check.DeepEquals, map[string]interface{}{
|
|
"result": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "test",
|
|
"plugs": []interface{}{
|
|
map[string]interface{}{
|
|
"snap": "consumer",
|
|
"plug": "plug",
|
|
"label": "label",
|
|
"attrs": map[string]interface{}{
|
|
"key": "value",
|
|
},
|
|
}},
|
|
"slots": []interface{}{
|
|
map[string]interface{}{
|
|
"snap": "producer",
|
|
"slot": "slot",
|
|
"label": "label",
|
|
"attrs": map[string]interface{}{
|
|
"key": "value",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"status": "OK",
|
|
"status-code": 200.0,
|
|
"type": "sync",
|
|
})
|
|
}
|