Merge lp:~benji/landscape-client/bug-1508110-resync-users-on-upgrade into lp:~landscape/landscape-client/trunk

Proposed by Benji York
Status: Merged
Approved by: Benji York
Approved revision: 832
Merged at revision: 829
Proposed branch: lp:~benji/landscape-client/bug-1508110-resync-users-on-upgrade
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 259 lines (+134/-5)
4 files modified
debian/landscape-client.postinst (+7/-0)
landscape/monitor/tests/test_usermonitor.py (+69/-0)
landscape/monitor/usermonitor.py (+52/-1)
landscape/user/tests/helpers.py (+6/-4)
To merge this branch: bzr merge lp:~benji/landscape-client/bug-1508110-resync-users-on-upgrade
Reviewer Review Type Date Requested Status
Chad Smith Approve
Simon Poirier (community) Approve
Review via email: mp+285353@code.launchpad.net

Commit message

Add a complete refresh of user data after client install (remediation
for bug 1508110).

Description of the change

This branch is a remediation for bug 1508110 which caused incomplete
user data to be stored in LS.

Upon client install, the postinst script creates a flag file in the
client data directory which signals a complete refresh of user data.
Then when the usermonitor is triggered, it checks for the flag file
and if present sends a total refresh of user data (deleting the flag
file on completion).

Testing instructions:

- build a new set of packages (see "make package")
- stop the client on the test machine
- install -common and -client packages
- if you don't want to wait an hour for the full refresh to be
  triggered, edit the run_interval class attribute in usermonitor.py
- verify that /var/lib/landscape/client/user-update-flag exists
- set "log_level = debug" in /etc/landscape/client.conf
- start the client
- wait run_interval seconds and verify that a complete refresh took
  place via /var/log/landscape/monitor.log
- verify that /var/lib/landscape/client/user-update-flag has been
  removed
- if you were lucky enough to be testing with an account with
  incomplete user data, verify that LS now lists complete user data
  for the computer in question

To post a comment you must log in.
Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Chad Smith (chad.smith) wrote :

Ok got through this, thank you.

Note 1:
Chatted a bit in IRC about the round trip between client sending this initial duplicate user data to OPL and whether we care about trimming that initial data to a single duplicated pair of user rows because we know it will likely all be thrown out anyway by OPL and re-requested.

Answer: We don't care about trimming the initial failed client user sync. Let the resync roundtrip from OPL handle things since this is a one time operation.

The OPL traceback and user scoped resynch request is here for reference.
https://pastebin.canonical.com/149414/

Marked needs fixing only to see the addressed postinst logic to handle upgrade path.

Revision history for this message
Chad Smith (chad.smith) :
review: Needs Fixing
Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Simon Poirier (simpoir) :
830. By Benji York

add code to postinst to respect the user's data_dir and a big comment explaining this insanity

Revision history for this message
Benji York (benji) wrote :

Chad, I've addressed your comments. Thanks very much for the good review.

Revision history for this message
Benji York (benji) :
Revision history for this message
Simon Poirier (simpoir) :
831. By Benji York

remove uneeded variable

Revision history for this message
Benji York (benji) :
Revision history for this message
Simon Poirier (simpoir) wrote :

+1

review: Approve
Revision history for this message
Chad Smith (chad.smith) wrote :

Excellent, we can now resync the masses.

review: Approve
Revision history for this message
Chad Smith (chad.smith) wrote :

Upgrade path from a broken client as well greenfield registration tested with these new bits.

review: Approve
832. By Benji York

