blob: 5fe143a080e3d0b37492a93e71da8ff275e9cffe [file] [log] [blame]
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +02001# Copyright 2019 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import os
16import netaddr
17import yaml
18
19from devops.helpers import helpers
20from devops.helpers.helpers import ssh_client
21from retry import retry
22
23from cached_property import cached_property
24
25from heatclient import client as heatclient
26from heatclient import exc as heat_exceptions
27from heatclient.common import template_utils
28from keystoneauth1.identity import v3 as keystone_v3
29from keystoneauth1 import session as keystone_session
30
31import requests
32from requests.packages.urllib3.exceptions import InsecureRequestWarning
33
34from oslo_config import cfg
35from paramiko.ssh_exception import (
36 AuthenticationException,
37 BadAuthenticationType)
38
39from tcp_tests import settings
40from tcp_tests import settings_oslo
41from tcp_tests.helpers import exceptions
42from tcp_tests import logger
43
44LOG = logger.logger
45
46EXPECTED_STACK_STATUS = "CREATE_COMPLETE"
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +030047BAD_STACK_STATUSES = ["CREATE_FAILED", "DELETE_FAILED"]
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +020048
49# Disable multiple notifications like:
50# "InsecureRequestWarning: Unverified HTTPS request is being made."
51requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
52
53
54class EnvironmentManagerHeat(object):
55 """Class-helper for creating VMs via devops environments"""
56
57 __config = None
58
59 # Do not use self.__heatclient directly! Use properties
60 # for necessary resources with catching HTTPUnauthorized exception
61 __heatclient = None
62
63 def __init__(self, config=None):
64 """Create/connect to the Heat stack with test environment
65
66 :param config: oslo.config object
67 :param config.hardware.heat_version: Heat version
68 :param config.hardware.os_auth_url: OS auth URL to access heat
69 :param config.hardware.os_username: OS username
70 :param config.hardware.os_password: OS password
71 :param config.hardware.os_project_name: OS tenant name
72 """
73 self.__config = config
74
75 if not self.__config.hardware.heat_stack_name:
76 self.__config.hardware.heat_stack_name = settings.ENV_NAME
77
78 self.__init_heatclient()
79
80 try:
81 stack_status = self._current_stack.stack_status
82 if stack_status != EXPECTED_STACK_STATUS:
83 raise exceptions.EnvironmentWrongStatus(
84 self.__config.hardware.heat_stack_name,
85 EXPECTED_STACK_STATUS,
86 stack_status
87 )
88 LOG.info("Heat stack '{0}' already exists".format(
89 self.__config.hardware.heat_stack_name))
90 except heat_exceptions.HTTPNotFound:
91 self._create_environment()
92 LOG.info("Heat stack '{0}' created".format(
93 self.__config.hardware.heat_stack_name))
94
95 self.set_address_pools_config()
96 self.set_dhcp_ranges_config()
97
98 @cached_property
99 def _keystone_session(self):
100 keystone_auth = keystone_v3.Password(
101 auth_url=settings.OS_AUTH_URL,
102 username=settings.OS_USERNAME,
103 password=settings.OS_PASSWORD,
104 project_name=settings.OS_PROJECT_NAME,
Dennis Dmitrievc902ad82019-04-12 13:41:30 +0300105 user_domain_name=settings.OS_USER_DOMAIN_NAME,
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200106 project_domain_name='Default')
107 return keystone_session.Session(auth=keystone_auth, verify=False)
108
109 def __init_heatclient(self):
110 token = self._keystone_session.get_token()
111 endpoint_url = self._keystone_session.get_endpoint(
112 service_type='orchestration', endpoint_type='publicURL')
113 self.__heatclient = heatclient.Client(
114 version=settings.OS_HEAT_VERSION, endpoint=endpoint_url,
115 token=token, insecure=True)
116
117 @property
118 def _current_stack(self):
119 return self.__stacks.get(
120 self.__config.hardware.heat_stack_name)
121
122 @property
123 def __stacks(self):
124 try:
125 return self.__heatclient.stacks
126 except heat_exceptions.HTTPUnauthorized:
127 LOG.warning("Authorization token outdated, refreshing")
128 self.__init_heatclient()
129 return self.__heatclient.stacks
130
131 @property
132 def __resources(self):
133 try:
134 return self.__heatclient.resources
135 except heat_exceptions.HTTPUnauthorized:
136 LOG.warning("Authorization token outdated, refreshing")
137 self.__init_heatclient()
138 return self.__heatclient.resources
139
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300140 def __get_stack_parent(self, stack_id, stacks):
141 """Find the parent ID of the specified stack_id in the 'stacks' list"""
142 for stack in stacks:
143 if stack_id == stack.id:
144 if stack.parent:
Oleksii Butenko9cd9cbf2019-08-01 18:30:16 +0300145 try:
146 return self.__get_stack_parent(stack.parent, stacks)
147 except Exception:
148 return stack.id
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300149 else:
150 return stack.id
151 raise Exception("stack with ID {} not found!".format(stack_id))
152
153 @property
154 def __nested_resources(self):
155 resources = []
156 stacks = [s for s in self.__stacks.list(show_nested=True)]
157 current_stack_id = self._current_stack.id
158 for stack in stacks:
159 parent_stack_id = self.__get_stack_parent(stack.id, stacks)
160 if parent_stack_id == current_stack_id:
161 # Add resources to list
162 LOG.info("Get resources from stack {0}"
163 .format(stack.stack_name))
164 resources.extend([
165 res for res in self.__resources.list(stack.id)
166 ])
167 LOG.info("Found {0} resources".format(len(resources)))
168 return resources
169
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200170 def _get_resources_by_type(self, resource_type):
171 res = []
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300172 for item in self.__nested_resources:
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200173 if item.resource_type == resource_type:
174 resource = self.__resources.get(
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300175 item.stack_name,
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200176 item.resource_name)
177 res.append(resource)
178 return res
179
180 @cached_property
181 def _nodes(self):
182 """Get list of nodenames from heat
183
184 Returns list of dicts.
185 Example:
186 - name: cfg01
187 roles:
188 - salt_master
189 addresses: # Optional. May be an empty dict
190 admin-pool01: p.p.p.202
191 - name: ctl01
192 roles:
193 - salt_minion
194 - openstack_controller
195 - openstack_messaging
196 - openstack_database
197 addresses: {} # Optional. May be an empty dict
198
199 'name': taken from heat template resource's ['name'] parameter
200 'roles': a list taken from resource's ['metadata']['roles'] parameter
201 """
202 address_pools = self._address_pools
203 nodes = []
204 for heat_node in self._get_resources_by_type("OS::Nova::Server"):
205 # addresses will have the following dict structure:
206 # {'admin-pool01': <floating_ip1>,
207 # 'private-pool01': <floating_ip2>,
208 # 'external-pool01': <floating_ip3>
209 # }
210 # , where key is one of roles from OS::Neutron::Subnet,
211 # and value is a floating IP associated to the fixed IP
212 # in this subnet (if exists).
213 # If no floating IPs associated to the server,
214 # then addresses will be an empty list.
215 addresses = {}
216 for network in heat_node.attributes['addresses']:
217 fixed = None
218 floating = None
219 for address in heat_node.attributes['addresses'][network]:
220 addr_type = address['OS-EXT-IPS:type']
221 if addr_type == 'fixed':
222 fixed = address['addr']
223 elif addr_type == 'floating':
224 floating = address['addr']
225 else:
226 LOG.error("Unexpected OS-EXT-IPS:type={0} "
227 "in node '{1}' for network '{2}'"
228 .format(addr_type,
229 heat_node.attributes['name'],
230 network))
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300231 if fixed is None and floating is None:
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200232 LOG.error("Unable to determine the correct IP address "
233 "in node '{0}' for network '{1}'"
234 .format(heat_node.attributes['name'], network))
235 continue
236 # Check which address pool has the fixed address, and set
237 # the floating address as the access to this address pool.
238 for address_pool in address_pools:
239 pool_net = netaddr.IPNetwork(address_pool['cidr'])
240 if fixed in pool_net:
241 for role in address_pool['roles']:
Dennis Dmitrievc902ad82019-04-12 13:41:30 +0300242 # addresses[role] = floating
243 # Use fixed addresses for SSH access
244 addresses[role] = fixed
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300245 if 'metadata' not in heat_node.attributes or \
246 'roles' not in heat_node.attributes['metadata']:
247 raise Exception("Node {} doesn't have metadata:roles:[...,...]"
248 .format(heat_node.attributes['name']))
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200249
250 nodes.append({
251 'name': heat_node.attributes['name'],
252 'roles': yaml.load(heat_node.attributes['metadata']['roles']),
253 'addresses': addresses,
254 })
255 return nodes
256
257 @cached_property
258 def _address_pools(self):
259 """Get address pools from subnets OS::Neutron::Subnet
260
261 Returns list of dicts.
262 Example:
263 - roles:
264 - admin-pool01
265 cidr: x.x.x.x/y
266 start: x.x.x.2
267 end: x.x.x.254
268 gateway: x.x.x.1 # or None
269 """
270 pools = []
271 for heat_subnet in self._get_resources_by_type("OS::Neutron::Subnet"):
272 pools.append({
273 'roles': heat_subnet.attributes['tags'],
274 'cidr': heat_subnet.attributes['cidr'],
275 'gateway': heat_subnet.attributes['gateway_ip'],
276 'start': heat_subnet.attributes[
277 'allocation_pools'][0]['start'],
278 'end': heat_subnet.attributes['allocation_pools'][0]['end'],
279 })
280 return pools
281
282 def _get_nodes_by_roles(self, roles=None):
283 nodes = []
284 if roles is None:
285 return self._nodes
286
287 for node in self._nodes:
288 if set(node['roles']).intersection(set(roles)):
289 nodes.append(node)
290 return nodes
291
292 def get_ssh_data(self, roles=None):
293 """Generate ssh config for Underlay
294
295 :param roles: list of strings
296 """
297 if roles is None:
298 raise Exception("No roles specified for the environment!")
299
300 config_ssh = []
301 for d_node in self._get_nodes_by_roles(roles=roles):
302 for pool_name in d_node['addresses']:
303 ssh_data = {
304 'node_name': d_node['name'],
305 'minion_id': d_node['name'],
306 'roles': d_node['roles'],
307 'address_pool': pool_name,
308 'host': d_node['addresses'][pool_name],
309 'login': settings.SSH_NODE_CREDENTIALS['login'],
310 'password': settings.SSH_NODE_CREDENTIALS['password'],
311 'keys': [k['private']
312 for k in self.__config.underlay.ssh_keys]
313 }
314 config_ssh.append(ssh_data)
315 return config_ssh
316
317 def _get_resources_with_wrong_status(self):
318 res = []
Dennis Dmitriev4015adc2019-04-15 18:33:44 +0300319 for item in self.__nested_resources:
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200320 if item.resource_status in BAD_STACK_STATUSES:
321 res.append({
322 'resource_name': item.resource_name,
323 'resource_status': item.resource_status,
324 'resource_status_reason': item.resource_status_reason,
325 'resource_type': item.resource_type
326 })
327 wrong_resources = '\n'.join([
328 "*** Heat stack resource '{0}' ({1}) has wrong status '{2}': {3}"
329 .format(item['resource_name'],
330 item['resource_type'],
331 item['resource_status'],
332 item['resource_status_reason'])
333 for item in res
334 ])
335 return wrong_resources
336
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +0300337 def wait_of_stack_status(self, status, delay=30, tries=60,
338 wait_for_delete=False):
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200339
340 @retry(exceptions.EnvironmentWrongStatus, delay=delay, tries=tries)
341 def wait():
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +0300342 try:
343 st = self._current_stack.stack_status
344 except heat_exceptions.HTTPNotFound as ex:
345 if wait_for_delete is True:
346 return
347 raise ex
Denis V. Meltsaykine68b4452021-01-19 00:48:11 +0100348 except heat_exceptions.HTTPException as ex:
349 # tolerate HTTP timeouts from Heat
350 if ex.code == 504:
351 raise exceptions.EnvironmentWrongStatus(
352 self.__config.hardware.heat_stack_name,
353 status,
354 "Heat API Temporary Unavailable"
355 )
356 else:
357 raise ex
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200358 if st == status:
359 return
360 elif st in BAD_STACK_STATUSES:
361 wrong_resources = self._get_resources_with_wrong_status()
362 raise exceptions.EnvironmentBadStatus(
363 self.__config.hardware.heat_stack_name,
364 status,
365 st,
366 wrong_resources
367 )
368 else:
369 LOG.info("Stack {0} status: {1}".format(
370 self.__config.hardware.heat_stack_name, st))
371 raise exceptions.EnvironmentWrongStatus(
372 self.__config.hardware.heat_stack_name,
373 status,
374 st
375 )
376 LOG.info("Waiting for stack '{0}' status <{1}>".format(
377 self.__config.hardware.heat_stack_name, status))
378 wait()
379
380 def revert_snapshot(self, name):
381 """Revert snapshot by name
382
383 - Revert the heat snapshot in the environment
384 - Try to reload 'config' object from a file 'config_<name>.ini'
385 If the file not found, then pass with defaults.
386 - Set <name> as the current state of the environment after reload
387
388 :param name: string
389 """
390 LOG.info("Reading INI config (without reverting env to snapshot) "
391 "named '{0}'".format(name))
392
393 try:
394 test_config_path = self._get_snapshot_config_name(name)
395 settings_oslo.reload_snapshot_config(self.__config,
396 test_config_path)
397 except cfg.ConfigFilesNotFoundError as conf_err:
398 LOG.error("Config file(s) {0} not found!".format(
399 conf_err.config_files))
400
401 self.__config.hardware.current_snapshot = name
402
403 def create_snapshot(self, name, *args, **kwargs):
404 """Create named snapshot of current env.
405
406 - Create a snapshot for the environment
407 - Save 'config' object to a file 'config_<name>.ini'
408
409 :name: string
410 """
Dennis Dmitrievfa1774a2019-05-28 15:27:44 +0300411 if not settings.MAKE_SNAPSHOT_STAGES:
412 msg = ("[ SKIP snapshot '{0}' because MAKE_SNAPSHOT_STAGES=false ]"
413 .format(name))
414 LOG.info("\n\n{0}\n{1}".format(msg, '*' * len(msg)))
415 return
416
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200417 LOG.info("Store INI config (without env snapshot) named '{0}'"
418 .format(name))
419 self.__config.hardware.current_snapshot = name
420 settings_oslo.save_config(self.__config,
421 name,
422 self.__config.hardware.heat_stack_name)
423
424 def _get_snapshot_config_name(self, snapshot_name):
425 """Get config name for the environment"""
426 env_name = self.__config.hardware.heat_stack_name
427 if env_name is None:
428 env_name = 'config'
429 test_config_path = os.path.join(
430 settings.LOGS_DIR, '{0}_{1}.ini'.format(env_name, snapshot_name))
431 return test_config_path
432
433 def has_snapshot(self, name):
434 # Heat doesn't support live snapshots, so just
435 # check if an INI file was created for this environment,
436 # assuming that the environment has the configuration
437 # described in this INI.
438 return self.has_snapshot_config(name)
439
440 def has_snapshot_config(self, name):
441 test_config_path = self._get_snapshot_config_name(name)
442 return os.path.isfile(test_config_path)
443
444 def start(self, underlay_node_roles, timeout=480):
445 """Start environment"""
446 LOG.warning("HEAT Manager doesn't support start environment feature. "
447 "Waiting for finish the bootstrap process on the nodes "
448 "with accessible SSH")
449
450 check_cloudinit_started = '[ -f /is_cloud_init_started ]'
451 check_cloudinit_finished = ('[ -f /is_cloud_init_finished ] || '
452 '[ -f /var/log/mcp/.bootstrap_done ]')
453 check_cloudinit_failed = 'cat /is_cloud_init_failed'
454 passed = {}
455 for node in self._get_nodes_by_roles(roles=underlay_node_roles):
456
457 try:
458 node_ip = self.node_ip(node)
459 except exceptions.EnvironmentNodeAccessError:
460 LOG.warning("Node {0} doesn't have accessible IP address"
461 ", skipping".format(node['name']))
462 continue
463
464 LOG.info("Waiting for SSH on node '{0}' / {1} ...".format(
465 node['name'], node_ip))
466
467 def _ssh_check(host,
468 port,
469 username=settings.SSH_NODE_CREDENTIALS['login'],
470 password=settings.SSH_NODE_CREDENTIALS['password'],
471 timeout=0):
472 try:
473 ssh = ssh_client.SSHClient(
474 host=host, port=port,
475 auth=ssh_client.SSHAuth(
476 username=username,
477 password=password))
478
479 # If '/is_cloud_init_started' exists, then wait for
480 # the flag /is_cloud_init_finished
481 if ssh.execute(check_cloudinit_started)['exit_code'] == 0:
482 result = ssh.execute(check_cloudinit_failed)
483 if result['exit_code'] == 0:
484 raise exceptions.EnvironmentNodeIsNotStarted(
485 "{0}:{1}".format(host, port),
486 result.stdout_str)
487
488 status = ssh.execute(
489 check_cloudinit_finished)['exit_code'] == 0
490 # Else, just wait for SSH
491 else:
492 status = ssh.execute('echo ok')['exit_code'] == 0
493 return status
494
495 except (AuthenticationException, BadAuthenticationType):
496 return True
497 except Exception:
498 return False
499
500 def _ssh_wait(host,
501 port,
502 username=settings.SSH_NODE_CREDENTIALS['login'],
503 password=settings.SSH_NODE_CREDENTIALS['password'],
504 timeout=0):
505
506 if host in passed and passed[host] >= 2:
507 # host already passed the check
508 return True
509
510 for node in self._get_nodes_by_roles(
511 roles=underlay_node_roles):
512 ip = node_ip
513 if ip not in passed:
514 passed[ip] = 0
515 if _ssh_check(ip, port):
516 passed[ip] += 1
517 else:
518 passed[ip] = 0
519
520 helpers.wait(
521 lambda: _ssh_wait(node_ip, 22),
522 timeout=timeout,
523 timeout_msg="Node '{}' didn't open SSH in {} sec".format(
524 node['name'], timeout
525 )
526 )
527 LOG.info('Heat stack "{0}" ready'
528 .format(self.__config.hardware.heat_stack_name))
529
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +0300530 def _verify_resources_status(self, status):
531 """Check that all resources have verified `status`
532
533 In case when all resources have expected status return empty list,
534 otherwise return a list with resources with incorrect status.
535 """
536 ret = [r for r in self.__nested_resources if
537 r.resource_status != status]
538 return ret
539
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200540 def _create_environment(self):
541 tpl_files, template = template_utils.get_template_contents(
542 self.__config.hardware.heat_conf_path)
543 env_files_list = []
544 env_files, env = (
545 template_utils.process_multiple_environments_and_files(
546 env_paths=[self.__config.hardware.heat_env_path],
547 env_list_tracker=env_files_list))
548
Dmitriy Kruglovf40de7e2020-03-03 12:06:00 +0100549 mcp_version = settings.MCP_VERSION
550 if "2019.2" in settings.MCP_VERSION:
551 mcp_version = "2019.2.0"
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200552 fields = {
553 'stack_name': self.__config.hardware.heat_stack_name,
554 'template': template,
555 'files': dict(list(tpl_files.items()) + list(env_files.items())),
556 'environment': env,
Dennis Dmitrievc902ad82019-04-12 13:41:30 +0300557 'parameters': {
Dmitriy Kruglovf40de7e2020-03-03 12:06:00 +0100558 'mcp_version': mcp_version,
Dennis Dmitrievc902ad82019-04-12 13:41:30 +0300559 'env_name': settings.ENV_NAME,
Hanna Arhipova31cb1d82021-01-27 09:41:11 +0200560 'deploy_empty_node': bool(settings.DEPLOY_EMPTY_NODE)
Dennis Dmitrievc902ad82019-04-12 13:41:30 +0300561 }
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200562 }
563
564 if env_files_list:
565 fields['environment_files'] = env_files_list
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +0300566
567 @retry(heat_exceptions.HTTPBadGateway, delay=15, tries=20)
568 def safe_heat_stack_create():
569 self.__stacks.create(**fields)
570
Hanna Arhipova168fc022020-09-04 14:36:17 +0300571 @retry(exceptions.EnvironmentBadStatus, delay=60, tries=1)
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +0300572 def safe_create():
573 self.delete_environment()
574 safe_heat_stack_create()
575 self.wait_of_stack_status(EXPECTED_STACK_STATUS, tries=140)
576 LOG.info("Stack '%s' created",
577 self.__config.hardware.heat_stack_name)
578 incorrect_resources = self._verify_resources_status(
579 EXPECTED_STACK_STATUS)
580 if incorrect_resources:
581 LOG.info("Recreate the stack because some resources have "
582 "incorrect status")
583 for r in incorrect_resources:
584 LOG.error(
585 'The resource %s has status %s. But it should be %s',
586 r.resource_name,
587 r.resource_status,
588 EXPECTED_STACK_STATUS)
589 st = self._current_stack.stack_status
590 raise exceptions.EnvironmentBadStatus(
591 self.__config.hardware.heat_stack_name,
592 EXPECTED_STACK_STATUS,
593 st,
594 incorrect_resources)
595 safe_create()
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200596
597 def stop(self):
598 """Stop environment"""
599 LOG.warning("HEAT Manager doesn't support stop environment feature")
600 pass
601
Dmitry Tyzhnenkoc800aad2019-05-27 18:10:46 +0300602 def delete_environment(self):
603 if list(self.__stacks.list(
604 stack_name=self.__config.hardware.heat_stack_name)):
605 LOG.info("Delete stack '%s'",
606 self.__config.hardware.heat_stack_name)
607
608 @retry(heat_exceptions.HTTPBadGateway, delay=15, tries=20)
609 def safe_heat_stack_delete():
610 self.__stacks.delete(self._current_stack.id)
611
612 safe_heat_stack_delete()
613 self.wait_of_stack_status('DELETE_COMPLETE',
614 delay=30, tries=20,
615 wait_for_delete=True)
616 else:
617 LOG.warning("Can't delete stack '%s' due is absent",
618 self.__config.hardware.heat_stack_name)
619
Dennis Dmitrievf5f2e602017-11-03 15:36:19 +0200620# TODO(ddmitriev): add all Environment methods
621 @staticmethod
622 def node_ip(node, address_pool_name='admin-pool01'):
623 """Determine node's IP
624
625 :param node: a dict element from the self._nodes
626 :return: string
627 """
628 if address_pool_name in node['addresses']:
629 addr = node['addresses'][address_pool_name]
630 LOG.debug('{0} IP= {1}'.format(node['name'], addr))
631 return addr
632 else:
633 raise exceptions.EnvironmentNodeAccessError(
634 node['name'],
635 "No addresses available for the subnet {0}"
636 .format(address_pool_name))
637
638 def set_address_pools_config(self):
639 """Store address pools CIDRs in config object"""
640 for ap in self._address_pools:
641 for role in ap['roles']:
642 self.__config.underlay.address_pools[role] = ap['cidr']
643
644 def set_dhcp_ranges_config(self):
645 """Store DHCP ranges in config object"""
646 for ap in self._address_pools:
647 for role in ap['roles']:
648 self.__config.underlay.dhcp_ranges[role] = {
649 "cidr": ap['cidr'],
650 "start": ap['start'],
651 "end": ap['end'],
652 "gateway": ap['gateway'],
653 }
654
655 def wait_for_node_state(self, node_name, state, timeout):
656 raise NotImplementedError()
657
658 def warm_shutdown_nodes(self, underlay, nodes_prefix, timeout=600):
659 raise NotImplementedError()
660
661 def warm_restart_nodes(self, underlay, nodes_prefix, timeout=600):
662 raise NotImplementedError()
663
664 @property
665 def slave_nodes(self):
666 raise NotImplementedError()