Skip to content

Commit

Permalink
feat(api): Add /networking/status endpoint to get all interface info
Browse files Browse the repository at this point in the history
Closes #2445
  • Loading branch information
mcous committed Oct 12, 2018
1 parent 241a8a3 commit cc2d8d4
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -276,32 +276,65 @@ async def configure(request: web.Request) -> web.Response:

async def status(request: web.Request) -> web.Response:
"""
Get request will return the status of the wifi connection from the
RaspberryPi to the internet.
Get request will return the status of the machine's connection to the
internet as well as the status of its network interfaces.
The body of the response is a json dict containing
'status': connectivity status, where the options are:
'status': internet connectivity status, where the options are:
"none" - no connection to router or network
"portal" - device behind a captive portal and cannot reach full internet
"limited" - connection to router but not internet
"full" - connection to router and internet
"unknown" - an exception occured while trying to determine status
'ipAddress': the ip address, if it exists (null otherwise); this also
contains the subnet mask in CIDR notation, e.g. 10.2.12.120/16
'macAddress': the mac address
'gatewayAddress': the address of the current gateway, if it exists (null
otherwise)
'interfaces': JSON object of networking interfaces, keyed by device name,
where the value of each entry is another object with the keys:
- 'type': "ethernet" or "wifi"
- 'state': state string, e.g. "disconnected", "connecting", "connected"
- 'ipAddress': the ip address, if it exists (null otherwise); this also
contains the subnet mask in CIDR notation, e.g. 10.2.12.120/16
- 'macAddress': the MAC address of the interface device
- 'gatewayAddress': the address of the current gateway, if it exists
(null otherwise)
Example request:
```
GET /networking/status
```
Example response:
```
200 OK
{
"status": "full",
"interfaces": {
"wlan0": {
"ipAddress": "192.168.43.97/24",
"macAddress": "B8:27:EB:6C:95:CF",
"gatewayAddress": "192.168.43.161",
"state": "connected",
"type": "wifi"
},
"eth0": {
"ipAddress": "169.254.229.173/16",
"macAddress": "B8:27:EB:39:C0:9A",
"gatewayAddress": null,
"state": "connected",
"type": "ethernet"
}
}
}
```
"""
connectivity = {'status': 'none',
'ipAddress': None,
'macAddress': 'unknown',
'gatewayAddress': None}
connectivity = {'status': 'none', 'interfaces': {}}
try:
connectivity['status'] = await nmcli.is_connected()
net_info = await nmcli.iface_info(nmcli.NETWORK_IFACES.WIFI)
connectivity.update(net_info)
connectivity['interfaces'] = {
i.value: await nmcli.iface_info(i) for i in nmcli.NETWORK_IFACES
}
log.debug("Connectivity: {}".format(connectivity['status']))
log.debug("Interfaces: {}".format(connectivity['interfaces']))
status = 200
except subprocess.CalledProcessError as e:
log.error("CalledProcessError: {}".format(e.stdout))
Expand Down
20 changes: 11 additions & 9 deletions api/src/opentrons/server/http.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from . import endpoints as endp
from .endpoints import (wifi, control, settings, update)
from .endpoints import (networking, control, settings, update)
from opentrons.deck_calibration import endpoints as dc_endp


Expand All @@ -16,16 +16,18 @@ def __init__(self, app, log_file_path):
self.app.router.add_get(
'/health', endp.health)
self.app.router.add_get(
'/wifi/list', wifi.list_networks)
self.app.router.add_post(
'/wifi/configure', wifi.configure)
'/networking/status', networking.status)
# TODO(mc, 2018-10-12): s/wifi/networking
self.app.router.add_get(
'/wifi/status', wifi.status)
self.app.router.add_post('/wifi/keys', wifi.add_key)
self.app.router.add_get('/wifi/keys', wifi.list_keys)
self.app.router.add_delete('/wifi/keys/{key_uuid}', wifi.remove_key)
'/wifi/list', networking.list_networks)
self.app.router.add_post(
'/wifi/configure', networking.configure)
self.app.router.add_post('/wifi/keys', networking.add_key)
self.app.router.add_get('/wifi/keys', networking.list_keys)
self.app.router.add_delete(
'/wifi/keys/{key_uuid}', networking.remove_key)
self.app.router.add_get(
'/wifi/eap-options', wifi.eap_options)
'/wifi/eap-options', networking.eap_options)
self.app.router.add_post(
'/identify', control.identify)
self.app.router.add_get(
Expand Down
39 changes: 28 additions & 11 deletions api/src/opentrons/system/nmcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ async def configure(ssid: str,
# This unfortunately doesn’t respect the --terse flag, so we need to
# regex out the name or the uuid to use later in connection up; the
# uuid is slightly more regular, so that’s what we use.
uuid_matches = re.search( # noqa
"Connection '(.*)'[\s]+\(([\w\d-]+)\) successfully", res) # noqa
uuid_matches = re.search( # noqa
"Connection '(.*)'[\s]+\(([\w\d-]+)\) successfully", res) # noqa
if not uuid_matches:
return False, err.split('\r')[-1]
name = uuid_matches.group(1)
Expand Down Expand Up @@ -507,10 +507,15 @@ async def iface_info(which_iface: NETWORK_IFACES) -> Dict[str, Optional[str]]:
which_iface should be a string in IFACE_NAMES.
"""
default_res: Dict[str, Optional[str]] = {'ipAddress': None,
'macAddress': None,
'gatewayAddress': None}
fields = ['GENERAL.HWADDR', 'IP4.ADDRESS', 'IP4.GATEWAY', 'GENERAL.STATE']
_IFACE_STATE_RE = r'\d+ \((.+)\)'

info: Dict[str, Optional[str]] = {'ipAddress': None,
'macAddress': None,
'gatewayAddress': None,
'state': None,
'type': None}
fields = ['GENERAL.HWADDR', 'IP4.ADDRESS',
'IP4.GATEWAY', 'GENERAL.TYPE', 'GENERAL.STATE']
# Note on this specific command: Most nmcli commands default to a tabular
# output mode, where if there are multiple things to pull a couple specific
# fields from it you’ll get a table where rows are, say, connections, and
Expand All @@ -522,14 +527,26 @@ async def iface_info(which_iface: NETWORK_IFACES) -> Dict[str, Optional[str]]:
'--escape', 'no',
'--terse', '--fields', ','.join(fields),
'dev', 'show', which_iface.value])
values = res.split('\n')

# nmcli uses -- for None, so replace
values = [None if v == '--' else v for v in res.split('\n')]

if len(fields) != len(values):
# We failed
raise ValueError("Bad result from nmcli: {}".format(err))
default_res['macAddress'] = values[0]
default_res['ipAddress'] = values[1]
default_res['gatewayAddress'] = values[2]
return default_res

info['macAddress'] = values[0]
info['ipAddress'] = values[1]
info['gatewayAddress'] = values[2]
info['type'] = values[3]
state_val = values[4]

if state_val:
state_match = re.fullmatch(_IFACE_STATE_RE, state_val)
if state_match:
info['state'] = state_match.group(1)

return info


async def _call(cmd: List[str]) -> Tuple[str, str]:
Expand Down
Loading

0 comments on commit cc2d8d4

Please sign in to comment.