]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/cephadm/services/ingress.py
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / pybind / mgr / cephadm / services / ingress.py
CommitLineData
f67539c2
TL
1import ipaddress
2import logging
3import random
4import string
5from typing import List, Dict, Any, Tuple, cast, Optional
6
7from ceph.deployment.service_spec import IngressSpec
8from cephadm.utils import resolve_ip
9
10from cephadm.services.cephadmservice import CephadmDaemonDeploySpec, CephService
11
12logger = logging.getLogger(__name__)
13
14
15class IngressService(CephService):
16 TYPE = 'ingress'
17
18 def primary_daemon_type(self) -> str:
19 return 'haproxy'
20
21 def per_host_daemon_type(self) -> Optional[str]:
22 return 'keepalived'
23
24 def prepare_create(
25 self,
26 daemon_spec: CephadmDaemonDeploySpec,
27 ) -> CephadmDaemonDeploySpec:
28 if daemon_spec.daemon_type == 'haproxy':
29 return self.haproxy_prepare_create(daemon_spec)
30 if daemon_spec.daemon_type == 'keepalived':
31 return self.keepalived_prepare_create(daemon_spec)
32 assert False, "unexpected daemon type"
33
34 def generate_config(
35 self,
36 daemon_spec: CephadmDaemonDeploySpec
37 ) -> Tuple[Dict[str, Any], List[str]]:
38 if daemon_spec.daemon_type == 'haproxy':
39 return self.haproxy_generate_config(daemon_spec)
40 else:
41 return self.keepalived_generate_config(daemon_spec)
42 assert False, "unexpected daemon type"
43
44 def haproxy_prepare_create(
45 self,
46 daemon_spec: CephadmDaemonDeploySpec,
47 ) -> CephadmDaemonDeploySpec:
48 assert daemon_spec.daemon_type == 'haproxy'
49
50 daemon_id = daemon_spec.daemon_id
51 host = daemon_spec.host
52 spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec)
53
54 logger.debug('prepare_create haproxy.%s on host %s with spec %s' % (
55 daemon_id, host, spec))
56
57 daemon_spec.final_config, daemon_spec.deps = self.haproxy_generate_config(daemon_spec)
58
59 return daemon_spec
60
61 def haproxy_generate_config(
62 self,
63 daemon_spec: CephadmDaemonDeploySpec,
64 ) -> Tuple[Dict[str, Any], List[str]]:
65 spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec)
66 assert spec.backend_service
67 daemons = self.mgr.cache.get_daemons_by_service(spec.backend_service)
68 deps = [d.name() for d in daemons]
69
70 # generate password?
71 pw_key = f'{spec.service_name()}/monitor_password'
72 password = self.mgr.get_store(pw_key)
73 if password is None:
74 if not spec.monitor_password:
75 password = ''.join(random.choice(string.ascii_lowercase) for _ in range(20))
76 self.mgr.set_store(pw_key, password)
77 else:
78 if spec.monitor_password:
79 self.mgr.set_store(pw_key, None)
80 if spec.monitor_password:
81 password = spec.monitor_password
82
83 haproxy_conf = self.mgr.template.render(
84 'services/ingress/haproxy.cfg.j2',
85 {
86 'spec': spec,
87 'servers': [
88 {
89 'name': d.name(),
90 'ip': d.ip or resolve_ip(str(d.hostname)),
91 'port': d.ports[0],
92 } for d in daemons if d.ports
93 ],
94 'user': spec.monitor_user or 'admin',
95 'password': password,
96 'ip': daemon_spec.ip or '*',
97 'frontend_port': daemon_spec.ports[0] if daemon_spec.ports else spec.frontend_port,
98 'monitor_port': daemon_spec.ports[1] if daemon_spec.ports else spec.monitor_port,
99 }
100 )
101 config_files = {
102 'files': {
103 "haproxy.cfg": haproxy_conf,
104 }
105 }
106 if spec.ssl_cert:
107 ssl_cert = spec.ssl_cert
108 if isinstance(ssl_cert, list):
109 ssl_cert = '\n'.join(ssl_cert)
110 config_files['files']['haproxy.pem'] = ssl_cert
111
112 return config_files, sorted(deps)
113
114 def keepalived_prepare_create(
115 self,
116 daemon_spec: CephadmDaemonDeploySpec,
117 ) -> CephadmDaemonDeploySpec:
118 assert daemon_spec.daemon_type == 'keepalived'
119
120 daemon_id = daemon_spec.daemon_id
121 host = daemon_spec.host
122 spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec)
123
124 logger.debug('prepare_create keepalived.%s on host %s with spec %s' % (
125 daemon_id, host, spec))
126
127 daemon_spec.final_config, daemon_spec.deps = self.keepalived_generate_config(daemon_spec)
128
129 return daemon_spec
130
131 def keepalived_generate_config(
132 self,
133 daemon_spec: CephadmDaemonDeploySpec,
134 ) -> Tuple[Dict[str, Any], List[str]]:
135 spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec)
136 assert spec.backend_service
137
138 # generate password?
139 pw_key = f'{spec.service_name()}/keepalived_password'
140 password = self.mgr.get_store(pw_key)
141 if password is None:
142 if not spec.keepalived_password:
143 password = ''.join(random.choice(string.ascii_lowercase) for _ in range(20))
144 self.mgr.set_store(pw_key, password)
145 else:
146 if spec.keepalived_password:
147 self.mgr.set_store(pw_key, None)
148 if spec.keepalived_password:
149 password = spec.keepalived_password
150
151 daemons = self.mgr.cache.get_daemons_by_service(spec.service_name())
152 deps = sorted([d.name() for d in daemons if d.daemon_type == 'haproxy'])
153
154 host = daemon_spec.host
155 hosts = sorted(list(set([str(d.hostname) for d in daemons])))
156
157 # interface
158 bare_ip = str(spec.virtual_ip).split('/')[0]
159 interface = None
160 for subnet, ifaces in self.mgr.cache.networks.get(host, {}).items():
161 if ifaces and ipaddress.ip_address(bare_ip) in ipaddress.ip_network(subnet):
162 interface = list(ifaces.keys())[0]
163 logger.info(
164 f'{bare_ip} is in {subnet} on {host} interface {interface}'
165 )
166 break
167 if not interface and spec.networks:
168 # hmm, try spec.networks
169 for subnet, ifaces in self.mgr.cache.networks.get(host, {}).items():
170 if subnet in spec.networks:
171 interface = list(ifaces.keys())[0]
172 logger.info(
173 f'{spec.virtual_ip} will be configured on {host} interface '
174 f'{interface} (which has guiding subnet {subnet})'
175 )
176 break
177 if not interface:
178 interface = 'eth0'
179
180 # script to monitor health
181 script = '/usr/bin/false'
182 for d in daemons:
183 if d.hostname == host:
184 if d.daemon_type == 'haproxy':
185 assert d.ports
186 port = d.ports[1] # monitoring port
187 script = f'/usr/bin/curl http://{d.ip or "localhost"}:{port}/health'
188 assert script
189
190 # set state. first host in placement is master all others backups
191 state = 'BACKUP'
192 if hosts[0] == host:
193 state = 'MASTER'
194
195 # remove host, daemon is being deployed on from hosts list for
196 # other_ips in conf file and converter to ips
197 hosts.remove(host)
198 other_ips = [resolve_ip(h) for h in hosts]
199
200 keepalived_conf = self.mgr.template.render(
201 'services/ingress/keepalived.conf.j2',
202 {
203 'spec': spec,
204 'script': script,
205 'password': password,
206 'interface': interface,
207 'state': state,
208 'other_ips': other_ips,
209 'host_ip': resolve_ip(host),
210 }
211 )
212
213 config_file = {
214 'files': {
215 "keepalived.conf": keepalived_conf,
216 }
217 }
218
219 return config_file, deps