merge from trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/landscape-client.postinst'
2--- debian/landscape-client.postinst 2013-05-21 13:34:24 +0000
3+++ debian/landscape-client.postinst 2016-02-11 14:22:25 +0000
4@@ -119,6 +119,13 @@
5 fi
6 fi
7
8+ # In response to bug 1508110 we need to trigger a complete update of
9+ # user information. The flag file will be removed by the client when
10+ # the update completes.
11+ DATA_PATH="`grep ^data_path /etc/landscape/client.conf | cut -d= -f2 | tr -d '[[:space:]]'`"
12+ USER_UPDATE_FLAG_FILE="$DATA_PATH/user-update-flag"
13+ echo "This file indicates that the Landscape client needs to send updated user information to the server." >> $USER_UPDATE_FLAG_FILE
14+ chown landscape $USER_UPDATE_FLAG_FILE
15 ;;
16
17 abort-upgrade|abort-remove|abort-deconfigure)
18
19=== modified file 'landscape/monitor/tests/test_usermonitor.py'
20--- landscape/monitor/tests/test_usermonitor.py 2016-01-15 16:13:04 +0000
21+++ landscape/monitor/tests/test_usermonitor.py 2016-02-11 14:22:25 +0000
22@@ -1,3 +1,6 @@
23+import os
24+import tempfile
25+
26 from twisted.internet.defer import fail
27
28 from landscape.amp import ComponentPublisher
29@@ -7,6 +10,7 @@
30 from landscape.user.tests.helpers import FakeUserProvider
31 from landscape.tests.helpers import LandscapeTest, MonitorHelper
32 from landscape.tests.mocker import ANY
33+import landscape.monitor.usermonitor
34
35
36 class UserMonitorNoManagerTest(LandscapeTest):
37@@ -59,10 +63,16 @@
38 self.publisher.start()
39 self.provider = FakeUserProvider()
40 self.plugin = UserMonitor(self.provider)
41+ # Part of bug 1048576 remediation:
42+ self._original_USER_UPDATE_FLAG_FILE = (
43+ landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE)
44
45 def tearDown(self):
46 self.publisher.stop()
47 self.plugin.stop()
48+ # Part of bug 1048576 remediation:
49+ landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = (
50+ self._original_USER_UPDATE_FLAG_FILE)
51 return super(UserMonitorTest, self).tearDown()
52
53 def test_constants(self):
54@@ -327,6 +337,65 @@
55 result.addCallback(lambda x: connector.disconnect())
56 return result
57
58+ def test_detect_changes_clears_user_provider_if_flag_file_exists(self):
59+ """
60+ Temporary bug 1508110 remediation: If a special flag file exists,
61+ cached user data is dumped and a complete refresh of all user data is
62+ transmitted.
63+ """
64+ self.monitor.add(self.plugin)
65+
66+ class FauxUserChanges(object):
67+ cleared = False
68+
69+ def __init__(self, *args):
70+ pass
71+
72+ def create_diff(self):
73+ return None
74+
75+ def clear(self):
76+ self.__class__.cleared = True
77+
78+ # Create the (temporary, test) user update flag file.
79+ landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = \
80+ update_flag_file = tempfile.mkstemp()[1]
81+ self.addCleanup(lambda: os.remove(update_flag_file))
82+
83+ # Trigger a detect changes.
84+ self.plugin._persist = None
85+ self.plugin._detect_changes([], UserChanges=FauxUserChanges)
86+
87+ # The clear() method was called.
88+ self.assertTrue(FauxUserChanges.cleared)
89+
90+ def test_detect_changes_deletes_flag_file(self):
91+ """
92+ Temporary bug 1508110 remediation: The total user data refresh flag
93+ file is deleted once the data has been sent.
94+ """
95+
96+ def got_result(result):
97+ # The flag file has been deleted.
98+ self.assertFalse(os.path.exists(update_flag_file))
99+
100+ # Create the (temporary, test) user update flag file.
101+ landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = \
102+ update_flag_file = tempfile.mkstemp()[1]
103+
104+ self.broker_service.message_store.set_accepted_types(["users"])
105+ self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,",
106+ "/home/jdoe", "/bin/sh")]
107+ self.provider.groups = []
108+
109+ self.monitor.add(self.plugin)
110+ connector = RemoteUserMonitorConnector(self.reactor, self.config)
111+ result = connector.connect()
112+ result.addCallback(lambda remote: remote.detect_changes())
113+ result.addCallback(got_result)
114+ result.addCallback(lambda x: connector.disconnect())
115+ return result
116+
117 def test_no_message_if_not_accepted(self):
118 """
119 Don't add any messages at all if the broker isn't currently
120
121=== modified file 'landscape/monitor/usermonitor.py'
122--- landscape/monitor/usermonitor.py 2016-01-13 13:54:28 +0000
123+++ landscape/monitor/usermonitor.py 2016-02-11 14:22:25 +0000
124@@ -1,3 +1,7 @@
125+import logging
126+import os
127+import os.path
128+
129 from twisted.internet.defer import maybeDeferred
130
131 from landscape.lib.log import log_failure
132@@ -8,6 +12,10 @@
133 from landscape.user.provider import UserProvider
134
135
136+# Part of bug 1048576 remediation:
137+USER_UPDATE_FLAG_FILE = "user-update-flag"
138+
139+
140 class UserMonitor(MonitorPlugin):
141 """
142 A plugin which monitors the system user databases.
143@@ -87,7 +95,8 @@
144 result.addErrback(lambda f: self._detect_changes([], operation_id))
145 return result
146
147- def _detect_changes(self, locked_users, operation_id=None):
148+ def _detect_changes(self, locked_users, operation_id=None,
149+ UserChanges=UserChanges):
150
151 def update_snapshot(result):
152 changes.snapshot()
153@@ -99,6 +108,22 @@
154
155 self._provider.locked_users = locked_users
156 changes = UserChanges(self._persist, self._provider)
157+
158+ # Part of bug 1048576 remediation: If the flag file exists, we need to
159+ # do a full update of user data.
160+ full_refresh = os.path.exists(self.user_update_flag_file_path)
161+ if full_refresh:
162+ # Clear the record of what changes have been sent to the server in
163+ # order to force sending of all user data which will do one of two
164+ # things server side: either the server has no user data at all,
165+ # in which case it will now have a complete copy, otherwise it
166+ # will have at least some user data which this message will
167+ # duplicate, provoking the server to note the inconsistency and
168+ # request a full resync of the user data. Either way, the result
169+ # is the same: the client and server will be in sync with regard
170+ # to users.
171+ changes.clear()
172+
173 message = changes.create_diff()
174
175 if message:
176@@ -108,9 +133,35 @@
177 result = self.registry.broker.send_message(
178 message, self._session_id, urgent=True)
179 result.addCallback(update_snapshot)
180+
181+ # Part of bug 1048576 remediation:
182+ if full_refresh:
183+ # If we are doing a full refresh, we want to remove the flag
184+ # file that triggered the refresh if it completes successfully.
185+ result.addCallback(lambda _: self._remove_update_flag_file())
186+
187 result.addErrback(log_error)
188 return result
189
190+ def _remove_update_flag_file(self):
191+ """Remove the full update flag file, logging any errors.
192+
193+ This is part of the bug 1048576 remediation.
194+ """
195+ try:
196+ os.remove(self.user_update_flag_file_path)
197+ except OSError:
198+ logging.exception("Error removing user update flag file.")
199+
200+ @property
201+ def user_update_flag_file_path(self):
202+ """Location of the user update flag file.
203+
204+ This is part of the bug 1048576 remediation.
205+ """
206+ return os.path.join(
207+ self.registry.config.data_path, USER_UPDATE_FLAG_FILE)
208+
209
210 class RemoteUserMonitorConnector(ComponentConnector):
211
212
213=== modified file 'landscape/user/tests/helpers.py'
214--- landscape/user/tests/helpers.py 2008-06-16 15:42:13 +0000
215+++ landscape/user/tests/helpers.py 2016-02-11 14:22:25 +0000
216@@ -42,7 +42,7 @@
217 gecos_string = "%s,%s,%s,%s" % (name, location or "",
218 work_phone or "", home_phone or "")
219 userdata = (username, "x", uid, primary_gid, gecos_string,
220- "/bin/sh" , "/home/user")
221+ "/bin/sh", "/home/user")
222 self.provider.users.append(userdata)
223 except KeyError:
224 raise UserManagementError("add_user failed")
225@@ -61,7 +61,8 @@
226 data = self._users.get(username, None)
227 if data:
228 data["enabled"] = True
229- # This will generate a shadow file with only the unlocked user in it.
230+ # This will generate a shadow file with only the unlocked user in
231+ # it.
232 self._make_fake_shadow_file([], [username])
233 return "unlock_user succeeded"
234 raise UserManagementError("unlock_user failed")
235@@ -94,7 +95,7 @@
236 userdata = (username, "x", data["uid"], data["primary-gid"],
237 "%s,%s,%s,%s," % (name, location, work_number,
238 home_number),
239- "/bin/sh" , "/home/user")
240+ "/bin/sh", "/home/user")
241 self.provider.users = [userdata]
242 return "set_user_details succeeded"
243
244@@ -150,7 +151,7 @@
245
246 class FakeUserProvider(UserProviderBase):
247
248- def __init__(self, users=None, groups=None, popen=None, shadow_file=None,
249+ def __init__(self, users=None, groups=None, popen=None, shadow_file=None,
250 locked_users=None):
251 self.users = users
252 self.groups = groups
253@@ -169,6 +170,7 @@
254 self.groups = []
255 return self.groups
256
257+
258 class FakeUserInfo(object):
259 """Implements enough functionality to work for Changes tests."""
260

Subscribers

People subscribed via source and target branches

to all changes: