class ExternalNetworkStackFixture(heat.HeatStackFixture): template = _hot.heat_template_file('neutron/external_network.yaml') @property def external_name(self): return tobiko.tobiko_config().neutron.external_network subnet_enable_dhcp: typing.Optional[bool] = False _external_network: typing.Optional[NeutronNetworkType] = None @property def external_network(self) -> typing.Optional[NeutronNetworkType]: external_network = self._external_network if external_network is None: subnet_parameters = {} if self.subnet_enable_dhcp is not None: subnet_parameters['enable_dhcp'] = self.subnet_enable_dhcp for network in list_external_networks(name=self.external_name): if not network['subnets']: LOG.debug(f"Network '{network['id']}' has any subnet") continue subnets = neutron.list_subnets(network_id=network['id'], **subnet_parameters) if not subnets: LOG.debug(f"Network '{network['id']}' has any valid " f"subnet: {subnet_parameters}") continue network_dump = json.dumps(network, indent=4, sort_keys=True) LOG.debug(f"Found external network for {self.fixture_name}:\n" f"{network_dump}") subnets_dump = json.dumps(subnets, indent=4, sort_keys=True) LOG.debug(f"External subnets for {self.fixture_name}:\n" f"{subnets_dump}") self._external_network = external_network = network break else: LOG.warning("No external network found for " f"'{self.fixture_name}':\n" f" - name or ID: {self.external_name}\n" f" - subnet attributes: {subnet_parameters}\n") return external_network @property def external_id(self): network = self.external_network return network and network['id'] or None @property def has_external_id(self): return bool(self.external_id) @property def network_details(self): return neutron.get_network(self.network_id) has_gateway = False
class KeyPairStackFixture(heat.HeatStackFixture): template = _hot.heat_template_file('nova/key_pair.yaml') key_file = os.path.expanduser(CONF.tobiko.nova.key_file) public_key = None private_key = None def setup_fixture(self): self.create_key_file() self.read_keys() super(KeyPairStackFixture, self).setup_fixture() def read_keys(self): with open(self.key_file, 'r') as fd: self.private_key = as_str(fd.read()) with open(self.key_file + '.pub', 'r') as fd: self.public_key = as_str(fd.read()) def create_key_file(self): key_file = os.path.realpath(self.key_file) if not os.path.isfile(key_file): key_dir = os.path.dirname(key_file) tobiko.makedirs(key_dir) try: sh.local_execute(['ssh-keygen', '-f', key_file, '-P', '']) except sh.ShellCommandFailed: if not os.path.isfile(key_file): raise else: assert os.path.isfile(key_file)
class AmphoraIPv4LoadBalancerStack(heat.HeatStackFixture): template = _hot.heat_template_file('octavia/load_balancer.yaml') vip_network = tobiko.required_fixture(_neutron.NetworkStackFixture) #: Floating IP network where the Neutron floating IP are created @property def floating_network(self) -> str: return self.vip_network.floating_network @property def has_floating_ip(self) -> bool: return bool(self.floating_network) ip_version = 4 provider = 'amphora' @property def vip_subnet_id(self): if self.ip_version == 4: return self.vip_network.ipv4_subnet_id else: return self.vip_network.ipv6_subnet_id def wait_for_active_loadbalancer(self, timeout: tobiko.Seconds = None): octavia.wait_for_status(status_key=octavia.PROVISIONING_STATUS, status=octavia.ACTIVE, get_client=octavia.get_loadbalancer, object_id=self.loadbalancer_id, timeout=timeout) def wait_for_update_loadbalancer(self, timeout: tobiko.Seconds = None): octavia.wait_for_status(status_key=octavia.PROVISIONING_STATUS, status=octavia.PENDING_UPDATE, get_client=octavia.get_loadbalancer, object_id=self.loadbalancer_id, timeout=timeout) def wait_for_octavia_service(self, interval: tobiko.Seconds = None, timeout: tobiko.Seconds = None, client=None): for attempt in tobiko.retry(timeout=timeout, interval=interval, default_timeout=180., default_interval=5.): try: octavia.list_amphorae(loadbalancer_id=self.loadbalancer_id, client=client) except octavia.OctaviaClientException as ex: LOG.debug(f"Error listing amphorae: {ex}") if attempt.is_last: raise LOG.info('Waiting for the LB to become functional again...') else: LOG.info('Octavia service is available!') break
class DesignateZoneStackFixture(heat.HeatStackFixture): template = _hot.heat_template_file('designate/zone.yaml') @property def zone_name(self) -> str: return tobiko.get_fixture_name(self).lower() + '.' @property def zone_details(self) -> typing.Mapping[str, typing.Any]: return designate.get_designate_zone(self.zone_id)
class FlavorStackFixture(heat.HeatStackFixture): template = _hot.heat_template_file('nova/flavor.yaml') disk = None ephemeral = None extra_specs = None is_public = None name = None rxtx_factor = None swap = None vcpus = None
class QosPolicyStackFixture(heat.HeatStackFixture): """Heat stack with a QoS Policy and some QoS Policy Rules """ has_qos_policy = True has_bwlimit = True has_dscp_marking = True bwlimit_kbps = CONF.tobiko.neutron.bwlimit_kbps bwlimit_burst_kbps = int(0.8 * bwlimit_kbps) direction = CONF.tobiko.neutron.direction dscp_mark = CONF.tobiko.neutron.dscp_mark #: Heat template file template = _hot.heat_template_file('neutron/qos.yaml')
class ServerStackFixture(heat.HeatStackFixture): #: Heat template file template = _hot.heat_template_file('nova/server.yaml') #: stack with the key pair for the server instance key_pair_stack = tobiko.required_setup_fixture(KeyPairStackFixture) #: stack with the internal where the server port is created network_stack = tobiko.required_setup_fixture(_neutron.NetworkStackFixture) #: Glance image used to create a Nova server instance image_fixture = None @property def image(self): return self.image_fixture.image_id @property def username(self): """username used to login to a Nova server instance""" return self.image_fixture.username @property def password(self): """password used to login to a Nova server instance""" return self.image_fixture.password # Stack used to create flavor for Nova server instance flavor_stack = None @property def flavor(self): """Flavor for Nova server instance""" return self.flavor_stack.flavor_id #: Whenever port security on internal network is enable port_security_enabled = False #: Security groups to be associated to network ports security_groups = [] # type: typing.List[str] @property def key_name(self): return self.key_pair_stack.key_name @property def network(self): return self.network_stack.network_id #: Floating IP network where the Neutron floating IP is created floating_network = CONF.tobiko.neutron.floating_network @property def has_floating_ip(self): """Whenever to allocate floating IP for the server""" return bool(self.floating_network) @property def ssh_client(self): return ssh.ssh_client(host=self.ip_address, username=self.username, password=self.password) @property def ssh_command(self): return ssh.ssh_command(host=self.ip_address, username=self.username) @property def ip_address(self): if self.has_floating_ip: return self.floating_ip_address else: return self.outputs.fixed_ips[0]['ip_address'] #: Schedule on different host that this Nova server instance ID different_host = None #: Schedule on same host as this Nova server instance ID same_host = None @property def scheduler_hints(self): scheduler_hints = {} if self.different_host: scheduler_hints.update(different_host=self.different_host) if self.same_host: scheduler_hints.update(same_host=self.same_host) return scheduler_hints @property def server_details(self): return nova.get_server(self.server_id) @property def port_details(self): return neutron.get_port(self.port_id) def getDetails(self): # pylint: disable=W0212 details = super(ServerStackFixture, self).getDetails() stack = self.get_stack() if stack: details[self.fixture_name + '.stack'] = (self.details_content( get_json=lambda: stack._info)) if stack.stack_status == 'CREATE_COMPLETE': details[self.fixture_name + '.server_details'] = (self.details_content( get_json=lambda: self.server_details._info)) details[self.fixture_name + '.console_output'] = (self.details_content( get_text=lambda: self.console_output)) return details def details_content(self, **kwargs): return tobiko.details_content(content_id=self.fixture_name, **kwargs) max_console_output_length = 64 * 1024 @property def console_output(self): return nova.get_console_output(server=self.server_id, length=self.max_console_output_length)
class NetworkStackFixture(heat.HeatStackFixture): """Heat stack for creating internal network with a router to external""" #: Heat template file template = _hot.heat_template_file('neutron/network.yaml') #: Disable port security by default for new network ports port_security_enabled = False @property def has_ipv4(self): """Whenever to setup IPv4 subnet""" return bool(CONF.tobiko.neutron.ipv4_cidr) @property def ipv4_cidr(self): if self.has_ipv4: return neutron.new_ipv4_cidr(seed=self.fixture_name) else: return None @property def has_ipv6(self): """Whenever to setup IPv6 subnet""" return bool(CONF.tobiko.neutron.ipv4_cidr) @property def ipv6_cidr(self): if self.has_ipv6: return neutron.new_ipv6_cidr(seed=self.fixture_name) else: return None @property def network_value_specs(self): """Extra network creation parameters""" return {} @property def gateway_network(self): """Floating IP network where the Neutron floating IPs are created""" return CONF.tobiko.neutron.floating_network ha = False @property def gateway_value_specs(self): value_specs = {} if self.has_l3_ha: value_specs.update(ha=(self.ha or False)) return value_specs @property def has_gateway(self): """Whenever to setup gateway router""" return bool(self.gateway_network) @property def has_net_mtu(self): """Whenever can obtain network MTU value""" return neutron.has_networking_extensions('net-mtu') @property def has_l3_ha(self): """Whenever can obtain gateway router HA value""" return neutron.has_networking_extensions('l3-ha') @property def network_details(self): return neutron.get_network(self.network_id) @property def ipv4_subnet_details(self): return neutron.get_subnet(self.ipv4_subnet_id) @property def ipv4_subnet_cidr(self): return netaddr.IPNetwork(self.ipv4_subnet_details['cidr']) @property def ipv4_subnet_gateway_ip(self): return netaddr.IPAddress(self.ipv4_subnet_details['gateway_ip']) @property def ipv6_subnet_details(self): return neutron.get_subnet(self.ipv6_subnet_id) @property def ipv6_subnet_cidr(self): return netaddr.IPNetwork(self.ipv6_subnet_details['cidr']) @property def ipv6_subnet_gateway_ip(self): return netaddr.IPAddress(self.ipv6_subnet_details['gateway_ip']) @property def gateway_details(self): return neutron.get_router(self.gateway_id) @property def external_gateway_ips(self): fixed_ips = self.gateway_details['external_gateway_info'][ 'external_fixed_ips'] return tobiko.select( netaddr.IPAddress(fixed_ip['ip_address']) for fixed_ip in fixed_ips) @property def ipv4_gateway_ports(self): return neutron.list_ports(fixed_ips='subnet_id=' + self.ipv4_subnet_id, device_id=self.gateway_id, network_id=self.network_id) @property def ipv6_gateway_ports(self): return neutron.list_ports(fixed_ips='subnet_id=' + self.ipv6_subnet_id, device_id=self.gateway_id, network_id=self.network_id) @property def external_geteway_ports(self): return neutron.list_ports(device_id=self.gateway_id, network_id=self.gateway_network_id) @property def ipv4_gateway_addresses(self): ips = tobiko.Selection() for port in self.ipv4_gateway_ports: ips.extend(neutron.list_port_ip_addresses(port)) return ips @property def ipv6_gateway_addresses(self): ips = tobiko.Selection() for port in self.ipv6_gateway_ports: ips.extend(neutron.list_port_ip_addresses(port)) return ips @property def external_gateway_addresses(self): ips = tobiko.Selection() for port in self.external_geteway_ports: ips.extend(neutron.list_port_ip_addresses(port)) return ips @property def gateway_network_details(self): return neutron.get_network(self.gateway_network_id)
class SecurityGroupsFixture(heat.HeatStackFixture): """Heat stack with some security groups """ #: Heat template file template = _hot.heat_template_file('neutron/security_groups.yaml')
class NetworkStackFixture(heat.HeatStackFixture): """Heat stack for creating internal network with a router to external""" #: Heat template file template = _hot.heat_template_file('neutron/network.yaml') #: Enable port security by default for new network ports port_security_enabled = True @property def has_ipv4(self): """Whenever to setup IPv4 subnet""" return bool(CONF.tobiko.neutron.ipv4_cidr) @property def ipv4_cidr(self): if self.has_ipv4: return neutron.new_ipv4_cidr(seed=self.fixture_name) else: return None @property def has_ipv6(self): """Whenever to setup IPv6 subnet""" return bool(CONF.tobiko.neutron.ipv6_cidr) @property def ipv6_cidr(self): if self.has_ipv6: return neutron.new_ipv6_cidr(seed=self.fixture_name) else: return None @property def network_value_specs(self): """Extra network creation parameters""" return {} floating_network_stack = tobiko.required_fixture( FloatingNetworkStackFixture) @property def floating_network(self): """Network ID where the Neutron floating IPs are created""" return self.floating_network_stack.network_id @property def gateway_network(self): """Network ID where gateway routes packages to""" return self.floating_network ha = False @property def gateway_value_specs(self): value_specs = {} if self.has_l3_ha: value_specs.update(ha=(self.ha or False)) return value_specs @property def has_gateway(self): """Whenever to setup gateway router""" return bool(self.gateway_network) @property def has_net_mtu(self): """Whenever can obtain network MTU value""" return neutron.has_networking_extensions('net-mtu') @property def has_l3_ha(self): """Whenever can obtain gateway router HA value""" return neutron.has_networking_extensions('l3-ha') @property def network_details(self): return neutron.get_network(self.network_id) @property def ipv4_subnet_details(self): return neutron.get_subnet(self.ipv4_subnet_id) @property def ipv4_subnet_cidr(self): return netaddr.IPNetwork(self.ipv4_subnet_details['cidr']) @property def ipv4_subnet_gateway_ip(self): return netaddr.IPAddress(self.ipv4_subnet_details['gateway_ip']) @property def ipv4_dns_nameservers(self): nameservers = CONF.tobiko.neutron.ipv4_dns_nameservers if nameservers is None: nameservers = default_nameservers(ip_version=4) return ','.join(str(nameserver) for nameserver in nameservers) @property def ipv6_subnet_details(self): return neutron.get_subnet(self.ipv6_subnet_id) @property def ipv6_subnet_cidr(self): return netaddr.IPNetwork(self.ipv6_subnet_details['cidr']) @property def ipv6_subnet_gateway_ip(self): return netaddr.IPAddress(self.ipv6_subnet_details['gateway_ip']) @property def ipv6_dns_nameservers(self): nameservers = CONF.tobiko.neutron.ipv6_dns_nameservers if nameservers is None: nameservers = default_nameservers(ip_version=6) return ','.join(str(nameserver) for nameserver in nameservers) @property def gateway_details(self): return neutron.get_router(self.gateway_id) @property def external_gateway_ips(self): fixed_ips = self.gateway_details['external_gateway_info'][ 'external_fixed_ips'] return tobiko.select( netaddr.IPAddress(fixed_ip['ip_address']) for fixed_ip in fixed_ips) @property def ipv4_gateway_ports(self): return neutron.list_ports(fixed_ips='subnet_id=' + self.ipv4_subnet_id, device_id=self.gateway_id, network_id=self.network_id) @property def ipv6_gateway_ports(self): return neutron.list_ports(fixed_ips='subnet_id=' + self.ipv6_subnet_id, device_id=self.gateway_id, network_id=self.network_id) @property def external_geteway_ports(self): return neutron.list_ports(device_id=self.gateway_id, network_id=self.gateway_network_id) @property def ipv4_gateway_addresses(self): ips = tobiko.Selection() for port in self.ipv4_gateway_ports: ips.extend(neutron.list_port_ip_addresses(port)) return ips @property def ipv6_gateway_addresses(self): ips = tobiko.Selection() for port in self.ipv6_gateway_ports: ips.extend(neutron.list_port_ip_addresses(port)) return ips @property def external_gateway_addresses(self): ips = tobiko.Selection() for port in self.external_geteway_ports: ips.extend(neutron.list_port_ip_addresses(port)) return ips @property def gateway_network_details(self): return neutron.get_network(self.gateway_network_id) @property def neutron_required_quota_set(self) -> typing.Dict[str, int]: requirements = super().neutron_required_quota_set requirements['network'] += 1 if self.has_ipv4: requirements['subnet'] += 1 if self.has_ipv6: requirements['subnet'] += 1 if self.has_gateway: requirements['router'] += 1 return requirements def is_router_distributed(self) -> bool: if self.has_gateway: tobiko.setup_fixture(self) return bool(self.gateway_details.get('distributed')) else: return False @classmethod def skip_if_router_is_distributed(cls, reason: str = None): fixture = tobiko.get_fixture(cls) if reason is None: reason = "Distributed router is not supported" return tobiko.skip_if(reason=reason, predicate=fixture.is_router_distributed)
class HttpRoundRobinAmphoraIpv4Listener(heat.HeatStackFixture): template = _hot.heat_template_file('octavia/listener.yaml') loadbalancer = tobiko.required_fixture(AmphoraIPv4LoadBalancerStack) lb_port = 80 lb_protocol = 'HTTP' @property def loadbalancer_id(self): return self.loadbalancer.loadbalancer_id @property def loadbalancer_provider(self): return self.loadbalancer.provider # Pool attributes pool_protocol = 'HTTP' lb_algorithm = 'ROUND_ROBIN' # healthmonitor attributes hm_type = 'HTTP' hm_delay = 3 hm_max_retries = 4 hm_timeout = 3 #: whenever to create the health monitor has_monitor = True @property def listener_id(self): return self.listener.listener_id def wait_for_active_members(self): """Wait for all pool members to be active""" for member in octavia.list_members(pool_id=self.pool_id): self.wait_for_active_member(pool_id=self.pool_id, member_id=member['id']) def wait_for_active_member(self, pool_id, member_id, **kwargs): """Wait for the member to be active Waits for the member to have an ACTIVE provisioning status. :param member_id: the member id. :param pool_id: the pool id. """ octavia.wait_for_status(status_key=octavia.PROVISIONING_STATUS, status=octavia.ACTIVE, get_client=octavia.get_member, object_id=pool_id, member_id=member_id, **kwargs) def wait_for_members_to_be_reachable(self, interval: tobiko.Seconds = None, timeout: tobiko.Seconds = None): members = [self.server_stack, self.other_server_stack] if len(members) < 1: return # Wait for members to be reachable from localhost last_reached_id = 0 for attempt in tobiko.retry(timeout=timeout, interval=interval, default_interval=5., default_timeout=members[0].wait_timeout): try: for member in members[last_reached_id:]: octavia.check_members_balanced( members_count=1, ip_address=member.ip_address, protocol=self.lb_protocol, port=self.lb_port, requests_count=1) last_reached_id += 1 # prevent retrying same member again except sh.ShellCommandFailed: if attempt.is_last: raise LOG.info( "Waiting for members to have HTTP service available...") continue else: break else: raise RuntimeError("Members couldn't be reached!") # Members attributes server_stack = tobiko.required_fixture(_ubuntu.UbuntuServerStackFixture) other_server_stack = tobiko.required_fixture( OctaviaOtherServerStackFixture) application_port = 80 ip_version = 4 @property def pool_id(self): return self.pool.pool_id @property def subnet_id(self): network_stack = self.server_stack.network_stack if self.ip_version == 4: return network_stack.ipv4_subnet_id else: return network_stack.ipv6_subnet_id @property def member_address(self) -> str: return self.get_member_address(self.server_stack) @property def other_member_address(self) -> str: return self.get_member_address(self.other_server_stack) def get_member_address(self, server_stack): return str(server_stack.find_fixed_ip(ip_version=self.ip_version))