Create reusable Heat staks.

Change-Id: I51a62f21ad29068faffca95087833fed9e3806cd
This commit is contained in:
Federico Ressi 2019-05-17 16:37:47 +02:00
parent 30f336a736
commit 03cd053a61
9 changed files with 617 additions and 3 deletions

View File

@ -0,0 +1,24 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from tobiko.openstack.stacks import _neutron
from tobiko.openstack.stacks import _nova
NovaKeyPairStackFixture = _nova.NovaKeyPairStackFixture
NeutronNetworkStackFixture = _neutron.NeutronNetworkStackFixture
NeutronServerStackFixture = _neutron.NeutronServerStackFixture

View File

@ -0,0 +1,36 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import os
from tobiko.openstack import heat
from tobiko import config
CONF = config.CONF
TEMPLATE_DIRS = [os.path.dirname(__file__)]
def heat_template_file(template_file):
"""Fixture to load template files from templates directory
Return fixtures to loads templates from
'tobiko/tests/scenario/neutron/templates' directory
"""
return heat.heat_template_file(template_file=template_file,
template_dirs=TEMPLATE_DIRS)

View File

@ -0,0 +1,107 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import tobiko
from tobiko import config
from tobiko.openstack import heat
from tobiko.openstack.stacks import _hot
from tobiko.openstack.stacks import _nova
from tobiko.shell import ssh
CONF = config.CONF
class NeutronNetworkStackFixture(heat.HeatStackFixture):
"""Heat stack for creating internal network with a router to external
"""
#: Heat template file
template = _hot.heat_template_file('neutron/network.yaml')
#: IPv4 sub-net CIDR
ipv4_cidr = '190.40.2.0/24'
@property
def has_ipv4(self):
return bool(self.ipv4_cidr)
#: IPv6 sub-net CIDR
ipv6_cidr = '2001:db8:1:2::/64'
@property
def has_ipv6(self):
return bool(self.ipv6_cidr)
#: Floating IP network where the Neutron floating IPs are created
gateway_network = CONF.tobiko.neutron.floating_network
@property
def has_gateway(self):
return bool(self.gateway_network)
class NeutronServerStackFixture(heat.HeatStackFixture):
#: Heat template file
template = _hot.heat_template_file('neutron/server.yaml')
key_pair_stack = tobiko.required_setup_fixture(
_nova.NovaKeyPairStackFixture)
network_stack = tobiko.required_setup_fixture(NeutronNetworkStackFixture)
#: Glance image used to create a Nova server instance
image = CONF.tobiko.nova.image
#: Nova flavor used to create a Nova server instance
flavor = CONF.tobiko.nova.flavor
#: username used to login to a Nova server instance
username = CONF.tobiko.nova.username
#: password used to login to a Nova server instance
password = CONF.tobiko.nova.password
@property
def key_name(self):
return self.key_pair_stack.outputs.key_name
@property
def network(self):
return self.network_stack.outputs.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):
return bool(self.floating_network)
@property
def ssh_client(self):
return ssh.ssh_client(
host=self.outputs.floating_ip_address,
username=self.username,
password=self.password)
@property
def ssh_command(self):
return ssh.ssh_command(
host=self.outputs.floating_ip_address,
username=self.username)

View File

@ -0,0 +1,51 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import os
import six
from tobiko import config
from tobiko.openstack import heat
from tobiko.openstack.stacks import _hot
CONF = config.CONF
class NovaKeyPairStackFixture(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.read_keys()
super(NovaKeyPairStackFixture, 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 as_str(text):
if isinstance(text, six.string_types):
return text
else:
return text.decode()

View File

@ -0,0 +1,185 @@
heat_template_version: newton
description: |
Creates an network with a subnet and a gateway router to an external network
if given
parameters:
port_security_enabled:
description: Default value to be assigned to network ports
type: boolean
default: false
value_specs:
description: Extra network creation parameters
type: json
default: {}
has_ipv4:
description: Whenever to create IPv4 subnet
type: boolean
default: false
has_ipv6:
description: Whenever to create IPv6 subnet
type: boolean
default: false
ipv4_cidr:
description: IPv4 subnet CIDR to be assigned to new network
type: string
constraints:
- custom_constraint: net_cidr
ipv6_cidr:
description: IPv6 subnet CIDR to be assigned to new network
type: string
constraints:
- custom_constraint: net_cidr
ipv4_dns_nameservers:
description: IPv4 nameservers IP addresses
type: comma_delimited_list
default: []
constraints:
- custom_constraint: dns_name
ipv6_dns_nameservers:
description: IPv6 nameservers IP addresses
type: comma_delimited_list
default: []
constraints:
- custom_constraint: dns_name
ipv6_address_mode:
description: IPv6 address mode
type: string
default: slaac
constraints:
- allowed_values: [ slaac, dhcpv6-stateful, dhcpv6-stateless ]
ipv6_ra_mode:
description: IPv6 router advertisement mode
type: string
default: slaac
constraints:
- allowed_values: [ slaac, dhcpv6-stateful, dhcpv6-stateless ]
has_gateway:
description: whenever to create gateway router
type: boolean
default: false
gateway_network:
description: Optional gateway network to route packages to
type: string
default:
constraints:
- custom_constraint: neutron.network
has_net_mtu:
description: whenever net mtu extension is available
type: boolean
default: false
conditions:
has_ipv4:
get_param: has_ipv4
has_ipv6:
get_param: has_ipv6
has_gateway:
get_param: has_gateway
has_ipv4_gateway:
and:
- get_param: has_ipv4
- get_param: has_gateway
has_ipv6_gateway:
and:
- get_param: has_ipv6
- get_param: has_gateway
has_net_mtu:
get_param: has_net_mtu
resources:
network:
type: OS::Neutron::Net
properties:
port_security_enabled: {get_param: port_security_enabled}
value_specs: {get_param: value_specs}
ipv4_subnet:
type: OS::Neutron::Subnet
condition: has_ipv4
properties:
network: {get_resource: network}
ip_version: 4
cidr: {get_param: ipv4_cidr}
dns_nameservers: {get_param: ipv4_dns_nameservers}
ipv6_subnet:
type: OS::Neutron::Subnet
condition: has_ipv6
properties:
network: {get_resource: network}
ip_version: 6
cidr: {get_param: ipv6_cidr}
dns_nameservers: {get_param: ipv6_dns_nameservers}
ipv6_address_mode: {get_param: ipv6_address_mode}
ipv6_ra_mode: {get_param: ipv6_ra_mode}
gateway:
type: OS::Neutron::Router
condition: has_gateway
properties:
external_gateway_info:
network: {get_param: gateway_network}
ipv4_gateway_interface:
type: OS::Neutron::RouterInterface
condition: has_ipv4_gateway
properties:
router: {get_resource: gateway}
subnet: {get_resource: ipv4_subnet}
ipv6_gateway_interface:
type: OS::Neutron::RouterInterface
condition: has_ipv6_gateway
properties:
router: {get_resource: gateway}
subnet: {get_resource: ipv6_subnet}
outputs:
network_id:
description: Network ID
value: {get_resource: network}
ipv4_subnet_id:
description: IPv4 subnet ID
value: {get_resource: ipv4_subnet}
condition: has_ipv4
ipv6_subnet_id:
description: IPv6 subnet ID
value: {get_resource: ipv6_subnet}
condition: has_ipv6
gateway_id:
description: Gateway router ID
value: {get_resource: gateway}
condition: has_gateway
mtu:
description: Network MTU value (integer)
value: {get_attr: [network, mtu]}
condition: has_net_mtu

View File

@ -0,0 +1,35 @@
heat_template_version: newton
description: |
Stack of shared Neutron security groups
resources:
icmp:
type: OS::Neutron::SecurityGroup
description: Security group to allow to ping Nova server instances
properties:
rules:
- protocol: icmp
ssh:
type: OS::Neutron::SecurityGroup
description: Security group to allow to SSH Nova server instances
properties:
rules:
- protocol: tcp
port_range_min: 22
port_range_max: 22
outputs:
icmp_security_group_id:
description: Security group ID to allow to ping Nova server instances
value: {get_resource: icmp}
ssh_security_group_id:
description: Security group ID to allow to SSH Nova server instances
value: {get_resource: ssh}

View File

@ -0,0 +1,117 @@
heat_template_version: newton
description: |
Creates a Nova server connected to an existing Neutron network and
optionally assign a floating IP address to server so it is routable from the
public network.
parameters:
key_name:
type: string
description: Name of keypair to assign to server
constraints:
- custom_constraint: nova.keypair
flavor:
type: string
description: Flavor to use for server
constraints:
- custom_constraint: nova.flavor
image:
type: string
description: Name of image to use for server
network:
type: string
description: ID of network to which server get connected
constraints:
- custom_constraint: neutron.network
port_security_enabled:
type: boolean
description: Whenever port security is enabled on server port
default: false
security_groups:
type: comma_delimited_list
description: Security groups to subscrive server port
default: []
has_floating_ip:
type: boolean
description: Whenever server has floating IP associated
default: false
floating_network:
type: string
description: |
Public network for which floating IP addresses will be allocated
constraints:
- custom_constraint: neutron.network
conditions:
has_floating_ip:
get_param: has_floating_ip
resources:
port:
type: OS::Neutron::Port
description: Neutron port
properties:
network: {get_param: network}
port_security_enabled: {get_param: port_security_enabled}
security_groups: {get_param: security_groups}
server_name:
type: OS::Heat::RandomString
properties:
character_classes: [{'class': 'lowercase', 'min': 1}]
length: 8
server:
type: OS::Nova::Server
description: Nova server connected to Neutron port
properties:
name: {get_attr: [server_name, value]}
key_name: {get_param: key_name}
image: {get_param: image}
flavor: {get_param: flavor}
networks:
- port: {get_resource: port}
floating_ip:
type: OS::Neutron::FloatingIP
description: Floating IP address to be connected to server
condition: has_floating_ip
properties:
floating_network: {get_param: floating_network}
port_id: {get_resource: port}
outputs:
fixed_ips:
description: fixed IP addresses of server
value: {get_attr: [port, fixed_ips]}
floating_ip_address:
description: Floating IP address of server in public network
value: { get_attr: [ floating_ip, floating_ip_address ] }
condition: has_floating_ip
port_security_enabled:
value: {get_attr: [port, port_security_enabled]}
security_groups:
value: {get_attr: [port, security_groups]}
server_name:
value: {get_attr: [server, name]}

View File

@ -0,0 +1,34 @@
heat_template_version: newton
description: |
Creates a nova SSH keypair to be used for creating Nova servers
parameters:
public_key:
type: string
description: SSH public key
resources:
key_name:
type: OS::Heat::RandomString
description: Random unique key pair name
properties:
length: 32
key_pair:
type: OS::Nova::KeyPair
description: SSH key pair
properties:
name: {get_attr: [key_name, value]}
public_key: {get_param: public_key}
outputs:
key_name:
description: unique Nova key pair name
value: {get_attr: [key_name, value]}

View File

@ -17,7 +17,9 @@ from __future__ import absolute_import
import testtools
import tobiko
from tobiko import config
from tobiko.openstack import stacks
from tobiko.shell import sh
@ -59,14 +61,17 @@ class ExecuteTest(testtools.TestCase):
def test_succeed_with_timeout(self):
self.test_succeed(timeout=30.)
def test_fails(self, command='false', exit_status=1, stdout='', stderr='',
**kwargs):
def test_fails(self, command='false', exit_status=None, stdout='',
stderr='', **kwargs):
ex = self.assertRaises(sh.ShellCommandFailed, self.execute, command,
**kwargs)
self.assertEqual(self.expected_ex_command(command), ex.command)
self.assertEqual(stdout, ex.stdout)
self.assertEqual(stderr, ex.stderr)
self.assertEqual(exit_status, ex.exit_status)
if exit_status:
self.assertEqual(exit_status, ex.exit_status)
else:
self.assertTrue(ex.exit_status)
def test_fails_getting_exit_status(self):
self.test_fails('exit 15', exit_status=15)
@ -103,3 +108,23 @@ class ExecuteTest(testtools.TestCase):
def expected_ex_command(self, command):
return sh.join_command(self.expected_command(command))
class ExecuteWithSSHClientTest(ExecuteTest):
server_stack = tobiko.required_setup_fixture(
stacks.NeutronServerStackFixture)
@property
def ssh_client(self):
return self.server_stack.ssh_client
class ExecuteWithSSHCommandTest(ExecuteTest):
server_stack = tobiko.required_setup_fixture(
stacks.NeutronServerStackFixture)
@property
def shell(self):
return self.server_stack.ssh_command