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
1diff --git a/softwareproperties/LivepatchService.py b/softwareproperties/LivepatchService.py
2index 7dcd889..163f729 100644
3--- a/softwareproperties/LivepatchService.py
4+++ b/softwareproperties/LivepatchService.py
5@@ -20,8 +20,12 @@
6 # USA
7
8 from gettext import gettext as _
9+from contextlib import closing
10+from urllib.parse import urlencode
11+import http.client
12 import logging
13 import json
14+import socket
15
16 import gi
17 from gi.repository import Gio, GLib, GObject
18@@ -43,8 +47,6 @@ from softwareproperties.gtk.utils import (
19
20 from softwareproperties.LivepatchSnap import LivepatchSnap
21
22-from .http_unixdomain import unix_http_request
23-
24 def datetime_parser(json_dict):
25 for (key, value) in json_dict.items():
26 try:
27@@ -60,13 +62,27 @@ class LivepatchAvailability:
28 CHECKING = 2
29
30
31-class LivepatchService(GObject.GObject):
32+# copied from update-manager package UpdateManager/Core/LivePatchSocket.py
33+class UHTTPConnection(http.client.HTTPConnection):
34+
35+ def __init__(self, path):
36+ http.client.HTTPConnection.__init__(self, 'localhost')
37+ self.path = path
38+
39+ def connect(self):
40+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
41+ sock.connect(self.path)
42+ self.sock = sock
43
44- # Constants
45- STATUS_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd.sock/status'
46- ENABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/enable'
47- DISABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable'
48- LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
49+
50+# Constants
51+LIVEPATCH_SNAP_CURRENT_DIR = '/var/snap/canonical-livepatch/current'
52+LIVEPATCH_SOCKET = f'{LIVEPATCH_SNAP_CURRENT_DIR}/livepatchd.sock'
53+LIVEPATCH_PRIV_SOCKET = f'{LIVEPATCH_SNAP_CURRENT_DIR}/livepatchd-priv.sock'
54+LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
55+
56+
57+class LivepatchService(GObject.GObject):
58
59 ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}')
60 DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}')
61@@ -95,7 +111,7 @@ class LivepatchService(GObject.GObject):
62 # Init Properties
63 self._availability = LivepatchAvailability.FALSE
64 self._availability_message = None
65- lp_file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
66+ lp_file = Gio.File.new_for_path(path=LIVEPATCH_RUNNING_FILE)
67 self._enabled = lp_file.query_exists()
68
69 # Monitor connectivity status
70@@ -196,8 +212,11 @@ class LivepatchService(GObject.GObject):
71 """
72 try:
73 params = {'verbosity': 3, 'format': 'json'}
74- _, response = unix_http_request('GET', self.STATUS_ENDPOINT, params=params)
75- return json.loads(response, object_hook=datetime_parser)
76+ with closing(UHTTPConnection(LIVEPATCH_SOCKET)) as c:
77+ url = f'/status?{urlencode(params)}'
78+ c.request('GET', url)
79+ response = c.getresponse()
80+ return json.loads(response.read(), object_hook=datetime_parser)
81 except Exception as e:
82 logging.debug('Failed to get Livepatch status: {}'.format(str(e)))
83 return None
84@@ -222,9 +241,12 @@ class LivepatchService(GObject.GObject):
85 @retry(Exception)
86 def _enable_service_with_retry(self, token):
87 params = {'auth-token': token}
88- code, response = unix_http_request('PUT', self.ENABLE_ENDPOINT, params=params)
89- if 400 <= code < 600:
90- return True, self.ENABLE_ERROR_MSG.format(response.decode('utf-8'))
91+ with closing(UHTTPConnection(LIVEPATCH_PRIV_SOCKET)) as c:
92+ url = f'/enable?{urlencode(params)}'
93+ c.request('PUT', url)
94+ response = c.getresponse()
95+ if 400 <= response.status < 600:
96+ return True, self.ENABLE_ERROR_MSG.format(response.read().decode('utf-8'))
97 return False, ''
98
99 def _disable_service(self):
100@@ -242,9 +264,11 @@ class LivepatchService(GObject.GObject):
101
102 @retry(Exception)
103 def _disable_service_with_retry(self):
104- code, response = unix_http_request('PUT', self.DISABLE_ENDPOINT)
105- if 400 <= code < 600:
106- return True, self.DISABLE_ERROR_MSG.format(response.decode('utf-8'))
107+ with closing(UHTTPConnection(LIVEPATCH_PRIV_SOCKET)) as c:
108+ c.request('PUT', '/disable')
109+ response = c.getresponse()
110+ if 400 <= response.status < 600:
111+ return True, self.DISABLE_ERROR_MSG.format(response.read().decode('utf-8'))
112 return False, ''
113
114 # Signals handlers
115diff --git a/softwareproperties/http_unixdomain.py b/softwareproperties/http_unixdomain.py
116deleted file mode 100644
117index 767a9f4..0000000
118--- a/softwareproperties/http_unixdomain.py
119+++ /dev/null
120@@ -1,43 +0,0 @@
121-import socket
122-import http.client
123-from urllib.parse import unquote, urlencode, urlparse
124-
125-# UnixHTTPConnection is from https://github.com/msabramo/requests-unixsocket
126-# which is released under the Apache License 2.0
127-class UnixHTTPConnection(http.client.HTTPConnection, object):
128-
129- def __init__(self, unix_socket_url, timeout=60):
130- """Create an HTTP connection to a unix domain socket
131-
132- :param unix_socket_url: A URL with a scheme of 'http+unix' and the
133- netloc is a percent-encoded path to a unix domain socket. E.g.:
134- 'http+unix://%2Ftmp%2Fprofilesvc.sock/status/pid'
135- """
136- super(UnixHTTPConnection, self).__init__('localhost', timeout=timeout)
137- self.unix_socket_url = unix_socket_url
138- self.timeout = timeout
139- self.sock = None
140-
141- def __del__(self): # base class does not have d'tor
142- if self.sock:
143- self.sock.close()
144-
145- def connect(self):
146- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
147- sock.settimeout(self.timeout)
148- socket_path = unquote(urlparse(self.unix_socket_url).netloc)
149- sock.connect(socket_path)
150- self.sock = sock
151-
152-def unix_http_request(verb, url, params=None):
153- parsed_url = urlparse(url)
154- conn = UnixHTTPConnection(url)
155- try:
156- path = parsed_url.path
157- if params is not None:
158- path = '%s?%s' % (parsed_url.path, urlencode(params))
159- conn.request(verb.upper(), path)
160- response = conn.getresponse()
161- return response.status, response.read()
162- finally:
163- conn.close()

Subscribers

People subscribed via source and target branches