github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/daemon/api_connections.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 daemon
    21  
    22  import (
    23  	"net/http"
    24  	"sort"
    25  
    26  	"github.com/snapcore/snapd/interfaces"
    27  	"github.com/snapcore/snapd/overlord/auth"
    28  	"github.com/snapcore/snapd/overlord/ifacestate"
    29  	"github.com/snapcore/snapd/overlord/snapstate"
    30  	"github.com/snapcore/snapd/overlord/state"
    31  )
    32  
    33  var connectionsCmd = &Command{
    34  	Path:       "/v2/connections",
    35  	GET:        getConnections,
    36  	ReadAccess: openAccess{},
    37  }
    38  
    39  type collectFilter struct {
    40  	snapName  string
    41  	ifaceName string
    42  	connected bool
    43  }
    44  
    45  func (c *collectFilter) plugOrConnectedSlotMatches(plug *interfaces.PlugRef, connectedSlots []interfaces.SlotRef) bool {
    46  	for _, slot := range connectedSlots {
    47  		if c.slotOrConnectedPlugMatches(&slot, nil) {
    48  			return true
    49  		}
    50  	}
    51  	if c.snapName != "" && plug.Snap != c.snapName {
    52  		return false
    53  	}
    54  	return true
    55  }
    56  
    57  func (c *collectFilter) slotOrConnectedPlugMatches(slot *interfaces.SlotRef, connectedPlugs []interfaces.PlugRef) bool {
    58  	for _, plug := range connectedPlugs {
    59  		if c.plugOrConnectedSlotMatches(&plug, nil) {
    60  			return true
    61  		}
    62  	}
    63  	if c.snapName != "" && slot.Snap != c.snapName {
    64  		return false
    65  	}
    66  	return true
    67  }
    68  
    69  func (c *collectFilter) ifaceMatches(ifaceName string) bool {
    70  	if c.ifaceName != "" && c.ifaceName != ifaceName {
    71  		return false
    72  	}
    73  	return true
    74  }
    75  
    76  type bySlotRef []interfaces.SlotRef
    77  
    78  func (b bySlotRef) Len() int      { return len(b) }
    79  func (b bySlotRef) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
    80  func (b bySlotRef) Less(i, j int) bool {
    81  	return b[i].SortsBefore(b[j])
    82  }
    83  
    84  type byPlugRef []interfaces.PlugRef
    85  
    86  func (b byPlugRef) Len() int      { return len(b) }
    87  func (b byPlugRef) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
    88  func (b byPlugRef) Less(i, j int) bool {
    89  	return b[i].SortsBefore(b[j])
    90  }
    91  
    92  // mergeAttrs merges attributes from 2 disjoint sets of static and dynamic slot or
    93  // plug attributes into a single map.
    94  func mergeAttrs(one map[string]interface{}, other map[string]interface{}) map[string]interface{} {
    95  	merged := make(map[string]interface{}, len(one)+len(other))
    96  	for k, v := range one {
    97  		merged[k] = v
    98  	}
    99  	for k, v := range other {
   100  		merged[k] = v
   101  	}
   102  	return merged
   103  }
   104  
   105  func collectConnections(ifaceMgr *ifacestate.InterfaceManager, filter collectFilter) (*connectionsJSON, error) {
   106  	repo := ifaceMgr.Repository()
   107  	ifaces := repo.Interfaces()
   108  
   109  	var connsjson connectionsJSON
   110  	var connStates map[string]ifacestate.ConnectionState
   111  	plugConns := map[string][]interfaces.SlotRef{}
   112  	slotConns := map[string][]interfaces.PlugRef{}
   113  
   114  	var err error
   115  	connStates, err = ifaceMgr.ConnectionStates()
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	connsjson.Established = make([]connectionJSON, 0, len(connStates))
   121  	connsjson.Plugs = make([]*plugJSON, 0, len(ifaces.Plugs))
   122  	connsjson.Slots = make([]*slotJSON, 0, len(ifaces.Slots))
   123  
   124  	for crefStr, cstate := range connStates {
   125  		if cstate.Undesired && filter.connected {
   126  			continue
   127  		}
   128  		if cstate.HotplugGone {
   129  			// XXX: hotplug connection - the device and slot are gone
   130  			continue
   131  		}
   132  
   133  		cref, err := interfaces.ParseConnRef(crefStr)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  
   138  		// plug or slot not in the repository, e.g. cref is referring to an
   139  		// inactive revision of the snap; this can happen when the new revision
   140  		// doesn't have given plug/slot anymore (but the connection state is
   141  		// kept in case of revert).
   142  		// XXX: if we decide to show such connections with special tags, then
   143  		// this needs to be tweaked together with collectFilter definition and
   144  		// connectionJSON output.
   145  		if repo.Plug(cref.PlugRef.Snap, cref.PlugRef.Name) == nil || repo.Slot(cref.SlotRef.Snap, cref.SlotRef.Name) == nil {
   146  			continue
   147  		}
   148  
   149  		if !filter.plugOrConnectedSlotMatches(&cref.PlugRef, nil) && !filter.slotOrConnectedPlugMatches(&cref.SlotRef, nil) {
   150  			continue
   151  		}
   152  		if !filter.ifaceMatches(cstate.Interface) {
   153  			continue
   154  		}
   155  		plugRef := interfaces.PlugRef{Snap: cref.PlugRef.Snap, Name: cref.PlugRef.Name}
   156  		slotRef := interfaces.SlotRef{Snap: cref.SlotRef.Snap, Name: cref.SlotRef.Name}
   157  		plugID := plugRef.String()
   158  		slotID := slotRef.String()
   159  
   160  		cj := connectionJSON{
   161  			Slot:      slotRef,
   162  			Plug:      plugRef,
   163  			Manual:    !cstate.Auto,
   164  			Gadget:    cstate.ByGadget,
   165  			Interface: cstate.Interface,
   166  			PlugAttrs: mergeAttrs(cstate.StaticPlugAttrs, cstate.DynamicPlugAttrs),
   167  			SlotAttrs: mergeAttrs(cstate.StaticSlotAttrs, cstate.DynamicSlotAttrs),
   168  		}
   169  		if cstate.Undesired {
   170  			// explicitly disconnected are always manual
   171  			cj.Manual = true
   172  			connsjson.Undesired = append(connsjson.Undesired, cj)
   173  		} else {
   174  			plugConns[plugID] = append(plugConns[plugID], slotRef)
   175  			slotConns[slotID] = append(slotConns[slotID], plugRef)
   176  
   177  			connsjson.Established = append(connsjson.Established, cj)
   178  		}
   179  	}
   180  
   181  	for _, plug := range ifaces.Plugs {
   182  		plugRef := interfaces.PlugRef{Snap: plug.Snap.InstanceName(), Name: plug.Name}
   183  		connectedSlots, connected := plugConns[plugRef.String()]
   184  		if !connected && filter.connected {
   185  			continue
   186  		}
   187  		if !filter.ifaceMatches(plug.Interface) || !filter.plugOrConnectedSlotMatches(&plugRef, connectedSlots) {
   188  			continue
   189  		}
   190  		var apps []string
   191  		for _, app := range plug.Apps {
   192  			apps = append(apps, app.Name)
   193  		}
   194  		sort.Sort(bySlotRef(connectedSlots))
   195  		pj := &plugJSON{
   196  			Snap:        plugRef.Snap,
   197  			Name:        plugRef.Name,
   198  			Interface:   plug.Interface,
   199  			Attrs:       plug.Attrs,
   200  			Apps:        apps,
   201  			Label:       plug.Label,
   202  			Connections: connectedSlots,
   203  		}
   204  		connsjson.Plugs = append(connsjson.Plugs, pj)
   205  	}
   206  	for _, slot := range ifaces.Slots {
   207  		slotRef := interfaces.SlotRef{Snap: slot.Snap.InstanceName(), Name: slot.Name}
   208  		connectedPlugs, connected := slotConns[slotRef.String()]
   209  		if !connected && filter.connected {
   210  			continue
   211  		}
   212  		if !filter.ifaceMatches(slot.Interface) || !filter.slotOrConnectedPlugMatches(&slotRef, connectedPlugs) {
   213  			continue
   214  		}
   215  		var apps []string
   216  		for _, app := range slot.Apps {
   217  			apps = append(apps, app.Name)
   218  		}
   219  		sort.Sort(byPlugRef(connectedPlugs))
   220  		sj := &slotJSON{
   221  			Snap:        slotRef.Snap,
   222  			Name:        slotRef.Name,
   223  			Interface:   slot.Interface,
   224  			Attrs:       slot.Attrs,
   225  			Apps:        apps,
   226  			Label:       slot.Label,
   227  			Connections: connectedPlugs,
   228  		}
   229  		connsjson.Slots = append(connsjson.Slots, sj)
   230  	}
   231  	return &connsjson, nil
   232  }
   233  
   234  type byCrefConnJSON []connectionJSON
   235  
   236  func (b byCrefConnJSON) Len() int      { return len(b) }
   237  func (b byCrefConnJSON) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   238  func (b byCrefConnJSON) Less(i, j int) bool {
   239  	icj := b[i]
   240  	jcj := b[j]
   241  	iCref := interfaces.ConnRef{PlugRef: icj.Plug, SlotRef: icj.Slot}
   242  	jCref := interfaces.ConnRef{PlugRef: jcj.Plug, SlotRef: jcj.Slot}
   243  	sortsBefore := iCref.SortsBefore(&jCref)
   244  	return sortsBefore
   245  }
   246  
   247  func checkSnapInstalled(st *state.State, name string) error {
   248  	st.Lock()
   249  	defer st.Unlock()
   250  
   251  	var snapst snapstate.SnapState
   252  	return snapstate.Get(st, name, &snapst)
   253  }
   254  
   255  func getConnections(c *Command, r *http.Request, user *auth.UserState) Response {
   256  	query := r.URL.Query()
   257  	snapName := query.Get("snap")
   258  	ifaceName := query.Get("interface")
   259  	qselect := query.Get("select")
   260  	if qselect != "all" && qselect != "" {
   261  		return BadRequest("unsupported select qualifier")
   262  	}
   263  	onlyConnected := qselect == ""
   264  
   265  	snapName = ifacestate.RemapSnapFromRequest(snapName)
   266  	if snapName != "" {
   267  		if err := checkSnapInstalled(c.d.overlord.State(), snapName); err != nil {
   268  			if err == state.ErrNoState {
   269  				return SnapNotFound(snapName, err)
   270  			}
   271  			return InternalError("cannot access snap state: %v", err)
   272  		}
   273  	}
   274  
   275  	connsjson, err := collectConnections(c.d.overlord.InterfaceManager(), collectFilter{
   276  		snapName:  snapName,
   277  		ifaceName: ifaceName,
   278  		connected: onlyConnected,
   279  	})
   280  	if err != nil {
   281  		return InternalError("collecting connection information failed: %v", err)
   282  	}
   283  	sort.Sort(byCrefConnJSON(connsjson.Established))
   284  	sort.Sort(byCrefConnJSON(connsjson.Undesired))
   285  
   286  	return SyncResponse(connsjson)
   287  }