]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/cephfs/test_sessionmap.py
import 15.2.0 Octopus source
[ceph.git] / ceph / qa / tasks / cephfs / test_sessionmap.py
CommitLineData
81eedcae 1import time
7c673cae
FG
2import json
3import logging
7c673cae
FG
4
5from tasks.cephfs.fuse_mount import FuseMount
6from teuthology.exceptions import CommandFailedError
7from tasks.cephfs.cephfs_test_case import CephFSTestCase
9f95a23c 8from teuthology.misc import sudo_write_file
7c673cae
FG
9
10log = logging.getLogger(__name__)
11
12
13class TestSessionMap(CephFSTestCase):
14 CLIENTS_REQUIRED = 2
15 MDSS_REQUIRED = 2
16
17 def test_tell_session_drop(self):
18 """
19 That when a `tell` command is sent using the python CLI,
20 its MDS session is gone after it terminates
21 """
22 self.mount_a.umount_wait()
23 self.mount_b.umount_wait()
24
81eedcae
TL
25 status = self.fs.status()
26 self.fs.rank_tell(["session", "ls"], status=status)
7c673cae 27
81eedcae 28 ls_data = self.fs.rank_asok(['session', 'ls'], status=status)
7c673cae
FG
29 self.assertEqual(len(ls_data), 0)
30
81eedcae
TL
31 def _get_connection_count(self, status=None):
32 perf = self.fs.rank_asok(["perf", "dump"], status=status)
33 conn = 0
9f95a23c 34 for module, dump in perf.items():
81eedcae
TL
35 if "AsyncMessenger::Worker" in module:
36 conn += dump['msgr_active_connections']
37 return conn
7c673cae
FG
38
39 def test_tell_conn_close(self):
40 """
41 That when a `tell` command is sent using the python CLI,
81eedcae 42 the conn count goes back to where it started (i.e. we aren't
7c673cae
FG
43 leaving connections open)
44 """
45 self.mount_a.umount_wait()
46 self.mount_b.umount_wait()
47
81eedcae
TL
48 status = self.fs.status()
49 s = self._get_connection_count(status=status)
50 self.fs.rank_tell(["session", "ls"], status=status)
51 e = self._get_connection_count(status=status)
7c673cae 52
81eedcae 53 self.assertEqual(s, e)
7c673cae
FG
54
55 def test_mount_conn_close(self):
56 """
57 That when a client unmounts, the thread count on the MDS goes back
58 to what it was before the client mounted
59 """
60 self.mount_a.umount_wait()
61 self.mount_b.umount_wait()
62
81eedcae
TL
63 status = self.fs.status()
64 s = self._get_connection_count(status=status)
7c673cae
FG
65 self.mount_a.mount()
66 self.mount_a.wait_until_mounted()
81eedcae 67 self.assertGreater(self._get_connection_count(status=status), s)
7c673cae 68 self.mount_a.umount_wait()
81eedcae 69 e = self._get_connection_count(status=status)
7c673cae 70
81eedcae 71 self.assertEqual(s, e)
7c673cae
FG
72
73 def test_version_splitting(self):
74 """
75 That when many sessions are updated, they are correctly
76 split into multiple versions to obey mds_sessionmap_keys_per_op
77 """
78
7c673cae
FG
79 self.mount_a.umount_wait()
80 self.mount_b.umount_wait()
81
82 # Configure MDS to write one OMAP key at once
83 self.set_conf('mds', 'mds_sessionmap_keys_per_op', 1)
84 self.fs.mds_fail_restart()
9f95a23c 85 status = self.fs.wait_for_daemons()
7c673cae
FG
86
87 # Bring the clients back
88 self.mount_a.mount()
89 self.mount_b.mount()
7c673cae
FG
90
91 # See that they've got sessions
81eedcae 92 self.assert_session_count(2, mds_id=self.fs.get_rank(status=status)['name'])
7c673cae
FG
93
94 # See that we persist their sessions
81eedcae 95 self.fs.rank_asok(["flush", "journal"], rank=0, status=status)
7c673cae
FG
96 table_json = json.loads(self.fs.table_tool(["0", "show", "session"]))
97 log.info("SessionMap: {0}".format(json.dumps(table_json, indent=2)))
98 self.assertEqual(table_json['0']['result'], 0)
92f5a8d4 99 self.assertEqual(len(table_json['0']['data']['sessions']), 2)
7c673cae
FG
100
101 # Now, induce a "force_open_sessions" event by exporting a dir
102 self.mount_a.run_shell(["mkdir", "bravo"])
9f95a23c
TL
103 self.mount_a.run_shell(["touch", "bravo/file_a"])
104 self.mount_b.run_shell(["touch", "bravo/file_b"])
105
106 self.fs.set_max_mds(2)
107 status = self.fs.wait_for_daemons()
7c673cae
FG
108
109 def get_omap_wrs():
81eedcae 110 return self.fs.rank_asok(['perf', 'dump', 'objecter'], rank=1, status=status)['objecter']['omap_wr']
7c673cae
FG
111
112 # Flush so that there are no dirty sessions on rank 1
81eedcae 113 self.fs.rank_asok(["flush", "journal"], rank=1, status=status)
7c673cae
FG
114
115 # Export so that we get a force_open to rank 1 for the two sessions from rank 0
116 initial_omap_wrs = get_omap_wrs()
81eedcae 117 self.fs.rank_asok(['export', 'dir', '/bravo', '1'], rank=0, status=status)
7c673cae
FG
118
119 # This is the critical (if rather subtle) check: that in the process of doing an export dir,
120 # we hit force_open_sessions, and as a result we end up writing out the sessionmap. There
121 # will be two sessions dirtied here, and because we have set keys_per_op to 1, we should see
122 # a single session get written out (the first of the two, triggered by the second getting marked
123 # dirty)
124 # The number of writes is two per session, because the header (sessionmap version) update and
11fdf7f2 125 # KV write both count. Also, multiply by 2 for each openfile table update.
7c673cae 126 self.wait_until_true(
11fdf7f2
TL
127 lambda: get_omap_wrs() - initial_omap_wrs == 2*2,
128 timeout=30 # Long enough for an export to get acked
7c673cae
FG
129 )
130
131 # Now end our sessions and check the backing sessionmap is updated correctly
132 self.mount_a.umount_wait()
133 self.mount_b.umount_wait()
134
135 # In-memory sessionmap check
81eedcae 136 self.assert_session_count(0, mds_id=self.fs.get_rank(status=status)['name'])
7c673cae
FG
137
138 # On-disk sessionmap check
81eedcae 139 self.fs.rank_asok(["flush", "journal"], rank=0, status=status)
7c673cae
FG
140 table_json = json.loads(self.fs.table_tool(["0", "show", "session"]))
141 log.info("SessionMap: {0}".format(json.dumps(table_json, indent=2)))
142 self.assertEqual(table_json['0']['result'], 0)
92f5a8d4 143 self.assertEqual(len(table_json['0']['data']['sessions']), 0)
7c673cae 144
7c673cae
FG
145 def _configure_auth(self, mount, id_name, mds_caps, osd_caps=None, mon_caps=None):
146 """
147 Set up auth credentials for a client mount, and write out the keyring
148 for the client to use.
149 """
150
151 if osd_caps is None:
152 osd_caps = "allow rw"
153
154 if mon_caps is None:
155 mon_caps = "allow r"
156
157 out = self.fs.mon_manager.raw_cluster_cmd(
158 "auth", "get-or-create", "client.{name}".format(name=id_name),
159 "mds", mds_caps,
160 "osd", osd_caps,
161 "mon", mon_caps
162 )
163 mount.client_id = id_name
9f95a23c 164 sudo_write_file(mount.client_remote, mount.get_keyring_path(), out)
7c673cae
FG
165 self.set_conf("client.{name}".format(name=id_name), "keyring", mount.get_keyring_path())
166
167 def test_session_reject(self):
168 if not isinstance(self.mount_a, FuseMount):
9f95a23c 169 self.skipTest("Requires FUSE client to inject client metadata")
7c673cae
FG
170
171 self.mount_a.run_shell(["mkdir", "foo"])
172 self.mount_a.run_shell(["mkdir", "foo/bar"])
173 self.mount_a.umount_wait()
174
175 # Mount B will be my rejected client
176 self.mount_b.umount_wait()
177
178 # Configure a client that is limited to /foo/bar
179 self._configure_auth(self.mount_b, "badguy", "allow rw path=/foo/bar")
180 # Check he can mount that dir and do IO
181 self.mount_b.mount(mount_path="/foo/bar")
182 self.mount_b.wait_until_mounted()
183 self.mount_b.create_destroy()
184 self.mount_b.umount_wait()
185
186 # Configure the client to claim that its mount point metadata is /baz
187 self.set_conf("client.badguy", "client_metadata", "root=/baz")
188 # Try to mount the client, see that it fails
11fdf7f2 189 with self.assert_cluster_log("client session with non-allowable root '/baz' denied"):
7c673cae
FG
190 with self.assertRaises(CommandFailedError):
191 self.mount_b.mount(mount_path="/foo/bar")
81eedcae
TL
192
193 def test_session_evict_blacklisted(self):
194 """
195 Check that mds evicts blacklisted client
196 """
197 if not isinstance(self.mount_a, FuseMount):
198 self.skipTest("Requires FUSE client to use is_blacklisted()")
199
200 self.fs.set_max_mds(2)
201 self.fs.wait_for_daemons()
202 status = self.fs.status()
203
204 self.mount_a.run_shell(["mkdir", "d0", "d1"])
205 self.mount_a.setfattr("d0", "ceph.dir.pin", "0")
206 self.mount_a.setfattr("d1", "ceph.dir.pin", "1")
207 self._wait_subtrees(status, 0, [('/d0', 0), ('/d1', 1)])
208
209 self.mount_a.run_shell(["touch", "d0/f0"])
210 self.mount_a.run_shell(["touch", "d1/f0"])
211 self.mount_b.run_shell(["touch", "d0/f1"])
212 self.mount_b.run_shell(["touch", "d1/f1"])
213
214 self.assert_session_count(2, mds_id=self.fs.get_rank(rank=0, status=status)['name'])
215 self.assert_session_count(2, mds_id=self.fs.get_rank(rank=1, status=status)['name'])
216
217 mount_a_client_id = self.mount_a.get_global_id()
218 self.fs.mds_asok(['session', 'evict', "%s" % mount_a_client_id],
219 mds_id=self.fs.get_rank(rank=0, status=status)['name'])
220 self.wait_until_true(lambda: self.mount_a.is_blacklisted(), timeout=30)
221
222 # 10 seconds should be enough for evicting client
223 time.sleep(10)
224 self.assert_session_count(1, mds_id=self.fs.get_rank(rank=0, status=status)['name'])
225 self.assert_session_count(1, mds_id=self.fs.get_rank(rank=1, status=status)['name'])
226
227 self.mount_a.kill_cleanup()
228 self.mount_a.mount()
229 self.mount_a.wait_until_mounted()