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
=== modified file 'debian/landscape-client.postinst'
--- debian/landscape-client.postinst 2013-05-21 13:34:24 +0000
+++ debian/landscape-client.postinst 2016-02-11 14:22:25 +0000
@@ -119,6 +119,13 @@
119 fi119 fi
120 fi120 fi
121121
122 # In response to bug 1508110 we need to trigger a complete update of
123 # user information. The flag file will be removed by the client when
124 # the update completes.
125 DATA_PATH="`grep ^data_path /etc/landscape/client.conf | cut -d= -f2 | tr -d '[[:space:]]'`"
126 USER_UPDATE_FLAG_FILE="$DATA_PATH/user-update-flag"
127 echo "This file indicates that the Landscape client needs to send updated user information to the server." >> $USER_UPDATE_FLAG_FILE
128 chown landscape $USER_UPDATE_FLAG_FILE
122 ;;129 ;;
123130
124 abort-upgrade|abort-remove|abort-deconfigure)131 abort-upgrade|abort-remove|abort-deconfigure)
125132
=== modified file 'landscape/monitor/tests/test_usermonitor.py'
--- landscape/monitor/tests/test_usermonitor.py 2016-01-15 16:13:04 +0000
+++ landscape/monitor/tests/test_usermonitor.py 2016-02-11 14:22:25 +0000
@@ -1,3 +1,6 @@
1import os
2import tempfile
3
1from twisted.internet.defer import fail4from twisted.internet.defer import fail
25
3from landscape.amp import ComponentPublisher6from landscape.amp import ComponentPublisher
@@ -7,6 +10,7 @@
7from landscape.user.tests.helpers import FakeUserProvider10from landscape.user.tests.helpers import FakeUserProvider
8from landscape.tests.helpers import LandscapeTest, MonitorHelper11from landscape.tests.helpers import LandscapeTest, MonitorHelper
9from landscape.tests.mocker import ANY12from landscape.tests.mocker import ANY
13import landscape.monitor.usermonitor
1014
1115
12class UserMonitorNoManagerTest(LandscapeTest):16class UserMonitorNoManagerTest(LandscapeTest):
@@ -59,10 +63,16 @@
59 self.publisher.start()63 self.publisher.start()
60 self.provider = FakeUserProvider()64 self.provider = FakeUserProvider()
61 self.plugin = UserMonitor(self.provider)65 self.plugin = UserMonitor(self.provider)
66 # Part of bug 1048576 remediation:
67 self._original_USER_UPDATE_FLAG_FILE = (
68 landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE)
6269
63 def tearDown(self):70 def tearDown(self):
64 self.publisher.stop()71 self.publisher.stop()
65 self.plugin.stop()72 self.plugin.stop()
73 # Part of bug 1048576 remediation:
74 landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = (
75 self._original_USER_UPDATE_FLAG_FILE)
66 return super(UserMonitorTest, self).tearDown()76 return super(UserMonitorTest, self).tearDown()
6777
68 def test_constants(self):78 def test_constants(self):
@@ -327,6 +337,65 @@
327 result.addCallback(lambda x: connector.disconnect())337 result.addCallback(lambda x: connector.disconnect())
328 return result338 return result
329339
340 def test_detect_changes_clears_user_provider_if_flag_file_exists(self):
341 """
342 Temporary bug 1508110 remediation: If a special flag file exists,
343 cached user data is dumped and a complete refresh of all user data is
344 transmitted.
345 """
346 self.monitor.add(self.plugin)
347
348 class FauxUserChanges(object):
349 cleared = False
350
351 def __init__(self, *args):
352 pass
353
354 def create_diff(self):
355 return None
356
357 def clear(self):
358 self.__class__.cleared = True
359
360 # Create the (temporary, test) user update flag file.
361 landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = \
362 update_flag_file = tempfile.mkstemp()[1]
363 self.addCleanup(lambda: os.remove(update_flag_file))
364
365 # Trigger a detect changes.
366 self.plugin._persist = None
367 self.plugin._detect_changes([], UserChanges=FauxUserChanges)
368
369 # The clear() method was called.
370 self.assertTrue(FauxUserChanges.cleared)
371
372 def test_detect_changes_deletes_flag_file(self):
373 """
374 Temporary bug 1508110 remediation: The total user data refresh flag
375 file is deleted once the data has been sent.
376 """
377
378 def got_result(result):
379 # The flag file has been deleted.
380 self.assertFalse(os.path.exists(update_flag_file))
381
382 # Create the (temporary, test) user update flag file.
383 landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = \
384 update_flag_file = tempfile.mkstemp()[1]
385
386 self.broker_service.message_store.set_accepted_types(["users"])
387 self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,",
388 "/home/jdoe", "/bin/sh")]
389 self.provider.groups = []
390
391 self.monitor.add(self.plugin)
392 connector = RemoteUserMonitorConnector(self.reactor, self.config)
393 result = connector.connect()
394 result.addCallback(lambda remote: remote.detect_changes())
395 result.addCallback(got_result)
396 result.addCallback(lambda x: connector.disconnect())
397 return result
398
330 def test_no_message_if_not_accepted(self):399 def test_no_message_if_not_accepted(self):
331 """400 """
332 Don't add any messages at all if the broker isn't currently401 Don't add any messages at all if the broker isn't currently
333402
=== modified file 'landscape/monitor/usermonitor.py'
--- landscape/monitor/usermonitor.py 2016-01-13 13:54:28 +0000
+++ landscape/monitor/usermonitor.py 2016-02-11 14:22:25 +0000
@@ -1,3 +1,7 @@
1import logging
2import os
3import os.path
4
1from twisted.internet.defer import maybeDeferred5from twisted.internet.defer import maybeDeferred
26
3from landscape.lib.log import log_failure7from landscape.lib.log import log_failure
@@ -8,6 +12,10 @@
8from landscape.user.provider import UserProvider12from landscape.user.provider import UserProvider
913
1014
15# Part of bug 1048576 remediation:
16USER_UPDATE_FLAG_FILE = "user-update-flag"
17
18
11class UserMonitor(MonitorPlugin):19class UserMonitor(MonitorPlugin):
12 """20 """
13 A plugin which monitors the system user databases.21 A plugin which monitors the system user databases.
@@ -87,7 +95,8 @@
87 result.addErrback(lambda f: self._detect_changes([], operation_id))95 result.addErrback(lambda f: self._detect_changes([], operation_id))
88 return result96 return result
8997
90 def _detect_changes(self, locked_users, operation_id=None):98 def _detect_changes(self, locked_users, operation_id=None,
99 UserChanges=UserChanges):
91100
92 def update_snapshot(result):101 def update_snapshot(result):
93 changes.snapshot()102 changes.snapshot()
@@ -99,6 +108,22 @@
99108
100 self._provider.locked_users = locked_users109 self._provider.locked_users = locked_users
101 changes = UserChanges(self._persist, self._provider)110 changes = UserChanges(self._persist, self._provider)
111
112 # Part of bug 1048576 remediation: If the flag file exists, we need to
113 # do a full update of user data.
114 full_refresh = os.path.exists(self.user_update_flag_file_path)
115 if full_refresh:
116 # Clear the record of what changes have been sent to the server in
117 # order to force sending of all user data which will do one of two
118 # things server side: either the server has no user data at all,
119 # in which case it will now have a complete copy, otherwise it
120 # will have at least some user data which this message will
121 # duplicate, provoking the server to note the inconsistency and
122 # request a full resync of the user data. Either way, the result
123 # is the same: the client and server will be in sync with regard
124 # to users.
125 changes.clear()
126
102 message = changes.create_diff()127 message = changes.create_diff()
103128
104 if message:129 if message:
@@ -108,9 +133,35 @@
108 result = self.registry.broker.send_message(133 result = self.registry.broker.send_message(
109 message, self._session_id, urgent=True)134 message, self._session_id, urgent=True)
110 result.addCallback(update_snapshot)135 result.addCallback(update_snapshot)
136
137 # Part of bug 1048576 remediation:
138 if full_refresh:
139 # If we are doing a full refresh, we want to remove the flag
140 # file that triggered the refresh if it completes successfully.
141 result.addCallback(lambda _: self._remove_update_flag_file())
142
111 result.addErrback(log_error)143 result.addErrback(log_error)
112 return result144 return result
113145
146 def _remove_update_flag_file(self):
147 """Remove the full update flag file, logging any errors.
148
149 This is part of the bug 1048576 remediation.
150 """
151 try:
152 os.remove(self.user_update_flag_file_path)
153 except OSError:
154 logging.exception("Error removing user update flag file.")
155
156 @property
157 def user_update_flag_file_path(self):
158 """Location of the user update flag file.
159
160 This is part of the bug 1048576 remediation.
161 """
162 return os.path.join(
163 self.registry.config.data_path, USER_UPDATE_FLAG_FILE)
164
114165
115class RemoteUserMonitorConnector(ComponentConnector):166class RemoteUserMonitorConnector(ComponentConnector):
116167
117168
=== modified file 'landscape/user/tests/helpers.py'
--- landscape/user/tests/helpers.py 2008-06-16 15:42:13 +0000
+++ landscape/user/tests/helpers.py 2016-02-11 14:22:25 +0000
@@ -42,7 +42,7 @@
42 gecos_string = "%s,%s,%s,%s" % (name, location or "",42 gecos_string = "%s,%s,%s,%s" % (name, location or "",
43 work_phone or "", home_phone or "")43 work_phone or "", home_phone or "")
44 userdata = (username, "x", uid, primary_gid, gecos_string,44 userdata = (username, "x", uid, primary_gid, gecos_string,
45 "/bin/sh" , "/home/user")45 "/bin/sh", "/home/user")
46 self.provider.users.append(userdata)46 self.provider.users.append(userdata)
47 except KeyError:47 except KeyError:
48 raise UserManagementError("add_user failed")48 raise UserManagementError("add_user failed")
@@ -61,7 +61,8 @@
61 data = self._users.get(username, None)61 data = self._users.get(username, None)
62 if data:62 if data:
63 data["enabled"] = True63 data["enabled"] = True
64 # This will generate a shadow file with only the unlocked user in it.64 # This will generate a shadow file with only the unlocked user in
65 # it.
65 self._make_fake_shadow_file([], [username])66 self._make_fake_shadow_file([], [username])
66 return "unlock_user succeeded"67 return "unlock_user succeeded"
67 raise UserManagementError("unlock_user failed")68 raise UserManagementError("unlock_user failed")
@@ -94,7 +95,7 @@
94 userdata = (username, "x", data["uid"], data["primary-gid"],95 userdata = (username, "x", data["uid"], data["primary-gid"],
95 "%s,%s,%s,%s," % (name, location, work_number,96 "%s,%s,%s,%s," % (name, location, work_number,
96 home_number),97 home_number),
97 "/bin/sh" , "/home/user")98 "/bin/sh", "/home/user")
98 self.provider.users = [userdata]99 self.provider.users = [userdata]
99 return "set_user_details succeeded"100 return "set_user_details succeeded"
100101
@@ -150,7 +151,7 @@
150151
151class FakeUserProvider(UserProviderBase):152class FakeUserProvider(UserProviderBase):
152153
153 def __init__(self, users=None, groups=None, popen=None, shadow_file=None, 154 def __init__(self, users=None, groups=None, popen=None, shadow_file=None,
154 locked_users=None):155 locked_users=None):
155 self.users = users156 self.users = users
156 self.groups = groups157 self.groups = groups
@@ -169,6 +170,7 @@
169 self.groups = []170 self.groups = []
170 return self.groups171 return self.groups
171172
173
172class FakeUserInfo(object):174class FakeUserInfo(object):
173 """Implements enough functionality to work for Changes tests."""175 """Implements enough functionality to work for Changes tests."""
174176

Subscribers

People subscribed via source and target branches

to all changes: