Merge ~ddstreet/software-properties:simple_http_unix_connection into software-properties:ubuntu/master

Proposed by Dan Streetman
Status: Merged
Merged at revision: fe446246f23a119d305b5cb924eeba31ca2b366d
Proposed branch: ~ddstreet/software-properties:simple_http_unix_connection
Merge into: software-properties:ubuntu/master
Diff against target: 163 lines (+41/-60)
2 files modified
dev/null (+0/-43)
softwareproperties/LivepatchService.py (+41/-17)
Reviewer Review Type Date Requested Status
Shivaram Lingamneni Pending
Review via email: mp+396926@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Shivaram Lingamneni (slingamn) wrote :

This crashed for me on 20.04:

Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: Traceback (most recent call last):
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: File "/usr/bin/software-properties-gtk", line 37, in <module>
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: from softwareproperties.gtk.SoftwarePropertiesGtk import SoftwarePropertiesGtk
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: File "/usr/lib/python3/dist-packages/softwareproperties/gtk/SoftwarePropertiesGtk.py", line 56, in <module>
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: from .LivepatchPage import LivepatchPage
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: File "/usr/lib/python3/dist-packages/softwareproperties/gtk/LivepatchPage.py", line 31, in <module>
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: from softwareproperties.LivepatchService import (
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: File "/usr/lib/python3/dist-packages/softwareproperties/LivepatchService.py", line 24, in <module>
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: from urllib import urlparse, urlencode
Jan 26 12:17:00 guivm gnome-control-center.desktop[2058]: ImportError: cannot import name 'urlparse' from 'urllib' (/usr/lib/python3.8/urllib/__init__.py)

With the import changed to `from urllib.parse import urlparse, urlencode`, it doesn't crash, but the UI displays "Failed to retrieve Livepatch status.". I think it might be hitting an exception somewhere?

Revision history for this message
Shivaram Lingamneni (slingamn) wrote :

Found this in strace: "WARNING:root:name 'socket' is not defined, Retrying in 12.8 seconds..."

I added the missing import of `socket`, but then I got this from strace:

connect(9, {sa_family=AF_UNIX, sun_path=http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable}, 90) = -1 ENOENT (No such file or directory)

so it's not correctly decoding the socket path from the URL.

Revision history for this message
Dan Streetman (ddstreet) wrote :

thanks - added the import socket and fixed the socket paths; can you try again?

Revision history for this message
Shivaram Lingamneni (slingamn) wrote :

I believe I tested this successfully in 20.04, with minor backporting. The following changes are necessary on top of ecf462b:

1. `sock.sock = sock` should be `self.sock = sock`
2. A reference to `self.LIVEPATCH_RUNNING_FILE` should be to `LIVEPATCH_RUNNING_FILE` instead

Revision history for this message
Dan Streetman (ddstreet) wrote :

thanks, think it should be all corrected now

Revision history for this message
Shivaram Lingamneni (slingamn) wrote :

Looks good to me --- fe44624 is identical to the version I successfully tested, except for the 20.04 backporting changes.

Thanks!

Revision history for this message
Dan Streetman (ddstreet) wrote :

thanks, merged!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/softwareproperties/LivepatchService.py b/softwareproperties/LivepatchService.py
index 7dcd889..163f729 100644
--- a/softwareproperties/LivepatchService.py
+++ b/softwareproperties/LivepatchService.py
@@ -20,8 +20,12 @@
20# USA20# USA
2121
22from gettext import gettext as _22from gettext import gettext as _
23from contextlib import closing
24from urllib.parse import urlencode
25import http.client
23import logging26import logging
24import json27import json
28import socket
2529
26import gi30import gi
27from gi.repository import Gio, GLib, GObject31from gi.repository import Gio, GLib, GObject
@@ -43,8 +47,6 @@ from softwareproperties.gtk.utils import (
4347
44from softwareproperties.LivepatchSnap import LivepatchSnap48from softwareproperties.LivepatchSnap import LivepatchSnap
4549
46from .http_unixdomain import unix_http_request
47
48def datetime_parser(json_dict):50def datetime_parser(json_dict):
49 for (key, value) in json_dict.items():51 for (key, value) in json_dict.items():
50 try:52 try:
@@ -60,13 +62,27 @@ class LivepatchAvailability:
60 CHECKING = 262 CHECKING = 2
6163
6264
63class LivepatchService(GObject.GObject):65# copied from update-manager package UpdateManager/Core/LivePatchSocket.py
66class UHTTPConnection(http.client.HTTPConnection):
67
68 def __init__(self, path):
69 http.client.HTTPConnection.__init__(self, 'localhost')
70 self.path = path
71
72 def connect(self):
73 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
74 sock.connect(self.path)
75 self.sock = sock
6476
65 # Constants77
66 STATUS_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd.sock/status'78# Constants
67 ENABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/enable'79LIVEPATCH_SNAP_CURRENT_DIR = '/var/snap/canonical-livepatch/current'
68 DISABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable'80LIVEPATCH_SOCKET = f'{LIVEPATCH_SNAP_CURRENT_DIR}/livepatchd.sock'
69 LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'81LIVEPATCH_PRIV_SOCKET = f'{LIVEPATCH_SNAP_CURRENT_DIR}/livepatchd-priv.sock'
82LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
83
84
85class LivepatchService(GObject.GObject):
7086
71 ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}')87 ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}')
72 DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}')88 DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}')
@@ -95,7 +111,7 @@ class LivepatchService(GObject.GObject):
95 # Init Properties111 # Init Properties
96 self._availability = LivepatchAvailability.FALSE112 self._availability = LivepatchAvailability.FALSE
97 self._availability_message = None113 self._availability_message = None
98 lp_file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)114 lp_file = Gio.File.new_for_path(path=LIVEPATCH_RUNNING_FILE)
99 self._enabled = lp_file.query_exists()115 self._enabled = lp_file.query_exists()
100116
101 # Monitor connectivity status117 # Monitor connectivity status
@@ -196,8 +212,11 @@ class LivepatchService(GObject.GObject):
196 """212 """
197 try:213 try:
198 params = {'verbosity': 3, 'format': 'json'}214 params = {'verbosity': 3, 'format': 'json'}
199 _, response = unix_http_request('GET', self.STATUS_ENDPOINT, params=params)215 with closing(UHTTPConnection(LIVEPATCH_SOCKET)) as c:
200 return json.loads(response, object_hook=datetime_parser)216 url = f'/status?{urlencode(params)}'
217 c.request('GET', url)
218 response = c.getresponse()
219 return json.loads(response.read(), object_hook=datetime_parser)
201 except Exception as e:220 except Exception as e:
202 logging.debug('Failed to get Livepatch status: {}'.format(str(e)))221 logging.debug('Failed to get Livepatch status: {}'.format(str(e)))
203 return None222 return None
@@ -222,9 +241,12 @@ class LivepatchService(GObject.GObject):
222 @retry(Exception)241 @retry(Exception)
223 def _enable_service_with_retry(self, token):242 def _enable_service_with_retry(self, token):
224 params = {'auth-token': token}243 params = {'auth-token': token}
225 code, response = unix_http_request('PUT', self.ENABLE_ENDPOINT, params=params)244 with closing(UHTTPConnection(LIVEPATCH_PRIV_SOCKET)) as c:
226 if 400 <= code < 600:245 url = f'/enable?{urlencode(params)}'
227 return True, self.ENABLE_ERROR_MSG.format(response.decode('utf-8'))246 c.request('PUT', url)
247 response = c.getresponse()
248 if 400 <= response.status < 600:
249 return True, self.ENABLE_ERROR_MSG.format(response.read().decode('utf-8'))
228 return False, ''250 return False, ''
229251
230 def _disable_service(self):252 def _disable_service(self):
@@ -242,9 +264,11 @@ class LivepatchService(GObject.GObject):
242264
243 @retry(Exception)265 @retry(Exception)
244 def _disable_service_with_retry(self):266 def _disable_service_with_retry(self):
245 code, response = unix_http_request('PUT', self.DISABLE_ENDPOINT)267 with closing(UHTTPConnection(LIVEPATCH_PRIV_SOCKET)) as c:
246 if 400 <= code < 600:268 c.request('PUT', '/disable')
247 return True, self.DISABLE_ERROR_MSG.format(response.decode('utf-8'))269 response = c.getresponse()
270 if 400 <= response.status < 600:
271 return True, self.DISABLE_ERROR_MSG.format(response.read().decode('utf-8'))
248 return False, ''272 return False, ''
249273
250 # Signals handlers274 # Signals handlers
diff --git a/softwareproperties/http_unixdomain.py b/softwareproperties/http_unixdomain.py
251deleted file mode 100644275deleted file mode 100644
index 767a9f4..0000000
--- a/softwareproperties/http_unixdomain.py
+++ /dev/null
@@ -1,43 +0,0 @@
1import socket
2import http.client
3from urllib.parse import unquote, urlencode, urlparse
4
5# UnixHTTPConnection is from https://github.com/msabramo/requests-unixsocket
6# which is released under the Apache License 2.0
7class UnixHTTPConnection(http.client.HTTPConnection, object):
8
9 def __init__(self, unix_socket_url, timeout=60):
10 """Create an HTTP connection to a unix domain socket
11
12 :param unix_socket_url: A URL with a scheme of 'http+unix' and the
13 netloc is a percent-encoded path to a unix domain socket. E.g.:
14 'http+unix://%2Ftmp%2Fprofilesvc.sock/status/pid'
15 """
16 super(UnixHTTPConnection, self).__init__('localhost', timeout=timeout)
17 self.unix_socket_url = unix_socket_url
18 self.timeout = timeout
19 self.sock = None
20
21 def __del__(self): # base class does not have d'tor
22 if self.sock:
23 self.sock.close()
24
25 def connect(self):
26 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
27 sock.settimeout(self.timeout)
28 socket_path = unquote(urlparse(self.unix_socket_url).netloc)
29 sock.connect(socket_path)
30 self.sock = sock
31
32def unix_http_request(verb, url, params=None):
33 parsed_url = urlparse(url)
34 conn = UnixHTTPConnection(url)
35 try:
36 path = parsed_url.path
37 if params is not None:
38 path = '%s?%s' % (parsed_url.path, urlencode(params))
39 conn.request(verb.upper(), path)
40 response = conn.getresponse()
41 return response.status, response.read()
42 finally:
43 conn.close()

Subscribers

People subscribed via source and target branches