]>
Commit | Line | Data |
---|---|---|
9f95a23c | 1 | import fnmatch |
20effc67 TL |
2 | import asyncio |
3 | import sys | |
4 | from tempfile import NamedTemporaryFile | |
f6b5b4d7 TL |
5 | from contextlib import contextmanager |
6 | ||
7 | from ceph.deployment.service_spec import PlacementSpec, ServiceSpec | |
adb31ebb | 8 | from ceph.utils import datetime_to_str, datetime_now |
20effc67 | 9 | from cephadm.serve import CephadmServe, cephadmNoImage |
f6b5b4d7 | 10 | |
9f95a23c | 11 | try: |
20effc67 | 12 | from typing import Any, Iterator, List, Callable, Dict |
9f95a23c TL |
13 | except ImportError: |
14 | pass | |
9f95a23c TL |
15 | |
16 | from cephadm import CephadmOrchestrator | |
20effc67 | 17 | from orchestrator import raise_if_exception, OrchResult, HostSpec, DaemonDescriptionStatus |
9f95a23c TL |
18 | from tests import mock |
19 | ||
20 | ||
20effc67 TL |
21 | def async_side_effect(result): |
22 | async def side_effect(*args, **kwargs): | |
23 | return result | |
24 | return side_effect | |
25 | ||
26 | ||
9f95a23c TL |
27 | def get_ceph_option(_, key): |
28 | return __file__ | |
29 | ||
30 | ||
31 | def _run_cephadm(ret): | |
20effc67 | 32 | async def foo(s, host, entity, cmd, e, **kwargs): |
b3b6e05e TL |
33 | if cmd == 'gather-facts': |
34 | return '{}', '', 0 | |
f91f0fd5 | 35 | return [ret], '', 0 |
9f95a23c TL |
36 | return foo |
37 | ||
38 | ||
39 | def match_glob(val, pat): | |
40 | ok = fnmatch.fnmatchcase(val, pat) | |
41 | if not ok: | |
42 | assert pat in val | |
43 | ||
44 | ||
20effc67 TL |
45 | class MockEventLoopThread: |
46 | def get_result(self, coro): | |
47 | if sys.version_info >= (3, 7): | |
48 | return asyncio.run(coro) | |
49 | ||
50 | loop = asyncio.new_event_loop() | |
51 | asyncio.set_event_loop(loop) | |
52 | try: | |
53 | return loop.run_until_complete(coro) | |
54 | finally: | |
55 | loop.close() | |
56 | asyncio.set_event_loop(None) | |
57 | ||
58 | ||
59 | def receive_agent_metadata(m: CephadmOrchestrator, host: str, ops: List[str] = None) -> None: | |
60 | to_update: Dict[str, Callable[[str, Any], None]] = { | |
61 | 'ls': m._process_ls_output, | |
62 | 'gather-facts': m.cache.update_host_facts, | |
63 | 'list-networks': m.cache.update_host_networks, | |
64 | } | |
65 | if ops: | |
66 | for op in ops: | |
67 | out = m.wait_async(CephadmServe(m)._run_cephadm_json(host, cephadmNoImage, op, [])) | |
68 | to_update[op](host, out) | |
69 | m.cache.last_daemon_update[host] = datetime_now() | |
70 | m.cache.last_facts_update[host] = datetime_now() | |
71 | m.cache.last_network_update[host] = datetime_now() | |
72 | m.cache.metadata_up_to_date[host] = True | |
73 | ||
74 | ||
75 | def receive_agent_metadata_all_hosts(m: CephadmOrchestrator) -> None: | |
76 | for host in m.cache.get_hosts(): | |
77 | receive_agent_metadata(m, host) | |
78 | ||
79 | ||
f6b5b4d7 TL |
80 | @contextmanager |
81 | def with_cephadm_module(module_options=None, store=None): | |
82 | """ | |
83 | :param module_options: Set opts as if they were set before module.__init__ is called | |
84 | :param store: Set the store before module.__init__ is called | |
85 | """ | |
9f95a23c | 86 | with mock.patch("cephadm.module.CephadmOrchestrator.get_ceph_option", get_ceph_option),\ |
f6b5b4d7 TL |
87 | mock.patch("cephadm.services.osd.RemoveUtil._run_mon_cmd"), \ |
88 | mock.patch("cephadm.module.CephadmOrchestrator.get_osdmap"), \ | |
20effc67 TL |
89 | mock.patch("cephadm.module.CephadmOrchestrator.remote"), \ |
90 | mock.patch("cephadm.agent.CephadmAgentHelpers._request_agent_acks"), \ | |
91 | mock.patch("cephadm.agent.CephadmAgentHelpers._apply_agent", return_value=False), \ | |
92 | mock.patch("cephadm.agent.CephadmAgentHelpers._agent_down", return_value=False), \ | |
93 | mock.patch('cephadm.agent.CherryPyThread.run'): | |
9f95a23c | 94 | |
f91f0fd5 | 95 | m = CephadmOrchestrator.__new__(CephadmOrchestrator) |
f6b5b4d7 TL |
96 | if module_options is not None: |
97 | for k, v in module_options.items(): | |
98 | m._ceph_set_module_option('cephadm', k, v) | |
99 | if store is None: | |
100 | store = {} | |
101 | if '_ceph_get/mon_map' not in store: | |
102 | m.mock_store_set('_ceph_get', 'mon_map', { | |
adb31ebb | 103 | 'modified': datetime_to_str(datetime_now()), |
f6b5b4d7 TL |
104 | 'fsid': 'foobar', |
105 | }) | |
a4b75251 TL |
106 | if '_ceph_get/mgr_map' not in store: |
107 | m.mock_store_set('_ceph_get', 'mgr_map', { | |
108 | 'services': { | |
109 | 'dashboard': 'http://[::1]:8080', | |
110 | 'prometheus': 'http://[::1]:8081' | |
111 | }, | |
112 | 'modules': ['dashboard', 'prometheus'], | |
113 | }) | |
f6b5b4d7 TL |
114 | for k, v in store.items(): |
115 | m._ceph_set_store(k, v) | |
116 | ||
9f95a23c TL |
117 | m.__init__('cephadm', 0, 0) |
118 | m._cluster_fsid = "fsid" | |
20effc67 TL |
119 | |
120 | m.event_loop = MockEventLoopThread() | |
121 | m.tkey = NamedTemporaryFile(prefix='test-cephadm-identity-') | |
122 | ||
9f95a23c TL |
123 | yield m |
124 | ||
125 | ||
126 | def wait(m, c): | |
f67539c2 TL |
127 | # type: (CephadmOrchestrator, OrchResult) -> Any |
128 | return raise_if_exception(c) | |
f6b5b4d7 TL |
129 | |
130 | ||
131 | @contextmanager | |
20effc67 | 132 | def with_host(m: CephadmOrchestrator, name, addr='1::4', refresh_hosts=True, rm_with_force=True): |
b3b6e05e TL |
133 | with mock.patch("cephadm.utils.resolve_ip", return_value=addr): |
134 | wait(m, m.add_host(HostSpec(hostname=name))) | |
135 | if refresh_hosts: | |
136 | CephadmServe(m)._refresh_hosts_and_daemons() | |
20effc67 | 137 | receive_agent_metadata(m, name) |
b3b6e05e | 138 | yield |
20effc67 | 139 | wait(m, m.remove_host(name, force=rm_with_force)) |
f6b5b4d7 TL |
140 | |
141 | ||
f67539c2 TL |
142 | def assert_rm_service(cephadm: CephadmOrchestrator, srv_name): |
143 | mon_or_mgr = cephadm.spec_store[srv_name].spec.service_type in ('mon', 'mgr') | |
144 | if mon_or_mgr: | |
145 | assert 'Unable' in wait(cephadm, cephadm.remove_service(srv_name)) | |
146 | return | |
f6b5b4d7 | 147 | assert wait(cephadm, cephadm.remove_service(srv_name)) == f'Removed service {srv_name}' |
f67539c2 TL |
148 | assert cephadm.spec_store[srv_name].deleted is not None |
149 | CephadmServe(cephadm)._check_daemons() | |
f91f0fd5 | 150 | CephadmServe(cephadm)._apply_all_services() |
f67539c2 TL |
151 | assert cephadm.spec_store[srv_name].deleted |
152 | unmanaged = cephadm.spec_store[srv_name].spec.unmanaged | |
153 | CephadmServe(cephadm)._purge_deleted_services() | |
154 | if not unmanaged: # cause then we're not deleting daemons | |
155 | assert srv_name not in cephadm.spec_store, f'{cephadm.spec_store[srv_name]!r}' | |
f6b5b4d7 TL |
156 | |
157 | ||
158 | @contextmanager | |
20effc67 | 159 | def with_service(cephadm_module: CephadmOrchestrator, spec: ServiceSpec, meth=None, host: str = '', status_running=False) -> Iterator[List[str]]: |
f67539c2 | 160 | if spec.placement.is_empty() and host: |
f6b5b4d7 | 161 | spec.placement = PlacementSpec(hosts=[host], count=1) |
f67539c2 TL |
162 | if meth is not None: |
163 | c = meth(cephadm_module, spec) | |
164 | assert wait(cephadm_module, c) == f'Scheduled {spec.service_name()} update...' | |
165 | else: | |
166 | c = cephadm_module.apply([spec]) | |
167 | assert wait(cephadm_module, c) == [f'Scheduled {spec.service_name()} update...'] | |
168 | ||
f6b5b4d7 TL |
169 | specs = [d.spec for d in wait(cephadm_module, cephadm_module.describe_service())] |
170 | assert spec in specs | |
171 | ||
f91f0fd5 | 172 | CephadmServe(cephadm_module)._apply_all_services() |
f6b5b4d7 | 173 | |
20effc67 TL |
174 | if status_running: |
175 | make_daemons_running(cephadm_module, spec.service_name()) | |
176 | ||
f6b5b4d7 | 177 | dds = wait(cephadm_module, cephadm_module.list_daemons()) |
f91f0fd5 | 178 | own_dds = [dd for dd in dds if dd.service_name() == spec.service_name()] |
20effc67 | 179 | if host and spec.service_type != 'osd': |
f67539c2 | 180 | assert own_dds |
f6b5b4d7 | 181 | |
f91f0fd5 | 182 | yield [dd.name() for dd in own_dds] |
f6b5b4d7 | 183 | |
f91f0fd5 | 184 | assert_rm_service(cephadm_module, spec.service_name()) |
f67539c2 TL |
185 | |
186 | ||
20effc67 TL |
187 | def make_daemons_running(cephadm_module, service_name): |
188 | own_dds = cephadm_module.cache.get_daemons_by_service(service_name) | |
189 | for dd in own_dds: | |
190 | dd.status = DaemonDescriptionStatus.running # We're changing the reference |