12 ensure_inside_container
,
13 ensure_outside_container
,
14 get_boxes_container_info
,
15 run_cephadm_shell_command
,
20 CEPH_IMAGE
= 'quay.ceph.io/ceph-ci/ceph:master'
21 BOX_IMAGE
= 'cephadm-box:latest'
23 # NOTE: this image tar is a trickeroo so cephadm won't pull the image everytime
24 # we deploy a cluster. Keep in mind that you'll be responsible of pulling the
25 # image yourself with `box cluster setup`
26 CEPH_IMAGE_TAR
= 'docker/ceph/image/quay.ceph.image.tar'
29 def remove_ceph_image_tar():
30 if os
.path
.exists(CEPH_IMAGE_TAR
):
31 os
.remove(CEPH_IMAGE_TAR
)
34 def cleanup_box() -> None:
36 remove_ceph_image_tar()
39 def image_exists(image_name
: str):
41 assert image_name
.find(':')
42 image_name
, tag
= image_name
.split(':')
43 images
= run_shell_command('docker image ls').split('\n')
49 print(image_name
, tag
)
50 if image
[IMAGE_NAME
] == image_name
and image
[TAG
] == tag
:
56 print('Getting ceph image')
57 run_shell_command(f
'docker pull {CEPH_IMAGE}')
59 run_shell_command(f
'docker build -t {CEPH_IMAGE} docker/ceph')
60 if not os
.path
.exists('docker/ceph/image'):
61 os
.mkdir('docker/ceph/image')
63 remove_ceph_image_tar()
65 run_shell_command(f
'docker save {CEPH_IMAGE} -o {CEPH_IMAGE_TAR}')
66 print('Ceph image added')
70 print('Getting box image')
71 run_shell_command('docker build -t cephadm-box -f Dockerfile .')
72 print('Box image added')
75 class Cluster(Target
):
76 _help
= 'Manage docker cephadm boxes'
77 actions
= ['bootstrap', 'start', 'down', 'list', 'sh', 'setup', 'cleanup']
80 self
.parser
.add_argument(
81 'action', choices
=Cluster
.actions
, help='Action to perform on the box'
83 self
.parser
.add_argument('--osds', type=int, default
=3, help='Number of osds')
85 self
.parser
.add_argument('--hosts', type=int, default
=2, help='Number of hosts')
86 self
.parser
.add_argument('--skip-deploy-osds', action
='store_true', help='skip deploy osd')
87 self
.parser
.add_argument('--skip-create-loop', action
='store_true', help='skip create loopback device')
88 self
.parser
.add_argument('--skip-monitoring-stack', action
='store_true', help='skip monitoring stack')
89 self
.parser
.add_argument('--skip-dashboard', action
='store_true', help='skip dashboard')
90 self
.parser
.add_argument('--expanded', action
='store_true', help='deploy 3 hosts and 3 osds')
92 @ensure_outside_container
97 @ensure_outside_container
101 @ensure_inside_container
103 print('Running bootstrap on seed')
104 cephadm_path
= os
.environ
.get('CEPHADM_PATH')
105 os
.symlink('/cephadm/cephadm', cephadm_path
)
107 'systemctl restart docker'
108 ) # restart to ensure docker is using daemon.json
110 st
= os
.stat(cephadm_path
)
111 os
.chmod(cephadm_path
, st
.st_mode | stat
.S_IEXEC
)
113 run_shell_command('docker load < /cephadm/box/docker/ceph/image/quay.ceph.image.tar')
114 # cephadm guid error because it sometimes tries to use quay.ceph.io/ceph-ci/ceph:<none>
115 # instead of master's tag
116 run_shell_command('export CEPH_SOURCE_FOLDER=/ceph')
117 run_shell_command('export CEPHADM_IMAGE=quay.ceph.io/ceph-ci/ceph:master')
119 'echo "export CEPHADM_IMAGE=quay.ceph.io/ceph-ci/ceph:master" >> ~/.bashrc'
124 extra_args
.append('--skip-pull')
126 # cephadm prints in warning, let's redirect it to the output so shell_command doesn't
128 extra_args
.append('2>&0')
130 extra_args
= ' '.join(extra_args
)
131 skip_monitoring_stack
= (
132 '--skip-monitoring-stack' if Config
.get('skip-monitoring-stack') else ''
134 skip_dashboard
= '--skip-dashboard' if Config
.get('skip-dashboard') else ''
136 fsid
= Config
.get('fsid')
137 config_folder
= Config
.get('config_folder')
138 config
= Config
.get('config')
139 keyring
= Config
.get('keyring')
140 if not os
.path
.exists(config_folder
):
141 os
.mkdir(config_folder
)
143 cephadm_bootstrap_command
= (
144 '$CEPHADM_PATH --verbose bootstrap '
145 '--mon-ip "$(hostname -i)" '
146 '--allow-fqdn-hostname '
147 '--initial-dashboard-password admin '
148 '--dashboard-password-noupdate '
149 '--shared_ceph_folder /ceph '
151 f
'--output-config {config} '
152 f
'--output-keyring {keyring} '
153 f
'--output-config {config} '
157 f
'{skip_monitoring_stack} '
161 print('Running cephadm bootstrap...')
162 run_shell_command(cephadm_bootstrap_command
)
163 print('Cephadm bootstrap complete')
165 run_shell_command('sudo vgchange --refresh')
166 run_shell_command('cephadm ls')
167 run_shell_command('ln -s /ceph/src/cephadm/box/box.py /usr/bin/box')
169 # NOTE: sometimes cephadm in the box takes a while to update the containers
170 # running in the cluster and it cannot deploy the osds. In this case
171 # run: box -v osd deploy --vg vg1 to deploy osds again.
172 run_cephadm_shell_command('ceph -s')
173 print('Bootstrap completed!')
175 @ensure_outside_container
177 osds
= Config
.get('osds')
178 hosts
= Config
.get('hosts')
180 # ensure boxes don't exist
181 run_shell_command('docker-compose down')
183 print('Checking docker images')
184 if not image_exists(CEPH_IMAGE
):
186 if not image_exists(BOX_IMAGE
):
189 if not Config
.get('skip_create_loop'):
190 print('Adding logical volumes (block devices) in loopback device...')
191 osd
.create_loopback_devices(osds
)
192 print(f
'Added {osds} logical volumes in a loopback device')
194 print('Starting containers')
196 dcflags
= '-f docker-compose.yml'
197 if not os
.path
.exists('/sys/fs/cgroup/cgroup.controllers'):
198 dcflags
+= ' -f docker-compose.cgroup1.yml'
199 run_shell_command(f
'docker-compose {dcflags} up --scale hosts={hosts} -d')
201 run_shell_command('sudo sysctl net.ipv4.conf.all.forwarding=1')
202 run_shell_command('sudo iptables -P FORWARD ACCEPT')
204 print('Seting up host ssh servers')
205 for h
in range(hosts
):
206 host
._setup
_ssh
(h
+ 1)
208 verbose
= '-v' if Config
.get('verbose') else ''
209 skip_deploy
= '--skip-deploy-osds' if Config
.get('skip-deploy-osds') else ''
210 skip_monitoring_stack
= (
211 '--skip-monitoring-stack' if Config
.get('skip-monitoring-stack') else ''
213 skip_dashboard
= '--skip-dashboard' if Config
.get('skip-dashboard') else ''
214 box_bootstrap_command
= (
215 f
'/cephadm/box/box.py {verbose} cluster bootstrap '
220 f
'{skip_monitoring_stack} '
222 run_dc_shell_command(box_bootstrap_command
, 1, 'seed')
224 info
= get_boxes_container_info()
226 hostnames
= info
['hostnames']
228 host
._copy
_cluster
_ssh
_key
(ips
)
230 expanded
= Config
.get('expanded')
232 host
._add
_hosts
(ips
, hostnames
)
234 if expanded
and not Config
.get('skip-deploy-osds'):
235 print('Deploying osds... This could take up to minutes')
236 osd
.deploy_osds_in_vg('vg1')
237 print('Osds deployed')
239 print('Bootstrap finished successfully')
241 @ensure_outside_container
243 run_shell_command('docker-compose down')
245 print('Successfully killed all boxes')
247 @ensure_outside_container
249 info
= get_boxes_container_info(with_seed
=True)
250 for i
in range(info
['size']):
252 name
= info
['container_names'][i
]
253 hostname
= info
['hostnames'][i
]
254 print(f
'{name} \t{ip} \t{hostname}')
256 @ensure_outside_container
258 # we need verbose to see the prompt after running shell command
259 Config
.set('verbose', True)
261 run_shell_command('docker-compose exec seed bash')
272 parser
= argparse
.ArgumentParser()
274 '-v', action
='store_true', dest
='verbose', help='be more verbose'
277 subparsers
= parser
.add_subparsers()
278 target_instances
= {}
279 for name
, target
in targets
.items():
280 target_instances
[name
] = target(None, subparsers
)
282 for count
, arg
in enumerate(sys
.argv
, 1):
284 instance
= target_instances
[arg
]
285 if hasattr(instance
, 'main'):
286 instance
.argv
= sys
.argv
[count
:]
288 args
= parser
.parse_args()
289 Config
.add_args(vars(args
))
296 if __name__
== '__main__':