summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--INFO.yaml44
-rwxr-xr-xci/run_tests.sh2
-rw-r--r--docs/conf.py1
-rw-r--r--docs/conf.yaml3
-rw-r--r--docs/how-to-use/APITests.rst290
-rw-r--r--docs/how-to-use/InstallSnaps.rst8
-rw-r--r--docs/how-to-use/IntegrationTests.rst477
-rw-r--r--docs/how-to-use/LibraryUsage.rst265
-rw-r--r--docs/how-to-use/Testing.rst4
-rw-r--r--docs/how-to-use/UnitTests.rst204
-rw-r--r--docs/how-to-use/VirtEnvDeploy.rst533
-rw-r--r--docs/index.rst16
-rw-r--r--docs/release/release-notes.rst25
-rw-r--r--docs/requirements.txt2
-rw-r--r--examples/complex-network/deploy-complex-network.yaml20
-rw-r--r--examples/demo.py47
-rw-r--r--examples/external-network/deploy-ext-net.yaml5
-rw-r--r--examples/inst-w-volume/deploy-env.yaml44
-rw-r--r--examples/inst-w-volume/deploy-vm-with-volume.yaml144
-rw-r--r--examples/launch.py645
-rw-r--r--examples/simple/deploy-simple.yaml5
-rw-r--r--examples/two-network/deploy-two-net-centos.yaml5
-rw-r--r--examples/two-network/deploy-two-net-ubuntu.yaml5
-rw-r--r--requirements.txt21
-rw-r--r--setup.cfg4
-rw-r--r--setup.py38
-rw-r--r--snaps/config/__init__.py15
-rw-r--r--snaps/config/cluster_template.py308
-rw-r--r--snaps/config/flavor.py107
-rw-r--r--snaps/config/image.py110
-rw-r--r--snaps/config/keypair.py61
-rw-r--r--snaps/config/network.py547
-rw-r--r--snaps/config/project.py60
-rw-r--r--snaps/config/qos.py92
-rw-r--r--snaps/config/router.py133
-rw-r--r--snaps/config/security_group.py399
-rw-r--r--snaps/config/stack.py79
-rw-r--r--snaps/config/tests/__init__.py15
-rw-r--r--snaps/config/tests/cluster_template_tests.py180
-rw-r--r--snaps/config/tests/flavor_tests.py254
-rw-r--r--snaps/config/tests/image_tests.py226
-rw-r--r--snaps/config/tests/keypair_tests.py179
-rw-r--r--snaps/config/tests/network_tests.py339
-rw-r--r--snaps/config/tests/project_tests.py97
-rw-r--r--snaps/config/tests/qos_tests.py91
-rw-r--r--snaps/config/tests/router_tests.py142
-rw-r--r--snaps/config/tests/security_group_tests.py187
-rw-r--r--snaps/config/tests/stack_tests.py117
-rw-r--r--snaps/config/tests/user_tests.py84
-rw-r--r--snaps/config/tests/vm_inst_tests.py246
-rw-r--r--snaps/config/tests/volume_tests.py99
-rw-r--r--snaps/config/tests/volume_type_tests.py91
-rw-r--r--snaps/config/user.py59
-rw-r--r--snaps/config/vm_inst.py166
-rw-r--r--snaps/config/volume.py60
-rw-r--r--snaps/config/volume_type.py146
-rw-r--r--snaps/domain/cluster_template.py175
-rw-r--r--snaps/domain/flavor.py11
-rw-r--r--snaps/domain/network.py83
-rw-r--r--snaps/domain/stack.py21
-rw-r--r--snaps/domain/test/cluster_template_tests.py109
-rw-r--r--snaps/domain/test/network_tests.py123
-rw-r--r--snaps/domain/test/stack_tests.py25
-rw-r--r--snaps/domain/test/vm_inst_tests.py25
-rw-r--r--snaps/domain/test/volume_tests.py42
-rw-r--r--snaps/domain/vm_inst.py24
-rw-r--r--snaps/domain/volume.py42
-rw-r--r--snaps/file_utils.py21
-rw-r--r--snaps/openstack/cluster_template.py96
-rw-r--r--snaps/openstack/create_flavor.py101
-rw-r--r--snaps/openstack/create_image.py124
-rw-r--r--snaps/openstack/create_instance.py612
-rw-r--r--snaps/openstack/create_keypairs.py52
-rw-r--r--snaps/openstack/create_network.py515
-rw-r--r--snaps/openstack/create_project.py123
-rw-r--r--snaps/openstack/create_qos.py66
-rw-r--r--snaps/openstack/create_router.py189
-rw-r--r--snaps/openstack/create_security_group.py397
-rw-r--r--snaps/openstack/create_stack.py413
-rw-r--r--snaps/openstack/create_user.py60
-rw-r--r--snaps/openstack/create_volume.py256
-rw-r--r--snaps/openstack/create_volume_type.py146
-rw-r--r--snaps/openstack/openstack_creator.py61
-rw-r--r--snaps/openstack/os_credentials.py63
-rw-r--r--snaps/openstack/tests/cluster_template_tests.py284
-rw-r--r--snaps/openstack/tests/conf/os_credentials_tests.py48
-rw-r--r--snaps/openstack/tests/conf/os_env.yaml.template11
-rw-r--r--snaps/openstack/tests/create_flavor_tests.py93
-rw-r--r--snaps/openstack/tests/create_image_tests.py66
-rw-r--r--snaps/openstack/tests/create_instance_tests.py1554
-rw-r--r--snaps/openstack/tests/create_keypairs_tests.py39
-rw-r--r--snaps/openstack/tests/create_network_tests.py600
-rw-r--r--snaps/openstack/tests/create_project_tests.py78
-rw-r--r--snaps/openstack/tests/create_qos_tests.py55
-rw-r--r--snaps/openstack/tests/create_router_tests.py669
-rw-r--r--snaps/openstack/tests/create_security_group_tests.py302
-rw-r--r--snaps/openstack/tests/create_stack_tests.py1009
-rw-r--r--snaps/openstack/tests/create_user_tests.py12
-rw-r--r--snaps/openstack/tests/create_volume_tests.py487
-rw-r--r--snaps/openstack/tests/create_volume_type_tests.py60
-rw-r--r--snaps/openstack/tests/heat/agent-group.yaml119
-rw-r--r--snaps/openstack/tests/heat/agent.yaml109
-rw-r--r--snaps/openstack/tests/heat/flavor_heat_template.yaml (renamed from snaps/provisioning/ansible_pb/centos-network-setup/playbooks/configure_host.yml)25
-rw-r--r--snaps/openstack/tests/heat/floating_ip_heat_template.yaml44
-rw-r--r--snaps/openstack/tests/heat/keypair_heat_template.yaml39
-rw-r--r--snaps/openstack/tests/heat/router_heat_template.yaml69
-rw-r--r--snaps/openstack/tests/heat/security_group_heat_template.yaml45
-rw-r--r--snaps/openstack/tests/heat/volume_heat_template.yaml52
-rw-r--r--snaps/openstack/tests/openstack_tests.py185
-rw-r--r--snaps/openstack/tests/os_source_file_test.py109
-rw-r--r--snaps/openstack/utils/__init__.py3
-rw-r--r--snaps/openstack/utils/cinder_utils.py149
-rw-r--r--snaps/openstack/utils/deploy_utils.py6
-rw-r--r--snaps/openstack/utils/glance_utils.py9
-rw-r--r--snaps/openstack/utils/heat_utils.py322
-rw-r--r--snaps/openstack/utils/keystone_utils.py70
-rw-r--r--snaps/openstack/utils/launch_utils.py889
-rw-r--r--snaps/openstack/utils/magnum_utils.py129
-rw-r--r--snaps/openstack/utils/neutron_utils.py397
-rw-r--r--snaps/openstack/utils/nova_utils.py316
-rw-r--r--snaps/openstack/utils/settings_utils.py269
-rw-r--r--snaps/openstack/utils/tests/cinder_utils_tests.py190
-rw-r--r--snaps/openstack/utils/tests/glance_utils_tests.py7
-rw-r--r--snaps/openstack/utils/tests/heat_utils_tests.py599
-rw-r--r--snaps/openstack/utils/tests/keystone_utils_tests.py34
-rw-r--r--snaps/openstack/utils/tests/magnum_utils_tests.py303
-rw-r--r--snaps/openstack/utils/tests/neutron_utils_tests.py715
-rw-r--r--snaps/openstack/utils/tests/nova_utils_tests.py343
-rw-r--r--snaps/openstack/utils/tests/settings_utils_tests.py178
-rw-r--r--snaps/playbook_runner.py46
-rw-r--r--snaps/provisioning/ansible_pb/__init__.py0
-rw-r--r--snaps/provisioning/ansible_pb/centos-network-setup/__init__.py0
-rw-r--r--snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py0
-rw-r--r--snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface14
-rw-r--r--snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py0
-rw-r--r--snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py0
-rw-r--r--snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg2
-rw-r--r--snaps/provisioning/ansible_utils.py108
-rw-r--r--snaps/provisioning/tests/ansible_utils_tests.py90
-rw-r--r--snaps/test_runner.py177
-rw-r--r--snaps/test_suite_builder.py304
-rw-r--r--snaps/tests/file_utils_tests.py4
-rw-r--r--snaps/thread_utils.py (renamed from snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/configure_host.yml)25
-rw-r--r--tox.ini17
145 files changed, 18456 insertions, 4945 deletions
diff --git a/.gitignore b/.gitignore
index b3c2444..9292c06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
*.*project
build
dist
+.tox
+docs/_build/*
diff --git a/INFO.yaml b/INFO.yaml
new file mode 100644
index 0000000..b29addf
--- /dev/null
+++ b/INFO.yaml
@@ -0,0 +1,44 @@
+---
+project: 'SNAPS-OO'
+project_creation_date: '07.02.2017'
+project_category: ''
+lifecycle_state: 'Incubation'
+project_lead: &opnfv_snaps_ptl
+ name: 'Steven Pisarski'
+ email: 's.pisarski@cablelabs.com'
+ id: 'spisarski'
+ company: 'cablelabs.com'
+ timezone: 'Unknown'
+primary_contact: *opnfv_snaps_ptl
+issue_tracking:
+ type: 'jira'
+ url: 'https://jira.opnfv.org/projects/SNAPS'
+ key: 'SNAPS'
+mailing_list:
+ type: 'mailman2'
+ url: 'opnfv-tech-discuss@lists.opnfv.org'
+ tag: '[snaps]'
+realtime_discussion:
+ type: irc
+ server: 'freenode.net'
+ channel: '#opnfv-snaps'
+meetings:
+ - type: 'gotomeeting+irc'
+ agenda: # eg: 'https://wiki.opnfv.org/display/'
+ url: # eg: 'https://global.gotomeeting.com/join/819733085'
+ server: 'freenode.net'
+ channel: '#opnfv-meeting'
+ repeats: 'weekly'
+ time: # eg: '16:00 UTC'
+repositories:
+ - 'snaps'
+committers:
+ - <<: *opnfv_snaps_ptl
+ - name: 'Randy Levensalor'
+ email: 'r.levensalor@cablelabs.com'
+ company: 'cablelabs.com'
+ id: 'RandyL'
+tsc:
+ # yamllint disable rule:line-length
+ approval: 'http//meetbot.opnfv.org/meetings/opnfv-meeting/2017/opnfv-meeting.2017-02-07-15.00.html'
+ # yamllint enable rule:line-length
diff --git a/ci/run_tests.sh b/ci/run_tests.sh
index 14f1a11..464ba63 100755
--- a/ci/run_tests.sh
+++ b/ci/run_tests.sh
@@ -6,7 +6,7 @@ set -e
sudo pip install virtualenv
virtualenv ./vpy
source ./vpy/bin/activate
-pip install -e ../
+pip install -chttps://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/pike -e ../
# $1 is the IP to the pod's build server
# $2 is the IP to the pod's control server
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..3c4453e
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1 @@
+from docs_conf.conf import *
diff --git a/docs/conf.yaml b/docs/conf.yaml
new file mode 100644
index 0000000..0bb596d
--- /dev/null
+++ b/docs/conf.yaml
@@ -0,0 +1,3 @@
+---
+project_cfg: opnfv
+project: SNAPS
diff --git a/docs/how-to-use/APITests.rst b/docs/how-to-use/APITests.rst
index 4a8035a..9f46839 100644
--- a/docs/how-to-use/APITests.rst
+++ b/docs/how-to-use/APITests.rst
@@ -34,6 +34,12 @@ nova_utils_tests.py - NovaSmokeTests
Ensures that a Nova client can be obtained as well as the proper
exceptions thrown with the wrong credentials.
+cinder_utils_tests.py - CinderSmokeTests
+----------------------------------------
+
+Ensures that a Cinder client can be obtained as well as the proper
+exceptions thrown with the wrong credentials.
+
heat_utils_tests.py - HeatSmokeTests
------------------------------------
@@ -98,6 +104,9 @@ create_project_tests.py - CreateProjectSuccessTests
| test_create_project | 2 & 3 | Tests the creation of a project via the OpenStackProject |
| | | class |
+----------------------------------+---------------+-----------------------------------------------------------+
+| test_create_project_quota | 2 & 3 | Tests the creation of a project via the OpenStackProject |
+| _override | | class with overriding the default quota values |
++----------------------------------+---------------+-----------------------------------------------------------+
| test_create_project_2x | 2 & 3 | Tests the creation of a project a second time via the |
| | | OpenStackProject class to ensure it is only created once |
+----------------------------------+---------------+-----------------------------------------------------------+
@@ -157,22 +166,63 @@ neutron_utils_tests.py - NeutronUtilsSubnetTests
+---------------------------------------+---------------+-----------------------------------------------------------+
| Test Name | Neutron API | Description |
+=======================================+===============+===========================================================+
-| test_create_subnet | 2 | Ensures neutron_utils.create_subnet() can properly create |
+| test_create_subnet | 2 | Ensures neutron_utils.create_network() can properly create|
| | | an OpenStack subnet object |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_subnet_null_name | 2 | Ensures neutron_utils.create_subnet() raises an exception |
+| test_create_subnet_null_name | 2 | Ensures neutron_utils.create_network() raises an exception|
| | | when the subnet name is None |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_subnet_empty_name | 2 | Ensures neutron_utils.create_subnet() raises an exception |
+| test_create_subnet_empty_name | 2 | Ensures neutron_utils.create_network() raises an exception|
| | | when the subnet name is an empty string |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_subnet_null_cidr | 2 | Ensures neutron_utils.create_subnet() raises an exception |
+| test_create_subnet_null_cidr | 2 | Ensures neutron_utils.create_network() raises an exception|
| | | when the subnet CIDR is None |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_subnet_empty_cidr | 2 | Ensures neutron_utils.create_subnet() raises an exception |
+| test_create_subnet_empty_cidr | 2 | Ensures neutron_utils.create_network() raises an exception|
| | | when the subnet CIDR is an empty string |
+---------------------------------------+---------------+-----------------------------------------------------------+
+neutron_utils_tests.py - NeutronUtilsIPv6Tests
+----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_create_network_slaac | 2 | Ensures neutron_utils.create_network() can properly create|
+| | | an OpenStack network with an IPv6 subnet when DHCP is True|
+| | | and modes are 'slaac' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_stateful | 2 | Ensures neutron_utils.create_network() can properly create|
+| | | an OpenStack network with an IPv6 subnet when DHCP is True|
+| | | and modes are 'stateful' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_stateless | 2 | Ensures neutron_utils.create_network() can properly create|
+| | | an OpenStack network with an IPv6 subnet when DHCP is True|
+| | | and modes are 'stateless' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_no_dhcp_slaac | 2 | Ensures neutron_utils.create_network() raises a BadRequest|
+| | | exception when deploying the network with an IPv6 subnet |
+| | | when DHCP is False and modes are 'slaac' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_invalid_start_ip | 2 | Ensures neutron_utils.create_network() sets the start IP |
+| | | address to the minimum value when the start configuration |
+| | | parameter is some garbage value |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_invalid_end_ip | 2 | Ensures neutron_utils.create_network() sets the end IP |
+| | | address to the maximum value when the end configuration |
+| | | parameter is some garbage value |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_with_bad_cidr | 2 | Ensures neutron_utils.create_network() raises a BadRequest|
+| | | exception when the IPv6 CIDR is incorrect |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_invalid_gateway_ip| 2 | Ensures neutron_utils.create_network() raises a BadRequest|
+| | | exception when the IPv6 gateway IP does not match the CIDR|
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_with_bad_dns | 2 | Ensures neutron_utils.create_network() raises a BadRequest|
+| | | exception when the IPv6 DNS IP address is not a valid IPv6|
+| | | address |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
neutron_utils_tests.py - NeutronUtilsRouterTests
------------------------------------------------
@@ -186,12 +236,6 @@ neutron_utils_tests.py - NeutronUtilsRouterTests
| face | | an OpenStack router object with an interface to the |
| | | external network |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_router_empty_name | 2 | Ensures neutron_utils.create_router() raises an exception |
-| | | when the name is an empty string |
-+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_router_null_name | 2 | Ensures neutron_utils.create_router() raises an exception |
-| | | when the name is None |
-+---------------------------------------+---------------+-----------------------------------------------------------+
| test_add_interface_router | 2 | Ensures neutron_utils.add_interface_router() properly adds|
| | | an interface to another subnet |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -201,6 +245,9 @@ neutron_utils_tests.py - NeutronUtilsRouterTests
| test_add_interface_router_null_subnet | 2 | Ensures neutron_utils.add_interface_router() raises an |
| | | exception when the subnet object is None |
+---------------------------------------+---------------+-----------------------------------------------------------+
+| test_add_interface_router_missing_sub | 2 | Ensures neutron_utils.add_interface_router() raises an |
+| net | | exception when the subnet object had been deleted |
++---------------------------------------+---------------+-----------------------------------------------------------+
| test_create_port | 2 | Ensures neutron_utils.create_port() can properly create an|
| | | OpenStack port object |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -256,6 +303,95 @@ neutron_utils_tests.py - NeutronUtilsFloatingIpTests
| test_floating_ips | 2 | Ensures that a floating IP can be created |
+---------------------------------------+---------------+-----------------------------------------------------------+
+cinder_utils_tests.py - CinderUtilsQoSTests
+-------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++=======================================+===============+===========================================================+
+| test_create_qos_both | 2 & 3 | Ensures that a QoS Spec can be created with a Consumer |
+| | | value of 'both' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_qos_front | 2 & 3 | Ensures that a QoS Spec can be created with a Consumer |
+| | | value of 'front-end' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_qos_back | 2 & 3 | Ensures that a QoS Spec can be created with a Consumer |
+| | | value of 'back-end' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_qos | 2 & 3 | Ensures that a QoS Spec can be created and deleted |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+cinder_utils_tests.py - CinderUtilsSimpleVolumeTypeTests
+--------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++=======================================+===============+===========================================================+
+| test_create_simple_volume_type | 2 & 3 | Tests the creation of a simple volume type with the |
+| | | function cinder_utils#create_volume_type() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_volume_type | 2 & 3 | Tests the creation of a simple volume type with the |
+| | | function cinder_utils#create_volume_type() then deletes |
+| | | with the function cinder_utils#delete_volume_type() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+cinder_utils_tests.py - CinderUtilsAddEncryptionTests
+-----------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++=======================================+===============+===========================================================+
+| test_create_simple_encryption | 2 & 3 | Tests the creation of a simple volume type encryption |
+| | | with the function cinder_utils#create_volume_encryption() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_encryption | 2 & 3 | Tests the creation of a simple volume type encryption |
+| | | with the function cinder_utils#create_volume_encryption() |
+| | | then deletes with the function |
+| | | cinder_utils#delete_volume_type_encryption() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_with_all_attrs | 2 & 3 | Tests the creation of a simple volume type encryption |
+| | | with the function cinder_utils#create_volume_encryption() |
+| | | where all configuration attributes have been set |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_bad_key_size | 2 & 3 | Tests to ensure that the function |
+| | | cinder_utils#create_volume_encryption() raises a |
+| | | BadRequest exception when the key_size attribute is -1 |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+cinder_utils_tests.py - CinderUtilsVolumeTypeCompleteTests
+----------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++=======================================+===============+===========================================================+
+| test_create_with_encryption | 2 & 3 | Tests the creation of a volume type with encryption |
+| | | with the function cinder_utils#create_volume_type() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_with_qos | 2 & 3 | Tests the creation of a volume type with a QoS Spec |
+| | | with the function cinder_utils#create_volume_type() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_with_invalid_qos | 2 & 3 | Tests the creation of a volume type with an invalid QoS |
+| | | Spec with the function cinder_utils#create_volume_type() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_with_qos_and_encryption | 2 & 3 | Tests the creation of a volume type with a QoS Spec and |
+| | | encryption with the function |
+| | | cinder_utils#create_volume_type() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+cinder_utils_tests.py - CinderUtilsVolumeTests
+----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++=======================================+===============+===========================================================+
+| test_create_simple_volume | 2 & 3 | Tests the creation of a simple volume with the function |
+| | | cinder_utils#create_volume() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_volume | 2 & 3 | Tests the creation of a volume with the function |
+| | | cinder_utils#create_volume() then deletion with the |
+| | | function cinder_utils#delete_volume() |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
nova_utils_tests.py - NovaUtilsKeypairTests
-------------------------------------------
@@ -295,6 +431,25 @@ nova_utils_tests.py - NovaUtilsInstanceTests
| | | nova_utils.create_server() |
+---------------------------------------+---------------+-----------------------------------------------------------+
+nova_utils_tests.py - NovaUtilsInstanceVolumeTests
+--------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Nova API | Description |
++=======================================+===============+===========================================================+
+| test_add_remove_volume | 2 | Ensures that a VM instance can properly attach and detach |
+| | | a volume using the nova interface while waiting for |
+| | | the update to fully occur |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_attach_volume_nowait | 2 | Ensures that the call to nova_utils.attach_volume raises |
+| | | an exception when the timeout is too short to return an |
+| | | properly updated VmInst object |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_detach_volume_nowait | 2 | Ensures that the call to nova_utils.detach_volume raises |
+| | | an exception when the timeout is too short to return an |
+| | | properly updated VmInst object |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
create_flavor_tests.py - CreateFlavorTests
------------------------------------------
@@ -324,9 +479,9 @@ heat_utils_tests.py - HeatUtilsCreateSimpleStackTests
+---------------------------------------+---------------+-----------------------------------------------------------+
| Test Name | Heat API | Description |
+=======================================+===============+===========================================================+
-| test_create_stack | 1 | Tests the heat_utils.create_stack() with a test template |
+| test_create_stack | 1-3 | Tests the heat_utils.create_stack() with a test template |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_stack_x2 | 1 | Tests the heat_utils.create_stack() with a test template |
+| test_create_stack_x2 | 1-3 | Tests the heat_utils.create_stack() with a test template |
| | | and attempts to deploy a second time w/o actually |
| | | deploying any objects |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -337,21 +492,120 @@ heat_utils_tests.py - HeatUtilsCreateComplexStackTests
+---------------------------------------+---------------+-----------------------------------------------------------+
| Test Name | Heat API | Description |
+=======================================+===============+===========================================================+
-| test_get_settings_from_stack | 1 | Tests the heat_utils functions that are responsible for |
+| test_get_settings_from_stack | 1-3 | Tests the heat_utils functions that are responsible for |
| | | reverse engineering settings objects of the types deployed|
| | | by Heat |
+---------------------------------------+---------------+-----------------------------------------------------------+
+heat_utils_tests.py - HeatUtilsRouterTests
+------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_create_router_with_stack | 1-3 | Tests ability of the function |
+| | | heat_utils.get_stack_routers() to return the correct |
+| | | OpenStackRouter instance |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+heat_utils_tests.py - HeatUtilsVolumeTests
+------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_create_vol_with_stack | 1-3 | Tests ability of the function |
+| | | heat_utils.create_stack() to return the correct |
+| | | Volume domain objects deployed with Heat |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_vol_types_with_stack | 1-3 | Tests ability of the function |
+| | | heat_utils.get_stack_volumes_types() to return the correct|
+| | | VolumeType domain objects deployed with Heat |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+heat_utils_tests.py - HeatUtilsKeypairTests
+-------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_create_keypair_with_stack | 1-3 | Tests ability of the function |
+| | | heat_utils.get_stack_keypairs() to return the correct |
+| | | Keypair domain objects deployed with Heat |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+heat_utils_tests.py - HeatUtilsSecurityGroupTests
+-------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_create_security_group_with_stack | 1-3 | Tests ability of the function |
+| | | heat_utils.get_stack_security_groups() to return the |
+| | | correct SecurityGroup domain objects deployed with Heat |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+heat_utils_tests.py - HeatUtilsFlavorTests
+------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_create_flavor_with_stack | 1-3 | Tests ability of the function |
+| | | heat_utils.get_stack_flavors() to return the correct |
+| | | Flavor domain objects deployed with Heat |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+magnum_utils_tests.py - MagnumUtilsTests
+----------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Magnum API | Description |
++=======================================+===============+===========================================================+
+| test_create_cluster_template_simple | 1 | Tests ability of the function |
+| | | magnum_utils.create_cluster_template() to create a simple |
+| | | cluster template OpenStack object with minimal config |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_all | 1 | Tests ability of the function |
+| | | magnum_utils.create_cluster_template() to create a |
+| | | cluster template OpenStack object with maximum config |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad_image| 1 | Ensures the function |
+| | | magnum_utils.create_cluster_template() will raise a |
+| | | BadRequest exception when the image does not exist |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad_ext | 1 | Ensures the function |
+| _net | | magnum_utils.create_cluster_template() will raise a |
+| | | BadRequest exception when the external network does not |
+| | | exist |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad | 1 | Ensures the function |
+| _flavor | | magnum_utils.create_cluster_template() will raise a |
+| | | BadRequest exception when the flavor does not exist |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad | 1 | Ensures the function |
+| _master_flavor | | magnum_utils.create_cluster_template() will raise a |
+| | | BadRequest exception when the master flavor does not exist|
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad | 1 | Ensures the function |
+| _network_driver | | magnum_utils.create_cluster_template() will raise a |
+| | | BadRequest exception when the network driver is invalid |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad | 1 | Ensures the function |
+| _volume_driver | | magnum_utils.create_cluster_template() will raise a |
+| | | BadRequest exception when the volume driver is invalid |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
settings_utils_tests.py - SettingsUtilsNetworkingTests
------------------------------------------------------
+---------------------------------------+---------------+-----------------------------------------------------------+
| Test Name | API | Description |
+=======================================+===============+===========================================================+
-| test_derive_net_settings_no_subnet | Neutron 2 | Tests to ensure that derived NetworkSettings from an |
+| test_derive_net_settings_no_subnet | Neutron 2 | Tests to ensure that derived NetworkConfig from an |
| | | OpenStack network are correct without a subnet |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_derive_net_settings_two_subnets | Neutron 2 | Tests to ensure that derived NetworkSettings from an |
+| test_derive_net_settings_two_subnets | Neutron 2 | Tests to ensure that derived NetworkConfig from an |
| | | OpenStack network are correct with two subnets |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -361,9 +615,9 @@ settings_utils_tests.py - SettingsUtilsVmInstTests
+---------------------------------------+---------------+-----------------------------------------------------------+
| Test Name | API | Description |
+=======================================+===============+===========================================================+
-| test_derive_vm_inst_settings | Neutron 2 | Tests to ensure that derived VmInstanceSettings from an |
+| test_derive_vm_inst_config | Neutron 2 | Tests to ensure that derived VmInstanceSettings from an |
| | | OpenStack VM instance is correct |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_derive_image_settings | Neutron 2 | Tests to ensure that derived ImageSettings from an |
+| test_derive_image_settings | Neutron 2 | Tests to ensure that derived ImageConfig from an |
| | | OpenStack VM instance is correct |
+---------------------------------------+---------------+-----------------------------------------------------------+
diff --git a/docs/how-to-use/InstallSnaps.rst b/docs/how-to-use/InstallSnaps.rst
index dc53ef0..f5e9cf8 100644
--- a/docs/how-to-use/InstallSnaps.rst
+++ b/docs/how-to-use/InstallSnaps.rst
@@ -69,4 +69,12 @@ The "pip" command below needs to be executed as root, if you are not using a vir
sudo pip install -e <path to repo>/snaps/
(note: on CentOS 7 and Ubuntu 14.04 you may have to try the previous command several times)
+SNAPS is now hosted on the Python Package Manager (PyPI).
+
+::
+
+ pip install snaps
+
+This will install the stable Euphrates version.
+
The install should now be complete and you can start using the SNAPS-OO libraries.
diff --git a/docs/how-to-use/IntegrationTests.rst b/docs/how-to-use/IntegrationTests.rst
index f082f9b..79ef8ef 100644
--- a/docs/how-to-use/IntegrationTests.rst
+++ b/docs/how-to-use/IntegrationTests.rst
@@ -46,6 +46,17 @@ create_security_group_tests.py - CreateSecurityGroupTests
| | | setting object |
+---------------------------------------+---------------+-----------------------------------------------------------+
+create_security_group_tests.py - CreateMultipleSecurityGroupTests
+-----------------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | API Versions | Description |
++=======================================+===============+===========================================================+
+| test_sec_grp_same_name_diff_proj | Keysone 2 & 3 | Ensures the OpenStackSecurityGroup class does not |
+| | Neutron 2 | initialize security groups with the same name from other |
+| | | project/tenants |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
create_image_tests.py - CreateImageSuccessTests
-----------------------------------------------
@@ -80,7 +91,7 @@ create_image_tests.py - CreateImageNegativeTests
| Test Name | Glance API | Description |
+=======================================+===============+===========================================================+
| test_bad_image_name | 1 & 2 | Ensures OpenStackImage.create() results in an Exception |
-| | | being raised when the ImageSettings.name attribute has |
+| | | being raised when the ImageConfig.name attribute has |
| | | not been set |
+---------------------------------------+---------------+-----------------------------------------------------------+
| test_bad_image_url | 1 & 2 | Ensures OpenStackImage.create() results in an Exception |
@@ -185,6 +196,52 @@ create_network_tests.py - CreateNetworkSuccessTests
| | | 'admin' project ID |
+---------------------------------------+---------------+-----------------------------------------------------------+
+create_network_tests.py - CreateNetworkGatewayTests
+---------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_create_subnet_default_gateway_ip | 2 | Ensures that a network can be created with a Subnet that |
+| | | has the gateway_ip automatically assigned |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_subnet_valid_gateway_ip | 2 | Ensures that a network can be created with a Subnet that |
+| | | has the gateway_ip statically assigned with a valid IP |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_subnet_no_gateway | 2 | Ensures that a network can be created where no gateway_ip |
+| | | is assigned |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_subnet_invalid_gateway_ip | 2 | Ensures that a network cannot be created with a Subnet |
+| | | has an invalid gateway_ip value such as 'foo' |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_network_tests.py - CreateNetworkIPv6Tests
+------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_create_network_one_ipv6_subnet | 2 | Ensures that a network can be created with an IPv6 subnet |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_network_ipv4_ipv6_subnet | 2 | Ensures that a network can be created with an IPv4 and |
+| | | IPv6 subnet |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_network_tests.py - CreateMultipleNetworkTests
+----------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_network_same_name_diff_proj | 2 | Ensures that a network with the same name can be created |
+| | | against different projects |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_network_create_by_admin_to | 2 | Ensures that a network can be created by the admin user |
+| _different_project | | to another project and that a creator with the credentials|
+| | | to the other project will not create a new network with |
+| | | the same name |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
create_router_tests.py - CreateRouterSuccessTests
-------------------------------------------------
@@ -203,6 +260,12 @@ create_router_tests.py - CreateRouterSuccessTests
| test_create_delete_router | 2 | Ensures that a router can be deleted via the |
| | | OpenStackRouter.clean() method |
+---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_with_internal_sub | 2 | Ensures that a router can be joined to a subnet created by|
+| | | the same user who created the subnet |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_with_invalid_internal_sub | 2 | Ensures that a router cannot be created when attempting to|
+| | | join a subnet created by the admin user |
++---------------------------------------+---------------+-----------------------------------------------------------+
| test_create_router_admin_state_false | 2 | Ensures that a router can created with |
| | | admin_state_up = False |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -215,6 +278,9 @@ create_router_tests.py - CreateRouterSuccessTests
| test_create_router_external_network | 2 | Ensures that a router can be created that is connected to |
| | | both external and private internal networks |
+---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_router_with_ext_port | 2 | Ensures that a router can be created by an 'admin' user |
+| | | with a port to an external network |
++---------------------------------------+---------------+-----------------------------------------------------------+
create_router_tests.py - CreateRouterNegativeTests
--------------------------------------------------
@@ -228,39 +294,326 @@ create_router_tests.py - CreateRouterNegativeTests
| test_create_router_invalid_gateway_name| 2 | Ensures that an exception is raised when attempting to |
| | | create a router to an external network that does not exist|
+----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_router_admin_ports | 2 | Ensures that an exception is raised when attempting to |
+| | | create a router with ports to networks owned by another |
+| | | project |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_router_tests.py - CreateMultipleRouterTests
+--------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_router_same_name_diff_proj | 2 | Ensures that a router with the same name can be created |
+| | | against different projects |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_router_create_by_admin_to | 2 | Ensures that a router can be created by the admin user |
+| _different_project | | to another project and that a creator with the credentials|
+| | | to the other project will not create a new router with |
+| | | the same name |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_router_tests.py - CreateRouterSecurityGroupTests
+-------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_create_router_secure_port | 2 | Ensures that a router's port can have a security group |
+| | | applied to it |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_router_tests.py - CreateRouterSharedNetworksTests
+--------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Neutron API | Description |
++=======================================+===============+===========================================================+
+| test_create_router_external | 2 | Ensures that a router can be joined to an external network|
+| | | that was created by an admin user |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_router_port_external | 2 | Ensures that a router can have a port created to an |
+| | | external network that was created by an admin user |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_router_port_shared | 2 | Ensures that a router can have a port created to an |
+| | | shared network that was created by an admin user |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_qos_tests.py - CreateQoSTests
+------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_create_qos | 2 & 3 | Tests the creation of a QoS Spec with the class |
+| | | OpenStackQoS |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_qos | 2 & 3 | Tests the creation of a QoS Spec with the class |
+| | | OpenStackQoS, its deletion with cinder_utils.py the |
+| | | the attempts to use the clean() method to ensure an |
+| | | exception is not called |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_same_qos | 2 & 3 | Tests the creation of a QoS Spec with the class |
+| | | OpenStackQoS then instantiates another OpenStackQoS |
+| | | object with the same configuration to ensure the second |
+| | | instance returns the ID of the original |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_type_tests.py - CreateSimpleVolumeTypeSuccessTests
+----------------------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_create_volume_type | 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolumeType |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_volume_type | 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolumeType, its deletion with cinder_utils.py, |
+| | | then attempts to use the clean() method to ensure an |
+| | | exception is not raised |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_same_volume_type | 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolumeType then instantiates another |
+| | | OpenStackVolumeType object with the same configuration to |
+| | | ensure the second instance returns the ID of the original |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_type_tests.py - CreateSimpleVolumeTypeComplexTests
+----------------------------------------------------------------
+
++-----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++=========================================+===============+===========================================================+
+| test_volume_type_with_qos | 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolumeType with a QoSSpec |
++-----------------------------------------+---------------+-----------------------------------------------------------+
+| test_volume_type_with_encryption | 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolumeType with encryption |
++-----------------------------------------+---------------+-----------------------------------------------------------+
+| test_volume_type_with_qos_and_encryption| 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolumeType with encryption and QoS Spec |
++-----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_tests.py - CreateSimpleVolumeSuccessTests
+-------------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_create_volume_simple | 2 & 3 | Tests the creation of a Volume Type with the class |
+| | | OpenStackVolume |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_volume | 2 & 3 | Tests the creation of a Volume with the class |
+| | | OpenStackVolume, its deletion with cinder_utils.py, then |
+| | | attempts to use the clean() method to ensure an |
+| | | exception is not raised |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_same_volume | 2 & 3 | Tests the creation of a Volume with the class |
+| | | OpenStackVolume then instantiates another |
+| | | OpenStackVolume object with the same configuration to |
+| | | ensure the second instance returns the ID of the original |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_tests.py - CreateSimpleVolumeFailureTests
+-------------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_create_volume_bad_size | 2 & 3 | Tests to ensure that attempting to create a volume with a |
+| | | size of -1 raises a BadRequest exception |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_volume_bad_type | 2 & 3 | Tests to ensure that attempting to create a volume with a |
+| | | type that does not exist raises a NotFound exception |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_volume_bad_image | 2 & 3 | Tests to ensure that attempting to create a volume with an|
+| | | image that does not exist raises a BadRequest exception |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_tests.py - CreateVolumeWithTypeTests
+--------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_bad_volume_type | 2 & 3 | Tests to ensure the creation of a Volume with the |
+| | | OpenStackVolume#create() method raises a NotFound |
+| | | exception when attempting to apply a VolumeType that does |
+| | | not exist |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_valid_volume_type | 2 & 3 | Tests to ensure the creation of a Volume with the |
+| | | OpenStackVolume#create() method properly creates the |
+| | | volume when associating with a valid VolumeType |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_tests.py - CreateVolumeWithImageTests
+---------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_bad_image_name | 2 & 3 | Tests to ensure the creation of a Volume with the |
+| | | OpenStackVolume#create() method raises a BadRequest |
+| | | exception when attempting to apply an image that does not |
+| | | exist |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_valid_volume_image | 2 & 3 | Tests to ensure the creation of a Volume with the |
+| | | OpenStackVolume#create() method properly creates the |
+| | | volume when associating with a valid image |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
+create_volume_tests.py - CreateVolMultipleCredsTests
+----------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Cinder API | Description |
++========================================+===============+===========================================================+
+| test_create_by_admin_to_other_proj | 2 & 3 | Tests to ensure the creation of a Volume as a user with |
+| | | an 'admin' role can create a volume to another project |
+| | | and a creator with the credentails to that project will |
+| | | not create another with the same name |
+| | | Currently inactive due to |
+| | | https://bugs.launchpad.net/cinder/+bug/1641982 |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_two_vol_same_name_diff_proj| 2 & 3 | Tests to ensure the creation of a Volume with the same |
+| | | name by two different creators with different credentials |
+| | | will create two different volumes with the same name |
+| | | that are applied to each project in question |
++----------------------------------------+---------------+-----------------------------------------------------------+
create_stack_tests.py - CreateStackSuccessTests
-----------------------------------------------
+---------------------------------------+---------------+-----------------------------------------------------------+
-| Test Name | Neutron API | Description |
+| Test Name | Heat API | Description |
+=======================================+===============+===========================================================+
-| test_create_stack_template_file | 2 | Ensures that a Heat stack can be created with a file-based|
+| test_create_stack_template_file | 1-3 | Ensures that a Heat stack can be created with a file-based|
| | | Heat template file |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_stack_template_dict | 2 | Ensures that a Heat stack can be created with a dictionary|
+| test_create_stack_template_dict | 1-3 | Ensures that a Heat stack can be created with a dictionary|
| | | Heat template |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_delete_stack | 2 | Ensures that a Heat stack can be created and deleted |
+| test_create_delete_stack | 1-3 | Ensures that a Heat stack can be created and deleted |
| | | while having clean() called 2x without an exception |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_create_same_stack | 2 | Ensures that a Heat stack with the same name cannot be |
+| test_create_same_stack | 1-3 | Ensures that a Heat stack with the same name cannot be |
| | | created 2x |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_retrieve_network_creators | 2 | Ensures that an OpenStackHeatStack instance can return an |
+| test_retrieve_network_creators | 1-3 | Ensures that an OpenStackHeatStack instance can return an |
| | | OpenStackNetwork instance configured as deployed |
+---------------------------------------+---------------+-----------------------------------------------------------+
-| test_retrieve_vm_inst_creators | 2 | Ensures that an OpenStackHeatStack instance can return an |
+| test_retrieve_vm_inst_creators | 1-3 | Ensures that an OpenStackHeatStack instance can return an |
| | | OpenStackVmInstance instance configured as deployed |
+---------------------------------------+---------------+-----------------------------------------------------------+
+create_stack_tests.py - CreateStackVolumeTests
+----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_retrieve_volume_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a |
+| | | OpenStackVolume instance that it was responsible for |
+| | | deploying |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_retrieve_volume_type_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a |
+| | | OpenStackVolumeType instance that it was responsible for |
+| | | deploying |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackFloatingIpTests
+--------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_connect_via_ssh_heat_vm | 1 | Ensures that an OpenStackHeatStack instance can create a |
+| | | VM with a floating IP that can be accessed via |
+| | | OpenStackVmInstance |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_connect_via_ssh_heat_vm_derived | 1 | Ensures that an OpenStackHeatStack instance can create a |
+| | | VM with a floating IP where a generated initialized |
+| | | OpenStackHeatStack can return an initialized |
+| | | OpenStackVmInstance object that will be used to access the|
+| | | VM via SSH |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackNestedResourceTests
+------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_nested | 1 | Ensures that an OpenStackHeatStack with an external |
+| | | resource file with VMs with floating IPs can be accessed |
+| | | in the class OpenStackVmInstance and return the associated|
+| | | initialized OpenStackVmInstance objects |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackUpdateTests
+----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_update | 1 | Ensures that an OpenStackHeatStack can have the number of |
+| | | VMs updated and they are spawned and access can be |
+| | | obtained with SSH over floating IPs |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackRouterTests
+----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_retrieve_router_creator | 1 | Ensures that an OpenStackHeatStack instance can return a |
+| | | OpenStackRouter instance that it was responsible for |
+| | | deploying |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackFlavorTests
+----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_retrieve_flavor_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a |
+| | | OpenStackFlavor instance that it was responsible for |
+| | | deploying |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackKeypairTests
+-----------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_retrieve_keypair_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a |
+| | | OpenStackKeypair instance that it was responsible for |
+| | | deploying |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_stack_tests.py - CreateStackSecurityGroupTests
+-----------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++=======================================+===============+===========================================================+
+| test_retrieve_security_group_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a |
+| | | OpenStackSecurityGroup instance that it was responsible |
+| | | for deploying |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
create_stack_tests.py - CreateComplexStackTests
-----------------------------------------------
+---------------------------------------+---------------+-----------------------------------------------------------+
-| Test Name | Neutron API | Description |
+| Test Name | Heat API | Description |
+=======================================+===============+===========================================================+
-| test_connect_via_ssh_heat_vm | 2 | Ensures that two OpenStackHeatStack instances can return |
+| test_connect_via_ssh_heat_vm | 1-3 | Ensures that two OpenStackHeatStack instances can return |
| | | OpenStackVmInstance instances one configured with a |
| | | floating IP and keypair and can be access via SSH |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -269,15 +622,25 @@ create_stack_tests.py - CreateStackNegativeTests
------------------------------------------------
+----------------------------------------+---------------+-----------------------------------------------------------+
-| Test Name | Neutron API | Description |
+| Test Name | Heat API | Description |
+========================================+===============+===========================================================+
-| test_missing_dependencies | 2 | Ensures that a Heat template fails to deploy when expected|
+| test_missing_dependencies | 1-3 | Ensures that a Heat template fails to deploy when expected|
| | | dependencies are missing |
+----------------------------------------+---------------+-----------------------------------------------------------+
-| test_bad_stack_file | 2 | Ensures that a Heat template fails to deploy when the Heat|
+| test_bad_stack_file | 1-3 | Ensures that a Heat template fails to deploy when the Heat|
| | | template file does not exist |
+----------------------------------------+---------------+-----------------------------------------------------------+
+create_stack_tests.py - CreateStackFailureTests
+-----------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Heat API | Description |
++========================================+===============+===========================================================+
+| test_stack_failure | 1-3 | Ensures that a Heat template fails to deploy when expected|
+| | | dependencies are missing |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
create_instance_tests.py - CreateInstanceSimpleTests
----------------------------------------------------
@@ -285,7 +648,21 @@ create_instance_tests.py - CreateInstanceSimpleTests
| Test Name | API Versions | Description |
+=======================================+===============+===========================================================+
| test_create_delete_instance | Nova 2 | Ensures that the OpenStackVmInstance.clean() method |
-| | Neutron 2 | deletes the instance |
+| | Neutron 2 | deletes the instance as well as ensuring the VmInst |
+| | | availability_zone is populated and compute_host is None |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_admin_instance | Nova 2 | Ensures that the VmInst object members availability_zone |
+| | Neutron 2 | and compute_host return a value |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
+create_instance_tests.py - CreateInstanceExternalNetTests
+---------------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | API Versions | Description |
++=======================================+===============+===========================================================+
+| test_create_instance_public_net | Nova 2 | Ensures that an OpenStackVmInstance initialized as a user |
+| | Neutron 2 | of type 'admin' can create a VM against an external net |
+---------------------------------------+---------------+-----------------------------------------------------------+
create_instance_tests.py - SimpleHealthCheck
@@ -323,6 +700,15 @@ create_instance_tests.py - CreateInstanceSingleNetworkTests
| test_ssh_client_fip_after_active | Nova 2 | Ensures that an instance can be reached over SSH when the |
| | Neutron 2 | floating IP is assigned after to the VM becoming ACTIVE |
+---------------------------------------+---------------+-----------------------------------------------------------+
+| test_ssh_client_fip_after_init | Nova 2 | Ensures that an instance can have a floating IP assigned |
+| | Neutron 2 | added after initialization |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_ssh_client_fip_reverse_engineer | Nova 2 | Ensures that an instance can be reverse engineered and |
+| | Neutron 2 | allows for a floating IP to be added after initialization |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_ssh_client_fip_after_reboot | Nova 2 | Ensures that an instance can be reached over SSH after |
+| | Neutron 2 | a reboot call has been issued |
++---------------------------------------+---------------+-----------------------------------------------------------+
| test_ssh_client_fip_second_creator | Nova 2 | Ensures that an instance can be reached over SSH via a |
| | Neutron 2 | second identical creator object |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -336,6 +722,12 @@ create_instance_tests.py - CreateInstancePortManipulationTests
| test_set_custom_valid_ip_one_subnet | Nova 2 | Ensures that an instance's can have a valid static IP is |
| | Neutron 2 | properly assigned |
+---------------------------------------+---------------+-----------------------------------------------------------+
+| test_set_one_port_two_ip_one_subnet | Nova 2 | Ensures that an instance can have two static IPs on a |
+| | Neutron 2 | single port from a single subnet |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_set_one_port_two_ip_two_subnets | Nova 2 | Ensures that an instance can have two static IPs on a |
+| | Neutron 2 | single port from different subnets on a network |
++---------------------------------------+---------------+-----------------------------------------------------------+
| test_set_custom_invalid_ip_one_subnet | Nova 2 | Ensures that an instance's port with an invalid static IP |
| | Neutron 2 | raises an exception |
+---------------------------------------+---------------+-----------------------------------------------------------+
@@ -378,14 +770,18 @@ create_instance_tests.py - CreateInstanceFromThreePartImage
| | Neutron 2 | delete it when using a 3-part image |
+-----------------------------------------------------+---------------+-----------------------------------------------------------+
-create_instance_tests.py - CreateInstancePubPrivNetTests
---------------------------------------------------------
+create_instance_tests.py - CreateInstanceIPv6NetworkTests (Staging)
+-------------------------------------------------------------------
+---------------------------------------+---------------+-----------------------------------------------------------+
| Test Name | API Versions | Description |
+=======================================+===============+===========================================================+
-| test_dual_ports_dhcp | Nova 2 | Ensures that a VM with two ports/NICs can have its second |
-| | Neutron 2 | NIC configured via SSH/Ansible after startup |
+| test_v4fip_v6overlay | Nova 2 | Expects a BadRequest exception to be raised when |
+| | Neutron 2 | attempting to add an IPv4 floating IP to a VM with an IPv6|
+| | | port |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_fip_v4and6_overlay | Nova 2 | Connects to a VM via a floating IP joined to a port that |
+| | Neutron 2 | has been confiured with both IPv4 and IPv6 addresses |
+---------------------------------------+---------------+-----------------------------------------------------------+
create_instance_tests.py - InstanceSecurityGroupTests
@@ -410,6 +806,19 @@ create_instance_tests.py - InstanceSecurityGroupTests
| | Neutron 2 | that has already been added to the instance |
+---------------------------------------+---------------+-----------------------------------------------------------+
+create_instance_tests.py - CreateInstanceVolumeTests
+----------------------------------------------------
+
++---------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | API Versions | Description |
++=======================================+===============+===========================================================+
+| test_create_instance_with_one_volume | Nova 2 | Ensures that a VM instance can have one volume attached |
+| | Cinder 2 & 3 | to it |
++---------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_instance_with_two_volumes | Nova 2 | Ensures that a VM instance can have two volumes attached |
+| | Cinder 2 & 3 | to it |
++---------------------------------------+---------------+-----------------------------------------------------------+
+
ansible_utils_tests.py - AnsibleProvisioningTests
-------------------------------------------------
@@ -423,3 +832,35 @@ ansible_utils_tests.py - AnsibleProvisioningTests
| | Neutron 2 | apply a Ansible playbook containing Jinga2 substitution |
| | | values |
+---------------------------------------+---------------+-----------------------------------------------------------+
+
+cluster_template_tests.py - CreateClusterTemplateTests
+------------------------------------------------------
+
++----------------------------------------+---------------+-----------------------------------------------------------+
+| Test Name | Magnum API | Description |
++========================================+===============+===========================================================+
+| test_create_cluster_template | 1 | Tests the creation of a Cluster template with the class |
+| | | OpenStackClusterTemplate |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_delete_cluster_template | 1 | Tests the creation and deletiong of a Cluster template |
+| | | with the class OpenStackClusterTemplate |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_same_cluster_template | 1 | Tests the creation of a Cluster template 2x using the same|
+| | | config object to ensure it was only created once |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad_flavor| 1 | Tests to ensure OpenStackClusterTemplate#create() will |
+| | | raise an exception when the flavor is invalid |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad_master| 1 | Tests to ensure OpenStackClusterTemplate#create() will |
+| _flavor | | raise an exception when the master flavor is invalid |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad_image | 1 | Tests to ensure OpenStackClusterTemplate#create() will |
+| | | raise an exception when the image is invalid |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad | 1 | Tests to ensure OpenStackClusterTemplate#create() will |
+| _network_driver | | raise an exception when the network driver is invalid |
++----------------------------------------+---------------+-----------------------------------------------------------+
+| test_create_cluster_template_bad | 1 | Tests to ensure OpenStackClusterTemplate#create() will |
+| _volume_driver | | raise an exception when the volume driver is invalid |
++----------------------------------------+---------------+-----------------------------------------------------------+
+
diff --git a/docs/how-to-use/LibraryUsage.rst b/docs/how-to-use/LibraryUsage.rst
index 16cf446..3183305 100644
--- a/docs/how-to-use/LibraryUsage.rst
+++ b/docs/how-to-use/LibraryUsage.rst
@@ -30,15 +30,16 @@ attributes are listed below:
- auth\_url
- project\_name (aka. tenant\_name)
- identity\_api\_version (for obtaining Keystone authorization token.
- Versions 2.0 & v3 only validated.)
-- image\_api\_version (Glance version 1 & 2 only validated)
+ default = 2, Versions 2.0 & v3 only validated.)
+- image\_api\_version (default = 2, Glance version 1 & 2 only validated)
- network\_api\_version (Neutron version 2 currently only validated)
- compute\_api\_version (Nova version 2 currently only validated)
- heat\_api\_version (Heat version 1 currently only validated)
+- volume\_api\_version (default = 2, Heat versions 2 & 3 currently only validated)
- user\_domain\_id (default='default')
-- user\_domain\_name (default='default')
+- user\_domain\_name (default='Default')
- project\_domain\_id (default='default')
-- project\_domain\_name (default='default')
+- project\_domain\_name (default='Default')
- interface (default='admin', used to specify the endpoint type for keystone: public, admin, internal)
- cacert (default=False, expected values T|F to denote server certificate verification, else value contains the path to an HTTPS certificate)
- region_name (The region name default=None)
@@ -78,7 +79,7 @@ Create User
-----------
- User - snaps.openstack.create\_user.OpenStackUser
- - snaps.openstack.create\_user.UserSettings
+ - snaps.openstack.user.UserConfig
- name - the username (required)
- password - the user's password (required)
@@ -88,11 +89,14 @@ Create User
- email - the user's email address (optional)
- enabled - flag to determine whether or not the user should be
enabled (default=True)
+ - roles - dict where key is the role's name and value is the name
+ the project to associate with the role (optional)
.. code:: python
- from snaps.openstack.create_user import UserSettings, OpenStackUser
- user_settings = UserSettings(name='username', password='password')
+ from snaps.config.user import UserConfig
+ from snaps.openstack.create_user import OpenStackUser
+ user_settings = UserConfig(name='username', password='password')
user_creator = OpenStackUser(os_creds, user_settings)
user_creator.create()
@@ -109,19 +113,20 @@ Create Project
--------------
- Project - snaps.openstack.create\_project.OpenStackProject
- - snaps.openstack.create\_project.ProjectSettings
+ - snaps.openstack.project.ProjectConfig
- name - the project name (required)
- domain - the project's domain (default='default')
- description - the project's description (optional)
- - enables - flag to determine whether or not the project should
+ - enabled - flag to determine whether or not the project should
be enabled (default=True)
.. code:: python
- from snaps.openstack.create_project import ProjectSettings, OpenStackProject
- project_settings = ProjectSettings(name='username', password='password')
+ from snaps.openstack.project import ProjectConfig
+ from snaps.openstack.create_project import OpenStackProject
+ project_settings = ProjectConfig(name='username', password='password')
project_creator = OpenStackProject(os_creds, project_settings)
project_creator.create()
@@ -135,7 +140,7 @@ Create Flavor
-------------
- Flavor - snaps.openstack.create\_flavor.OpenStackFlavor
- - snaps.openstack.create\_flavor.FlavorSettings
+ - snaps.config.flavor.FlavorConfig
- name - the flavor name (required)
- flavor\_id - the flavor's string ID (default='auto')
@@ -148,11 +153,17 @@ Create Flavor
if backend supports QoS extension (default=1.0)
- is\_public - flag that denotes whether or not other projects
can access image (default=True)
+ - metadata - freeform dict() for special metadata (optional)
+ - freeform dict() for values of basic elements
+ (e.g. ram, vcpu, disk, etc) could be added.
+ As result the hard coded values of those elements will be
+ overwritten by the new ones (optional)
.. code:: python
- from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor
- flavor_settings = FlavorSettings(name='flavor-name', ram=4, disk=10, vcpus=2)
+ from snaps.config.flavor import FlavorConfig
+ from snaps.openstack.create_flavor import OpenStackFlavor
+ flavor_settings = FlavorConfig(name='flavor-name', ram=4, disk=10, vcpus=2)
flavor_creator = OpenStackFlavor(os_creds, flavor_settings)
flavor_creator.create()
@@ -166,27 +177,33 @@ Create Image
------------
- Image - snaps.openstack.create\_image.OpenStackImage
- - snaps.openstack.create\_image.ImageSettings
+ - snaps.config.image.ImageConfig
- name - the image name (required)
- image\_user - the default image user generally used by
OpenStackVMInstance class for obtaining an SSH connection
(required)
- - img\_format - the image's format (i.e. qcow2) (required)
+ - img\_format or format - the image's format (i.e. qcow2) (required)
- url - the download URL to obtain the image file (this or
image\_file must be configured, not both)
- image\_file - the location of the file to be sourced from the
local filesystem (this or url must be configured, not both)
+ - extra\_properties - dict() object containing extra parameters to
+ pass when loading the image (i.e. ids of kernel and initramfs images)
- nic\_config\_pb\_loc - the location of the ansible playbook
that can configure additional NICs. Floating IPs are required
- to perform this operation. (optional)
+ to perform this operation. (optional and deprecated)
+ - kernel\_image\_settings - the image settings for a kernel image (optional)
+ - ramdisk\_image\_settings - the image settings for a ramdisk image (optional)
+ - public - image will be created with public visibility when True (default = False)
.. code:: python
- from snaps.openstack.create_image import ImageSettings, OpenStackImage
- image_settings = ImageSettings(name='image-name', image_user='ubuntu', img_format='qcow2',
- url='http://uec-images.ubuntu.com/releases/trusty/14.04/ubuntu-14.04-server-cloudimg-amd64-disk1.img')
+ from snaps.openstack.create_image import OpenStackImage
+ from snaps.config.image import ImageConfig
+ image_settings = ImageConfig(name='image-name', image_user='ubuntu', img_format='qcow2',
+ url='http://uec-images.ubuntu.com/releases/trusty/14.04/ubuntu-14.04-server-cloudimg-amd64-disk1.img')
image_creator = OpenStackImage(os_creds, image_settings)
image_creator.create()
@@ -200,7 +217,7 @@ Create Keypair
--------------
- Keypair - snaps.openstack.create\_keypair.OpenStackKeypair
- - snaps.openstack.create\_keypair.KeypairSettings
+ - snaps.openstack.keypair.KeypairConfig
- name - the keypair name (required)
- public\_filepath - the file location to where the public key is
@@ -209,11 +226,16 @@ Create Keypair
file is to be written or currently resides (optional but highly
recommended to leverage or the private key will be lost
forever)
+ - key\_size - The number of bytes for the key size when it needs to
+ be generated (value must be >=512, default = 1024)
+ - delete\_on\_clean - when True, the key files will be deleted when
+ OpenStackKeypair#clean() is called (default = False)
.. code:: python
- from snaps.openstack.create_keypairs import KeypairSettings, OpenStackKeypair
- keypair_settings = KeypairSettings(name='kepair-name', private_filepath='/tmp/priv-kp')
+ from snaps.openstack.keypair.KeypairConfig
+ from snaps.openstack.create_keypairs import OpenStackKeypair
+ keypair_settings = KeypairConfig(name='kepair-name', private_filepath='/tmp/priv-kp')
keypair_creator = OpenStackKeypair(os_creds, keypair_settings)
keypair_creator.create()
@@ -228,7 +250,7 @@ Create Network
- Network - snaps.openstack.create\_network.OpenStackNetwork
- - snaps.openstack.create\_network.NetworkSettings
+ - snaps.config_network.NetworkConfig
- name - the name of the network (required)
- admin\_state\_up - flag denoting the administrative status of
@@ -242,8 +264,10 @@ Create Network
- network\_type - the type of network (i.e. vlan\|vxlan\|flat)
- physical\_network - the name of the physical network (required
when network\_type is 'flat')
+ - segmentation\_id - the id of the segmentation (required
+ when network\_type is 'vlan')
- subnet\_settings (list of optional
- snaps.openstack.create\_network.SubnetSettings objects)
+ snaps.config.network.SubnetConfig objects)
- cidr - the subnet's CIDR (required)
- ip\_version - 4 or 6 (default=4)
@@ -267,10 +291,11 @@ Create Network
.. code:: python
- from snaps.openstack.create_network import NetworkSettings, SubnetSettings, OpenStackNetwork
+ from snaps.config.network import NetworkConfig, SubnetConfig
+ from snaps.openstack.create_network import OpenStackNetwork
- subnet_settings = SubnetSettings(name='subnet-name', cidr='10.0.0.0/24')
- network_settings = NetworkSettings(name='network-name', subnet_settings=[subnet_settings])
+ subnet_settings = SubnetConfig(name='subnet-name', cidr='10.0.0.0/24')
+ network_settings = NetworkConfig(name='network-name', subnet_settings=[subnet_settings])
network_creator = OpenStackNetwork(os_creds, network_settings)
network_creator.create()
@@ -320,10 +345,12 @@ Create Security Group
.. code:: python
+ from snaps.config.network import SubnetConfig
+ from snaps.config.rule import RuleConfig
from snaps.openstack.create_security_group import SecurityGroupSettings, SecurityGroupRuleSettings, Direction, OpenStackSecurityGroup
- rule_settings = SubnetSettings(name='subnet-name', cidr='10.0.0.0/24')
- network_settings = NetworkSettings(name='network-name', subnet_settings=[subnet_settings])
+ rule_settings = RuleConfig(name='subnet-name', cidr='10.0.0.0/24')
+ network_settings = SubnetConfig(name='network-name', subnet_settings=[subnet_settings])
sec_grp_name = 'sec-grp-name'
rule_settings = SecurityGroupRuleSettings(name=sec_grp_name, direction=Direction.ingress)
@@ -343,7 +370,7 @@ Create Router
- Router - snaps.openstack.create\_router.OpenStackRouter
- - snaps.openstack.create\_router.RouterSettings
+ - snaps.openstack.router.RouterConfig
- name - the router name (required)
- project\_name - the name of the project (optional - can only be
@@ -356,30 +383,41 @@ Create Router
- internal\_subnets - list of subnet names to which this router
will connect (optional)
- port\_settings (list of optional
- snaps.openstack.create\_router.PortSettings objects) - creates
+ snaps.config.network.PortConfig objects) - creates
custom ports to internal subnets (similar to internal\_subnets
with more control)
- - name
- - network\_name
- - admin\_state\_up
+ - name - the port's display name
+ - network\_name - the name of the network on which to create the port
+ - admin\_state\_up - A boolean value denoting the administrative
+ status of the port (default = True)
- project\_name - the name of the project (optional - can only
be set by admin users)
- - mac\_address
- - ip\_addrs
- - fixed\_ips
- - security\_groups
- - allowed\_address\_pairs
- - opt\_value
- - opt\_name
- - device\_owner
- - device\_id
+ - mac\_address - the port's MAC address to set (optional and
+ recommended not to set this configuration value)
+ - ip\_addrs - list of dict() objects containing two keys 'subnet_name'
+ and 'ip' where the value of the 'ip' entry is the expected IP
+ address assigned. This value gets mapped to the fixed\_ips
+ attribute (optional)
+ - fixed\_ips - dict() where the key is the subnet ID and value is the
+ associated IP address to assign to the port (optional)
+ - security\_groups - list of security group IDs (not tested)
+ - allowed\_address\_pairs - A dictionary containing a set of zero or
+ more allowed address pairs. An address pair contains an IP address
+ and MAC address (optional)
+ - opt\_value - the extra DHCP option value (optional)
+ - opt\_name - the extra DHCP option name (optional)
+ - device\_owner - The ID of the entity that uses this port.
+ For example, a DHCP agent (optional)
+ - device\_id - The ID of the device that uses this port.
+ For example, a virtual server (optional)
.. code:: python
- from snaps.openstack.create_router import RouterSettings, OpenStackRouter
+ from snaps.config.router import RouterConfig
+ from snaps.openstack.create_router import OpenStackRouter
- router_settings = RouterSettings(name='router-name', external_gateway='external')
+ router_settings = RouterConfig(name='router-name', external_gateway='external')
router_creator = OpenStackRouter(os_creds, router_settings)
router_creator.create()
@@ -389,6 +427,122 @@ Create Router
# Cleanup
router_creator.clean()
+Create QoS Spec
+---------------
+
+- Volume Type - snaps.openstack.create\_qos.OpenStackQoS
+
+ - snaps.openstack.qos.QoSConfig
+
+ - name - the volume type's name (required)
+ - consumer - the qos's consumer type of the enum type Consumer (required)
+ - specs - freeform dict() to be added as 'specs' (optional)
+
+.. code:: python
+
+ from snaps.openstack.qos import QoSConfig
+ from snaps.openstack.create_qos import OpenStackQoS
+
+ qos_settings = QoSConfig(name='stack-name', consumer=Consumer.front-end)
+ qos_creator = OpenStackQoS(os_creds, vol_type_settings)
+ qos_creator.create()
+
+ # Perform logic
+ ...
+
+ # Cleanup
+ qos_creator.clean()
+
+Create Volume Type
+------------------
+
+- Volume Type - snaps.openstack.create\_volume\_type.OpenStackVolumeType
+
+ - snaps.config.volume\_type.VolumeTypeConfig
+
+ - name - the volume type's name (required)
+ - description - the volume type's description (optional)
+ - encryption - instance or config for VolumeTypeEncryptionConfig (optional)
+ - qos\_spec\_name - name of the QoS Spec to associate (optional)
+ - public - instance or config for VolumeTypeEncryptionConfig (optional)
+
+.. code:: python
+
+ from snaps.config.volume_type import VolumeTypeConfig
+ from snaps.openstack.create_volume_type import OpenStackVolumeType
+
+ vol_type_settings = VolumeTypeConfig(name='stack-name')
+ vol_type_creator = OpenStackHeatStack(os_creds, vol_type_settings)
+ vol_type_creator.create()
+
+ # Perform logic
+ ...
+
+ # Cleanup
+ vol_type_creator.clean()
+
+Create Volume
+-------------
+
+- Volume - snaps.openstack.create\_volume.OpenStackVolume
+
+ - snaps.config.volume.VolumeConfig
+
+ - name - the volume type's name (required)
+ - description - the volume type's description (optional)
+ - size - size of volume in GB (default = 1)
+ - image_name - when a glance image is used for the image source (optional)
+ - type\_name - the associated volume's type name (optional)
+ - availability\_zone - the name of the compute server on which to
+ deploy the volume (optional)
+ - multi_attach - when true, volume can be attached to more than one
+ server (default = False)
+
+.. code:: python
+
+ from snaps.config.volume import VolumeConfig
+ from snaps.openstack.create\_volume import OpenStackVolume
+
+ vol_settings = VolumeConfig(name='stack-name')
+ vol_creator = OpenStackVolume(os_creds, vol_settings)
+ vol_creator.create()
+
+ # Perform logic
+ ...
+
+ # Cleanup
+ vol_type_creator.clean()
+
+Create Heat Stack
+-----------------
+
+- Heat Stack - snaps.openstack.create\_stack.OpenStackHeatStack
+
+ - snaps.config.stack.StackConfig
+
+ - name - the stack's name (required)
+ - template - the heat template in dict() format (required when
+ template_path is None)
+ - template\_path - the location of the heat template file (required
+ when template is None)
+ - env\_values - dict() of strings for substitution of template
+ default values (optional)
+
+.. code:: python
+
+ from snaps.config.stack import StackConfig
+ from snaps.openstack.create_stack import OpenStackHeatStack
+
+ stack_settings = StackConfig(name='stack-name', template_path='/tmp/template.yaml')
+ stack_creator = OpenStackHeatStack(os_creds, stack_settings)
+ stack_creator.create()
+
+ # Perform logic
+ ...
+
+ # Cleanup
+ stack_creator.clean()
+
Create VM Instance
------------------
@@ -399,7 +553,7 @@ Create VM Instance
- name - the name of the VM (required)
- flavor - the name of the flavor (required)
- port\_settings - list of
- snaps.openstack.create\_network.PortSettings objects where each
+ snaps.config.network.PortConfig objects where each
denote a NIC (see above in create router section for details)
API does not require, but newer NFVIs now require VMs have at
least one network
@@ -434,17 +588,17 @@ Create VM Instance
- userdata - the cloud-init script to execute after VM has been
started
- - image\_settings - see snaps.openstack.create\_image.ImageSettings
+ - image\_settings - see snaps.config.image.ImageConfig
above (required)
- keypair\_settings - see
- snaps.openstack.create\_keypairs.KeypairSettings above (optional)
+ snaps.openstack.keypair.KeypairConfig above (optional)
.. code:: python
from snaps.openstack.create_instance import VmInstanceSettings, FloatingIpSettings, OpenStackVmInstance
- from snaps.openstack.create_network import PortSettings
+ from snaps.config.network import PortConfig
- port_settings = PortSettings(name='port-name', network_name=network_settings.name)
+ port_settings = PortConfig(name='port-name', network_name=network_settings.name)
floating_ip_settings = FloatingIpSettings(name='fip1', port_name=port_settings.name, router_name=router_settings.name)
instance_settings = VmInstanceSettings(name='vm-name', flavor='flavor_settings.name', port_settings=[port_settings],
floating_ip_settings=[floating_ip_settings])
@@ -503,7 +657,12 @@ an example of this pattern as this is the only API where SNAPS is
supporting more than one version)
- snaps.openstack.utils.keystone\_utils - for calls to the Keystone
- APIs
+ APIs (support for versions 2 & 3)
- snaps.openstack.utils.glance\_utils - for calls to the Glance APIs
+ (support for versions 1 & 2)
- snaps.openstack.utils.neutron\_utils - for calls to the Neutron APIs
-- snaps.openstack.utils.nova\_utils - for calls to the Nova APIs
+ (version 2)
+- snaps.openstack.utils.nova\_utils - for calls to the Nova APIs (version 2)
+- snaps.openstack.utils.heat\_utils - for calls to the Heat APIs (version 1)
+- snaps.openstack.utils.cinder\_utils - for calls to the Cinder APIs
+ (support for versions 2 & 3)
diff --git a/docs/how-to-use/Testing.rst b/docs/how-to-use/Testing.rst
index 92340ab..8e08abb 100644
--- a/docs/how-to-use/Testing.rst
+++ b/docs/how-to-use/Testing.rst
@@ -26,6 +26,8 @@ Execute the tests
| \* -f [optional - When set, will execute tests requiring Floating
IPS]
| \* -im [optional - File containing image endpoints to override
-| \* -fm [optional - JSON string containing a dict() for flavor metadata default='{\"hw:mem_page_size\": \"any\"}']
+| \* -fm [optional - JSON string containing a dict(): - for flavor metadata default='{\"hw:mem_page_size\": \"any\"}'
+ - for values of basic elements (e.g. ram, vcpu, disk, etc) could be added.
+ As result the hard coded values of those elements will be overwritten by the new ones]
| \* -ci [optional - runs the tests required by SNAPS-OO CI]
| \* -r [optional with default value of '1' - The number of test iterations to execute]
diff --git a/docs/how-to-use/UnitTests.rst b/docs/how-to-use/UnitTests.rst
index 5fb04db..5bd4f08 100644
--- a/docs/how-to-use/UnitTests.rst
+++ b/docs/how-to-use/UnitTests.rst
@@ -36,11 +36,17 @@ OSCredsUnitTests
Ensures that all required members are included when constructing a
OSCreds object
+SecurityGroupRuleConfigUnitTests
+--------------------------------
+
+Ensures that all required members are included when constructing a
+SecurityGroupRuleConfig object
+
SecurityGroupRuleSettingsUnitTests
----------------------------------
Ensures that all required members are included when constructing a
-SecurityGroupRuleSettings object
+deprecated SecurityGroupRuleSettings object
SecurityGroupRuleDomainObjectTests
----------------------------------
@@ -48,11 +54,17 @@ SecurityGroupRuleDomainObjectTests
Ensures that all required members are included when constructing a
SecurityGroupRule domain object
+SecurityGroupConfigUnitTests
+----------------------------
+
+Ensures that all required members are included when constructing a
+SecuirtyGroupConfig object
+
SecurityGroupSettingsUnitTests
------------------------------
Ensures that all required members are included when constructing a
-SecuirtyGroupSettings object
+deprecated SecuirtyGroupSettings object
SecurityGroupDomainObjectTests
------------------------------
@@ -60,11 +72,17 @@ SecurityGroupDomainObjectTests
Ensures that all required members are included when constructing a
SecurityGroup domain object
+ImageConfigUnitTests
+--------------------
+
+Ensures that all required members are included when constructing a
+ImageConfig object
+
ImageSettingsUnitTests
----------------------
Ensures that all required members are included when constructing a
-ImageSettings object
+ImageSettings object (deprecated see ImageConfigUnitTests)
ImageDomainObjectTests
----------------------
@@ -72,11 +90,17 @@ ImageDomainObjectTests
Ensures that all required members are included when constructing a
Image domain object
+FlavorConfigUnitTests
+---------------------
+
+Ensures that all required members are included when constructing a
+FlavorConfig object
+
FlavorSettingsUnitTests
-----------------------
Ensures that all required members are included when constructing a
-FlavorSettings object
+deprecated FlavorSettings object
FlavorDomainObjectTests
-----------------------
@@ -84,11 +108,17 @@ FlavorDomainObjectTests
Ensures that all required members are included when constructing a
Flavor domain object
+KeypairConfigUnitTests
+----------------------
+
+Ensures that all required members are included when constructing a
+KeypairConfig object
+
KeypairSettingsUnitTests
------------------------
Ensures that all required members are included when constructing a
-KeypairSettings object
+deprecated KeypairSettings object
KeypairDomainObjectTests
------------------------
@@ -96,11 +126,17 @@ KeypairDomainObjectTests
Ensures that all required members are included when constructing a
Keypair domain object
+UserConfigUnitTests
+-------------------
+
+Ensures that all required members are included when constructing a
+UserConfig object
+
UserSettingsUnitTests
---------------------
Ensures that all required members are included when constructing a
-UserSettings object
+deprecated UserSettings object
UserDomainObjectTests
---------------------
@@ -108,11 +144,17 @@ UserDomainObjectTests
Ensures that all required members are included when constructing a
User domain object
+ProjectConfigUnitTests
+----------------------
+
+Ensures that all required members are included when constructing a
+ProjectConfig object
+
ProjectSettingsUnitTests
------------------------
Ensures that all required members are included when constructing a
-ProjectSettings object
+deprecated ProjectSettings object
ProjectDomainObjectTests
------------------------
@@ -144,11 +186,17 @@ RoleDomainObjectTests
Ensures that all required members are included when constructing a
Role domain object
+NetworkConfigUnitTests
+----------------------
+
+Ensures that all required members are included when constructing a
+NetworkConfig object
+
NetworkSettingsUnitTests
------------------------
Ensures that all required members are included when constructing a
-NetworkSettings object
+deprecated NetworkSettings object
NetworkObjectTests
------------------
@@ -156,11 +204,17 @@ NetworkObjectTests
Ensures that all required members are included when constructing a
Network domain object
+SubnetConfigUnitTests
+---------------------
+
+Ensures that all required members are included when constructing a
+SubnetConfig object
+
SubnetSettingsUnitTests
-----------------------
Ensures that all required members are included when constructing a
-SubnetSettings object
+deprecated SubnetSettings object
SubnetObjectTests
-----------------
@@ -168,11 +222,17 @@ SubnetObjectTests
Ensures that all required members are included when constructing a
Subnet domain object
+PortConfigUnitTests
+-------------------
+
+Ensures that all required members are included when constructing a
+PortConfig object
+
PortSettingsUnitTests
---------------------
Ensures that all required members are included when constructing a
-PortSettings object
+deprecated PortSettings object
PortDomainObjectTests
---------------------
@@ -180,11 +240,17 @@ PortDomainObjectTests
Ensures that all required members are included when constructing a
Port domain object
+RouterConfigUnitTests
+---------------------
+
+Ensures that all required members are included when constructing a
+RouterConfig object
+
RouterSettingsUnitTests
-----------------------
Ensures that all required members are included when constructing a
-RouterSettings object
+deprecated RouterSettings object
RouterDomainObjectTests
-----------------------
@@ -198,11 +264,17 @@ InterfaceRouterDomainObjectTests
Ensures that all required members are included when constructing a
InterfaceRouter domain object
+StackConfigUnitTests
+--------------------
+
+Ensures that all required members are included when constructing a
+StackConfig object
+
StackSettingsUnitTests
----------------------
Ensures that all required members are included when constructing a
-StackSettings object
+deprecated StackSettings object
StackDomainObjectTests
----------------------
@@ -222,11 +294,83 @@ OutputDomainObjectTests
Ensures that all required members are included when constructing a
Output domain object (for Heat)
+VolumeConfigUnitTests
+---------------------
+
+Ensures that all required members are included when constructing a
+VolumeConfig object
+
+VolumeSettingsUnitTests
+-----------------------
+
+Ensures that all required members are included when constructing a
+deprecated VolumeSettings object
+
+VolumeDomainObjectTests
+-----------------------
+
+Ensures that all required members are included when constructing a
+Volume domain object (for Cinder)
+
+VolumeTypeConfigUnitTests
+-------------------------
+
+Ensures that all required members are included when constructing a
+VolumeTypeConfig object
+
+VolumeTypeSettingsUnitTests
+---------------------------
+
+Ensures that all required members are included when constructing a
+deprecated VolumeTypeSettings object
+
+VolumeTypeDomainObjectTests
+---------------------------
+
+Ensures that all required members are included when constructing a
+VolumeType domain object (for Cinder)
+
+VolumeTypeEncryptionObjectTests
+-------------------------------
+
+Ensures that all required members are included when constructing a
+VolumeTypeEncryption domain object (for Cinder)
+
+QoSConfigUnitTests
+------------------
+
+Ensures that all required members are included when constructing a
+QoSConfig object
+
+QoSSettingsUnitTests
+--------------------
+
+Ensures that all required members are included when constructing a
+deprecated QoSSettings object
+
+QoSSpecDomainObjectTests
+------------------------
+
+Ensures that all required members are included when constructing a
+QoSSpec domain object (for Cinder)
+
+VolumeDomainObjectTests
+-----------------------
+
+Ensures that all required members are included when constructing a
+Volume domain object (for Cinder)
+
+FloatingIpConfigUnitTests
+-------------------------
+
+Ensures that all required members are included when constructing a
+FloatingIpConfig object
+
FloatingIpSettingsUnitTests
---------------------------
Ensures that all required members are included when constructing a
-FloatingIpSettings object
+depecated FloatingIpSettings object
FloatingIpDomainObjectTests
---------------------------
@@ -234,14 +378,46 @@ FloatingIpDomainObjectTests
Ensures that all required members are included when constructing a
FloatingIp domain object
+VmInstanceConfigUnitTests
+-------------------------
+
+Ensures that all required members are included when constructing a
+VmInstanceConfig object
+
VmInstanceSettingsUnitTests
---------------------------
Ensures that all required members are included when constructing a
-VmInstanceSettings object
+deprecated VmInstanceSettings object
VmInstDomainObjectTests
-----------------------
Ensures that all required members are included when constructing a
VmInst domain object
+
+ClusterTemplateConfigUnitTests
+------------------------------
+
+Ensures that all required members are included when constructing a
+ClusterTemplateConfig object
+
+ClusterTemplateUnitTests
+------------------------
+
+Ensures that all required members are included when constructing a
+ClusterTemplate object
+
+SettingsUtilsUnitTests
+----------------------
+
+Ensures that the settings_utils.py#create_volume_config() function properly
+maps a snaps.domain.Volume object correctly to a
+snaps.config.volume.VolumeConfig object as well as a
+snaps.domain.VolumeType object to a
+snaps.config.volume.VolumeConfig object
+
+
+Ensures that the settings_utils.py#create_flavor_config() function properly
+maps a snaps.domain.Flavor object correctly to a
+snaps.config.flavor.FlavorConfig object \ No newline at end of file
diff --git a/docs/how-to-use/VirtEnvDeploy.rst b/docs/how-to-use/VirtEnvDeploy.rst
index dd95202..0345f34 100644
--- a/docs/how-to-use/VirtEnvDeploy.rst
+++ b/docs/how-to-use/VirtEnvDeploy.rst
@@ -3,9 +3,10 @@ Try an example
Use launcher.py to deploy and clean up example environments. These examples are described in YAML files.
-#. Add your OpenStack connection information to the deploy-complex-network.yaml.
+#. Add your OpenStack connection information.
- Edit <path to repo>/examples/complex-network/deploy-complex-network.yaml
+ Edit <path to repo>/examples/inst-w-volume/deploy-env.yaml with your OpenStack
+ credentials and authorization URL
- openstack: the top level tag that denotes configuration for the OpenStack components
@@ -16,8 +17,7 @@ Use launcher.py to deploy and clean up example environments. These examples are
- auth\_url: - the URL to the OpenStack APIs (required)
- project\_name: - the name of the OpenStack project for the user
(required)
- - http\_proxy: - the {{ host }}:{{ port }} of the proxy server the
- HTTPPhotoman01(optional)
+ - http\_proxy: - the {{ host }}:{{ port }} of the proxy server (optional)
#. Go to the examples directory.
@@ -29,13 +29,13 @@ Use launcher.py to deploy and clean up example environments. These examples are
::
- python launch.py -t ./complex-network/deploy-complex-network.yaml -d
+ python launch.py -t ./inst-w-volume/deploy-vm-with-volume.yaml -e ./inst-w-volume/deploy-env.yaml -d
#. Clean the deployment.
::
- python launch.py -t ./complex-network/deploy-complex-network.yaml -c
+ python launch.py -t ./complex-network/deploy-complex-network.yaml -e ./inst-w-volume/deploy-env.yaml -c
#. Customize the deployment by changing the yaml file.
@@ -46,183 +46,378 @@ Use launcher.py to deploy and clean up example environments. These examples are
- openstack: the top level tag that denotes configuration for the
OpenStack components
- - connection: - contains the credentials and endpoints required to
- connect with OpenStack
- - username: - the project's user (required)
- - password: - the tentant's user password (required)
- - auth\_url: - the URL to the OpenStack APIs (required)
- - project\_name: - the name of the OpenStack project for the user
- (required)
- - http\_proxy: - the {{ host }}:{{ port }} of the proxy server the
- HTTPPhotoman01(optional)
- - images: - describes each image
- - image:
-
- - name: The unique image name. If the name already exists for
- your project, a new one will not be created (required)
- - format: The format type of the image i.e. qcow2 (required)
- - download\_url: The HTTP download location of the image file
- (required)
- - nic\_config\_pb\_loc: The file location relative to the CWD
- (python directory) to the Ansible Playbook used to configure
- VMs with more than one port. VMs get their first NIC configured
- for free while subsequent ones are not. This value/script will
- only be leveraged when necessary. Centos has been supported
- with
- "provisioning/ansible/centos-network-setup/configure\_host.yml".
+ - connections: the different connections/credentials to be used by the
+ launcher application
+
+ - connection: the credentials and endpoints required to connect to an
+ OpenStack project/tenant
+
+ - name: the name of the credentials for use when creating objects (required)
+ - username: the project's user (required)
+ - password: the tentant's user password (required)
+ - auth\_url: the URL to the OpenStack APIs (required)
+ - project\_name: the name of the OpenStack project for the user
+ (required)
+ - identity\_api\_version: the Keystone client version to use (default = 2)
+ - image\_api\_version: the Glance client version to use (default = 2)
+ - network\_api\_version: the Neutron client version to use (default = 2)
+ - compute\_api\_version: the Nova client version to use (default = 2)
+ - heat\_api\_version: the Heat client version to use (default = 1)
+ - volume\_api\_version: the Cinder client version to use (default = 2)
+ - user\_domain\_id: the user domain ID to use (default = 'default')
+ - user\_domain\_name: the user domain name to use (default = 'Default')
+ - project\_domain\_id: the project domain ID to use (default = 'default')
+ - project\_domain\_name: the project domain name to use (default = 'Default')
+ - interface: Used to specify the endpoint type for keystone (default = 'public')
+ - cacert: True for https or the certification file location (default = False)
+ - region\_name: the region (default = None)
+ - proxy\_settings: for accessing APIs hidden behind an HTTP proxy
+
+ - host: hostname or IP of HTTP proxy host (required)
+ - port: port number of the HTTP proxy server (required)
+ - http\_host: hostname or IP of HTTPS proxy host (default = host)
+ - port: port number of the HTTPS proxy server (default = port)
+ - ssh\_proxy\_cmd: the OpenSSH command used to access the SSH port
+ of a VM (optional)
+
+ - projects: the projects/tenants to create
+
+ - project: a project/tenant to create (admin user credentials required)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - name: the project's name (required)
+ - domain or domain_name: the project's domain name (default = 'Default')
+ - description: the description (optional)
+ - users: a list of users to associate to the project (optional)
+ - enabled: when True the project will be enabled on creation (default = True)
+
+ - users: the users to create
+
+ - user: a user to create (admin user credentials required)
+
+ - os\_creds\_name: the connection name (required)
+ - name: the username (required)
+ - password: the user's password (required)
+ - project\_name: the user's primary project name (optional)
+ - domain\_name: the user's domain name (default = 'Default')
+ - email: the user's email address (optional)
+ - roles: dict where key is the role's name and value is the name
+ of the project to associate with the role (optional)
+
+ - flavors: the flavors to create
+
+ - flavor: a flavor to create (admin user credentials required)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - name: the name (required)
+ - flavor\_id: the string ID (default 'auto')
+ - ram: the required RAM in MB (required)
+ - disk: the size of the root disk in GB (required)
+ - vcpus: the number of virtual CPUs (required)
+ - ephemeral: the size of the ephemeral disk in GB (default 0)
+ - swap: the size of the dedicated swap disk in GB (default 0)
+ - rxtx\_factor: the receive/transmit factor to be set on ports if
+ backend supports QoS extension (default 1.0)
+ - is\_public: denotes whether or not the flavor is public (default = True)
+ - metadata: - freeform dict() for special metadata (optional)
+ - freeform dict() for values of basic elements
+ (e.g. ram, vcpu, disk, etc) could be added.
+ As result the hard coded values of those elements will be
+ overwritten by the new ones (optional)
+
+ - qos_specs: the QoS Specs to create
+
+ - qos_spec: a QoS Spec to create (admin user credentials required)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - name: the name (required)
+ - consumer: enumerations: 'front-end', 'back-end', 'both' (required)
+ - specs: dict of custom values (optional)
+
+ - volume_types: the Volume Type to create
+
+ - volume_type: a Volume Type to create (admin user credentials required)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - name: the name (required)
+ - description: the description (optional)
+ - qos_spec_name: the name of the associate QoS Spec (optional)
+ - public: visibility (default - False)
+ - encryption: the encryption settings (optional)
+
+ - name: the name (required)
+ - provider_class: the provider class (required i.e. LuksEncryptor)
+ - control_location: enumerations: 'front-end', 'back-end' (required)
+ - cipher: the encryption algorithm/mode to use (optional)
+ - key_size: the size of the encryption key, in bits (optional)
+
+ - volumes: the Volume to create
+
+ - volume: a Volume to create
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: the name (required)
+ - description: the description (optional)
+ - size: the volume size in GB (default = 1)
+ - image_name: the image name to leverage (optional)
+ - type_name: the volume type name to associate (optional)
+ - availability_zone: the zone name on which to deploy (optional)
+ - multi_attach: when true, volume can be attached to more than one VM
+ (default = False)
+
+ - images: describes each image to create
+
+ - image:
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: The unique image name. If the name already exists for
+ your project, a new one will not be created (required)
+ - image\_user: the image's default sudo user (required)
+ - format or img\_format: the image format type (required i.e. qcow2)
+ - url or download\_url: The HTTP download location of the image file
+ (required when "image_file" below has not been configured)
+ - image\_file: the image file location (required when "url" has not
+ been configured)
+ - kernel\_image\_settings: the settings for a kernel image (optional)
+ - ramdisk\_image\_settings: the settings for a kernel image (optional)
+ - public: publically visibile when True (default = True)
- networks:
- - network:
-
- - name: The name of the network to be created. If one already
- exists, a new one will not be created (required)
- - admin\_state\_up: T\|F (default True)
- - shared: (optional)
- - project\_name: Name of the project who owns the network. Note:
- only administrative users can specify projects other than their
- own (optional)
- - external: T\|F whether or not network is external (default
- False)
- - network\_type: The type of network to create. (optional)
- - subnets:
- - subnet:
-
- - name: The name of the network to be created. If one already
- exists, a new one will not be created. Note: although
- OpenStack allows for multiple subnets to be applied to any
- given network, we have not included support as our current
- use cases does not utilize this functionality (required)
- - cidr: The subnet mask value (required)
- - dns\_nameservers: A list of IP values used for DNS
- resolution (default: 8.8.8.8)
- - ip\_version: 4\|6 (default: 4)
- - project\_name: Name of the project who owns the network.
- Note: only administrative users can specify projects other
- than their own (optional)
- - start: The start address for allocation\_pools (optional)
- - end: The ending address for allocation\_pools (optional)
- - gateway\_ip: The IP address to the gateway (optional)
- - enable\_dhcp: T\|F (optional)
- - dns\_nameservers: List of DNS server IPs
- - host\_routes: A list of host route dictionaries (optional)
- i.e.:
- ``yaml "host_routes":[ { "destination":"0.0.0.0/0", "nexthop":"123.456.78.9" }, { "destination":"192.168.0.0/24", "nexthop":"192.168.0.1" } ]``
- - destination: The destination for a static route (optional)
- - nexthop: The next hop for the destination (optional)
- - ipv6\_ra\_mode: Valid values: "dhcpv6-stateful",
- "dhcpv6-stateless", and "slaac" (optional)
- - ipv6\_address\_mode: Valid values: "dhcpv6-stateful",
- "dhcpv6-stateless", and "slaac" (optional)
+ - network:
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: The name of the network to be created. If one already
+ exists, a new one will not be created (required)
+ - admin\_state\_up: T\|F (default True)
+ - shared: (optional)
+ - project\_name: Name of the project who owns the network. Note:
+ only administrative users can specify projects other than their
+ own (optional)
+ - external: T\|F whether or not network is external (default False)
+ - network\_type: The type of network to create (optional)
+ - physical\_network: the name of the physical network
+ (required when network_type is 'flat')
+ - segmentation\_id: the id of the segmentation
+ (required when network_type is 'vlan')
+ - subnets:
+ - subnet:
+
+ - name: The name of the network to be created. If one already
+ exists, a new one will not be created. Note: although
+ OpenStack allows for multiple subnets to be applied to any
+ given network, we have not included support as our current
+ use cases does not utilize this functionality (required)
+ - cidr: The subnet mask value (required)
+ - dns\_nameservers: A list of IP values used for DNS
+ resolution (default: 8.8.8.8)
+ - ip\_version: 4\|6 (default: 4)
+ - project\_name: Name of the project who owns the network.
+ Note: only administrative users can specify projects other
+ than their own (optional)
+ - start: The start address for allocation\_pools (optional)
+ - end: The ending address for allocation\_pools (optional)
+ - gateway\_ip: The IP address to the gateway (optional)
+ - enable\_dhcp: T\|F (optional)
+ - dns\_nameservers: List of DNS server IPs (default = ['8.8.8.8']
+ - host\_routes: A list of host route dictionaries (optional)
+ i.e.:
+ ``yaml "host_routes":[ { "destination":"0.0.0.0/0", "nexthop":"123.456.78.9" }, { "destination":"192.168.0.0/24", "nexthop":"192.168.0.1" } ]``
+ - destination: The destination for a static route (optional)
+ - nexthop: The next hop for the destination (optional)
+ - ipv6\_ra\_mode: Valid values: "dhcpv6-stateful",
+ "dhcpv6-stateless", and "slaac" (optional)
+ - ipv6\_address\_mode: Valid values: "dhcpv6-stateful",
+ "dhcpv6-stateless", and "slaac" (optional)
+
+ - security_groups:
+
+ - security_group:
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: The name of the security group to be created (required)
+ - description: The security group's description (optional)
+ - project\_name: Name of the project who owns the security group (optional)
+ - rule\_settings: List of rules to place onto security group (optional)
+
+ - description: the rule's description (optional)
+ - protocol: rule's protcol ('icmp' or 'tcp' or 'udp' or 'null')
+ - ethertype: rule's ethertype ('4' or '6')
+ - port\_range\_min: The minimum port number in the range that is
+ matched by the security group rule. When the protocol is 'tcp'
+ or 'udp', this value must be <= 'port_range_max' (optional)
+ - port\_range\_max: The maximum port number in the range that is
+ matched by the security group rule. When the protocol is 'tcp'
+ or 'udp', this value must be <= 'port_range_max' (optional)
+ - remote\_ip\_prefix: The remote IP prefix to associate with this
+ metering rule packet (optional)
- routers:
- router:
- - name: The name of the router to be created. If one already
- exists, a new one will not be created (required)
- - project\_name: Name of the project who owns the network. Note:
- only administrative users can specify projects other than their
- own (optional)
- - internal\_subnets: A list of subnet names on which the router
- will be placed (optional)
- - external\_gateway: A dictionary containing the external gateway
- parameters: "network\_id", "enable\_snat",
- "external\_fixed\_ips" (optional)
- - interfaces: A list of port interfaces to create to other
- subnets (optional)
-
- - port (Leverages the same class/structure as port objects on
- VM instances. See port definition below for a
- full accounting of the port attributes. The ones listed
- below are generally used for routers)
-
- - name: The name given to the new port (must be unique for
- project) (required)
- - network\_name: The name of the new port's network
- (required)
- - ip\_addrs: A list of k/v pairs (optional)
- - subnet\_name: the name of a subnet that is on the port's
- network
- - ip: An IP address of the associated subnet to assign to
- the new port (optional but generally required for router
- interfaces)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: The name of the router to be created (required)
+ - project\_name: Name of the project who owns the network (optional)
+ - external\_gateway: Name of the external network to which to route
+ (optional)
+ - admin\_state\_up: T\|F (default True)
+ - external\_fixed\_ids: Dictionary containing the IP address
+ parameters (optional)
+ - internal\_subnets: List of subnet names to which to connect this
+ router (optional)
+
+ - port_settings (Leverages the same class/structure as port objects on
+ VM instances. See port definition below for a
+ full accounting of the port attributes. The ones listed
+ below are generally used for routers)
+
+ - name: The name given to the new port (required and must be
+ unique for project)
+ - network\_name: The name of the network on which to create
+ the port (optional)
+ - admin\_state\_up: T\|F (default True)
+ - project\_name: Name of the project who owns the network (optional)
+ - mac\_address: The port's MAC address (optional)
+ - ip\_addrs: A list of k/v pairs (optional)
+ - security\_groups: a list of names of the the security groups
+ to apply to the port
+ - opt\_value: The extra DHCP option value (optional)
+ - opt\_name: The extra DHCP option name (optional)
- keypairs:
- keypair:
- - name: The name of the keypair to be created. If one already
- exists, a new one will not be created but simply loaded from
- its configured file location (required)
- - public\_filepath: The path to where the generated public key
- will be stored if it does not exist (optional but really
- required for provisioning purposes)
- - private\_filepath: The path to where the generated private key
- will be stored if it does not exist (optional but really
- required for provisioning purposes)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: The name of the keypair to be created. If one already
+ exists, a new one will not be created but simply loaded from
+ its configured file location (required)
+ - public\_filepath: The path to where the generated public key
+ will be stored if it does not exist (optional but really
+ required for provisioning purposes)
+ - private\_filepath: The path to where the generated private key
+ will be stored if it does not exist (optional but really
+ required for provisioning purposes)
- instances:
- instance:
- - name: The unique instance name for project. (required)
- - flavor: Must be one of the preconfigured flavors (required)
- - imageName: The name of the image to be used for deployment
- (required)
- - keypair\_name: The name of the keypair to attach to instance
- (optional but required for NIC configuration and Ansible
- provisioning)
- - sudo\_user: The name of a sudo\_user that is attached to the
- keypair (optional but required for NIC configuration and
- Ansible provisioning)
- - vm\_boot\_timeout: The number of seconds to block waiting for
- an instance to deploy and boot (default 900)
- - vm\_delete\_timeout: The number of seconds to block waiting for
- an instance to be deleted (default 300)
- - ssh\_connect\_timeout: The number of seconds to block waiting
- for an instance to achieve an SSH connection (default 120)
- - ports: A list of port configurations (should contain at least
- one)
- - port: Denotes the configuration of a NIC
-
- - name: The unique port name for project (required)
- - network\_name: The name of the network to which the port is
- attached (required)
- - ip\_addrs: Static IP addresses to be added to the port by
- subnet (optional)
- - subnet\_name: The name of the subnet
- - ip: The assigned IP address (when null, OpenStack will
- assign an IP to the port)
- - admin\_state\_up: T\|F (default True)
- - project\_name: The name of the project who owns the network.
- Only administrative users can specify a the project ID other
- than their own (optional)
- - mac\_address: The desired MAC for the port (optional)
- - fixed\_ips: A dictionary that allows one to specify only a
- subnet ID, OpenStack Networking allocates an available IP
- from that subnet to the port. If you specify both a subnet
- ID and an IP address, OpenStack Networking tries to allocate
- the specified address to the port. (optional)
- - seurity\_groups: A list of security group IDs (optional)
- - allowed\_address\_pairs: A dictionary containing a set of
- zero or more allowed address pairs. An address pair contains
- an IP address and MAC address. (optional)
- - opt\_value: The extra DHCP option value (optional)
- - opt\_name: The extra DHCP option name (optional)
- - device\_owner: The ID of the entity that uses this port. For
- example, a DHCP agent (optional)
- - device\_id: The ID of the device that uses this port. For
- example, a virtual server (optional)
-
- - floating\_ips: list of floating\_ip configurations (optional)
-
- - floating\_ip:
- - name: Must be unique for VM instance (required)
- - port\_name: The name of the port requiring access to the
- external network (required)
- - subnet\_name: The name of the subnet contains the IP address on
- the port on which to create the floating IP (optional)
- - router\_name: The name of the router connected to an external
- network used to attach the floating IP (required)
- - provisioning: (True\|False) Denotes whether or not this IP can
- be used for Ansible provisioning (default True)
+
+ - os\_creds\_name: the connection name (default = 'default'
+ required or use "os\_user" below instead)
+ - os\_user: the connection from a new user defined in template
+ (required or use "os\_creds\_name" above
+
+ - name: the user's name (required)
+ - project\_name: the project name to use
+
+ - name: The unique instance name for project. (required)
+ - flavor: Must be one of the preconfigured flavors (required)
+ - imageName: The name of the image to be used for deployment
+ (required)
+ - keypair\_name: The name of the keypair to attach to instance
+ (optional but required for NIC configuration and Ansible
+ provisioning)
+ - sudo\_user: The name of a sudo\_user that is attached to the
+ keypair (optional but required for NIC configuration and
+ Ansible provisioning)
+ - vm\_boot\_timeout: The number of seconds to block waiting for
+ an instance to deploy and boot (default 900)
+ - vm\_delete\_timeout: The number of seconds to block waiting for
+ an instance to be deleted (default 300)
+ - ssh\_connect\_timeout: The number of seconds to block waiting
+ for an instance to achieve an SSH connection (default 120)
+ - ports: A list of port configurations (should contain at least
+ one)
+ - port: Denotes the configuration of a NIC
+
+ - name: The unique port name for project (required)
+ - network\_name: The name of the network to which the port is
+ attached (required)
+ - ip\_addrs: Static IP addresses to be added to the port by
+ subnet (optional)
+ - subnet\_name: The name of the subnet
+ - ip: The assigned IP address (when null, OpenStack will
+ assign an IP to the port)
+ - admin\_state\_up: T\|F (default True)
+ - project\_name: The name of the project who owns the network.
+ Only administrative users can specify a the project ID other
+ than their own (optional)
+ - mac\_address: The desired MAC for the port (optional)
+ - fixed\_ips: A dictionary that allows one to specify only a
+ subnet ID, OpenStack Networking allocates an available IP
+ from that subnet to the port. If you specify both a subnet
+ ID and an IP address, OpenStack Networking tries to allocate
+ the specified address to the port. (optional)
+ - seurity\_groups: A list of security group IDs (optional)
+ - allowed\_address\_pairs: A dictionary containing a set of
+ zero or more allowed address pairs. An address pair contains
+ an IP address and MAC address. (optional)
+ - opt\_value: The extra DHCP option value (optional)
+ - opt\_name: The extra DHCP option name (optional)
+ - device\_owner: The ID of the entity that uses this port. For
+ example, a DHCP agent (optional)
+ - device\_id: The ID of the device that uses this port. For
+ example, a virtual server (optional)
+
+ - floating\_ips: list of floating\_ip configurations (optional)
+
+ - floating\_ip:
+ - name: Must be unique for VM instance (required)
+ - port\_name: The name of the port requiring access to the
+ external network (required)
+ - subnet\_name: The name of the subnet contains the IP address on
+ the port on which to create the floating IP (optional)
+ - router\_name: The name of the router connected to an external
+ network used to attach the floating IP (required)
+ - provisioning: (True\|False) Denotes whether or not this IP can
+ be used for Ansible provisioning (default True)
- ansible: Each set of attributes below are contained in a list
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..aceb2fc
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,16 @@
+.. _snaps:
+
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. SPDX-License-Identifier CC-BY-4.0
+.. (c) Open Platform for NFV Project, Inc. and its contributors
+
+*********************************
+OPNFV Snaps
+*********************************
+
+.. toctree::
+ :numbered:
+ :maxdepth: 3
+
+ /how-to-use/index
diff --git a/docs/release/release-notes.rst b/docs/release/release-notes.rst
new file mode 100644
index 0000000..d682dd9
--- /dev/null
+++ b/docs/release/release-notes.rst
@@ -0,0 +1,25 @@
+=================
+SNAPS-OO in OPNFV
+=================
+
+Introduction
+============
+
+SNAPS-OO is a object-oriented library designed for managing objects deployed
+on OpenStack. In addition to its ability to deploy and manage objects, this
+project also contains many API and integration tests that can be used to
+validate your OpenStack cloud. Also included is a lightweight orchestrator that
+can be used for rapid prototyping virtual environments.
+
+Installation
+============
+
+To use SNAPS-OO, it must first be installed into your Python 2.7-3.5 runtime.
+To install:
+
+pip install -e <SNAPS-OO directory>
+
+Using SNAPS-OO
+==============
+
+Please refer to the RST documents in ../how-to-use \ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..9fde2df
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+lfdocs-conf
+sphinx_opnfv_theme
diff --git a/examples/complex-network/deploy-complex-network.yaml b/examples/complex-network/deploy-complex-network.yaml
index 7915c38..be38794 100644
--- a/examples/complex-network/deploy-complex-network.yaml
+++ b/examples/complex-network/deploy-complex-network.yaml
@@ -70,8 +70,14 @@ openstack:
name: mgr-router
external_gateway: external
internal_subnets:
- - mgr-subnet
- - mgr-subnet-2
+ - subnet:
+ project_name: admin
+ network_name: mgr-net
+ subnet_name: mgr-subnet
+ - subnet:
+ project_name: admin
+ network_name: mgr-net
+ subnet_name: mgr-subnet-2
interfaces:
- port:
name: mgr-router-to-site1
@@ -83,12 +89,18 @@ openstack:
name: site1-router
external_gateway: external
internal_subnets:
- - site1-subnet
+ - subnet:
+ project_name: admin
+ network_name: site1-net
+ subnet_name: site1-subnet
- router:
name: site2-router
external_gateway: external
internal_subnets:
- - site2-subnet
+ - subnet:
+ project_name: admin
+ network_name: site2-net
+ subnet_name: site2-subnet
- router:
name: site-to-site-router
interfaces:
diff --git a/examples/demo.py b/examples/demo.py
index 108bdc0..87c095b 100644
--- a/examples/demo.py
+++ b/examples/demo.py
@@ -1,22 +1,29 @@
import logging
+
+from snaps.config.vm_inst import VmInstanceConfig
+
logging.basicConfig(level=logging.INFO)
# Credentials
from snaps.openstack.os_credentials import OSCreds, ProxySettings
-proxy_settings = ProxySettings(host='10.197.123.27', port='3128',
- ssh_proxy_cmd='/usr/local/bin/corkscrew 10.197.123.27 3128 %h %p')
+proxy_settings = ProxySettings(
+ host='10.197.123.27', port='3128',
+ ssh_proxy_cmd='/usr/local/bin/corkscrew 10.197.123.27 3128 %h %p')
-os_creds = OSCreds(username='admin', password='cable123', auth_url='http://192.168.67.10:5000/v2.0/',
- project_name='admin', proxy_settings=proxy_settings)
+os_creds = OSCreds(
+ username='admin', password='cable123',
+ auth_url='http://192.168.67.10:5000/v2.0/', project_name='admin',
+ proxy_settings=proxy_settings)
# Images
-from snaps.openstack.create_image import ImageSettings, OpenStackImage
+from snaps.openstack.create_image import OpenStackImage
+from snaps.config.image import ImageConfig
-image_settings = ImageSettings(name='cirros-test', image_user='cirros', img_format='qcow2',
- url='http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img')
+image_settings = ImageConfig(name='cirros-test', image_user='cirros', img_format='qcow2',
+ url='http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img')
image = OpenStackImage(os_creds, image_settings)
image.create()
@@ -24,27 +31,33 @@ image.create()
# Network
-from snaps.openstack.create_network import NetworkSettings, SubnetSettings, OpenStackNetwork
+from snaps.config.network import NetworkConfig, SubnetConfig
+from snaps.openstack.create_network import OpenStackNetwork
-subnet_settings = SubnetSettings(name='test-subnet', cidr='10.0.0.1/24')
-network_settings = NetworkSettings(name='test-net', subnet_settings=[subnet_settings])
+subnet_settings = SubnetConfig(name='test-subnet', cidr='10.0.0.1/24')
+network_settings = NetworkConfig(
+ name='test-net', subnet_settings=[subnet_settings])
network = OpenStackNetwork(os_creds, network_settings)
network.create()
# Flavors
-from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor
+from snaps.config.flavor import FlavorConfig
+from snaps.openstack.create_flavor import OpenStackFlavor
-flavor_settings = FlavorSettings(name='test-flavor', ram=256, disk=10, vcpus=2)
+flavor_settings = FlavorConfig(name='test-flavor', ram=256, disk=10, vcpus=2)
flavor = OpenStackFlavor(os_creds, flavor_settings)
flavor.create()
# Instances
-from snaps.openstack.create_network import PortSettings
-from snaps.openstack.create_instance import VmInstanceSettings, OpenStackVmInstance
-
-port_settings = PortSettings(name='test-port', network_name=network_settings.name)
-instance_settings = VmInstanceSettings(name='test-inst', flavor=flavor_settings.name, port_settings=[port_settings])
+from snaps.config.network import PortConfig
+from snaps.openstack.create_instance import OpenStackVmInstance
+
+port_settings = PortConfig(
+ name='test-port', network_name=network_settings.name)
+instance_settings = VmInstanceConfig(
+ name='test-inst', flavor=flavor_settings.name,
+ port_settings=[port_settings])
vm_inst = OpenStackVmInstance(os_creds, instance_settings, image_settings)
vm_inst.create(block=True)
diff --git a/examples/external-network/deploy-ext-net.yaml b/examples/external-network/deploy-ext-net.yaml
index ac5e214..b5f00b1 100644
--- a/examples/external-network/deploy-ext-net.yaml
+++ b/examples/external-network/deploy-ext-net.yaml
@@ -52,7 +52,10 @@ openstack:
name: ext-net-router
external_gateway: ext-net
internal_subnets:
- - internal-subnet
+ - subnet:
+ project_name: admin
+ network_name: internal-net
+ subnet_name: internal-subnet
keypairs:
- keypair:
name: ext-net-kp
diff --git a/examples/inst-w-volume/deploy-env.yaml b/examples/inst-w-volume/deploy-env.yaml
new file mode 100644
index 0000000..7baf8a2
--- /dev/null
+++ b/examples/inst-w-volume/deploy-env.yaml
@@ -0,0 +1,44 @@
+---
+admin_user: admin
+admin_pass: ChangeMe
+admin_proj: admin
+auth_url: http://10.197.103.31:5000/
+proxy_host:
+proxy_port:
+id_api_version: 3
+
+ext_net: public1
+
+username: test_user
+pass: test_user
+proj: example-volume
+
+flavor_name: example.m1.small
+flavor_ram: 2048
+flavor_disk: 10
+flavor_cpus: 1
+
+qos_name: example_qos
+vol_type_name: example-vol-type
+vol_type_encryption_name: example_vol-type-encryption
+volume_name: example-volume
+
+image_name: example-image
+image_format: qcow2
+image_url: http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img
+image_file:
+image_user: cirros
+
+net_name: example-net
+subnet_name: example-subnet
+cidr: 10.0.8.0/24
+router_name: example-router
+
+kp_name: example-kp
+kp_pub_path: ~/tmp/example-kp.pub
+kp_priv_path: ~/tmp/example-kp
+
+sg_name: example-sg
+
+port_name: example-port
+inst_name: example-inst
diff --git a/examples/inst-w-volume/deploy-vm-with-volume.yaml b/examples/inst-w-volume/deploy-vm-with-volume.yaml
new file mode 100644
index 0000000..b15f655
--- /dev/null
+++ b/examples/inst-w-volume/deploy-vm-with-volume.yaml
@@ -0,0 +1,144 @@
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+---
+openstack:
+ connections:
+ # Note - when http_proxy is set, you must also configure ssh for proxy tunneling on your host.
+ - connection:
+ name: admin-creds
+ username: {{ admin_user }}
+ project_name: {{ admin_proj }}
+ password: {{ admin_pass }}
+ auth_url: {{ auth_url }}
+ identity_api_version: {{ id_api_version }}
+ projects:
+ - project:
+ os_creds_name: admin-creds
+ name: {{ proj }}
+ description: Project for Orchestrators
+ users:
+ - {{ username }}
+ - {{ admin_user }}
+ users:
+ - user:
+ os_creds_name: admin-creds
+ name: {{ username }}
+ password: {{ pass }}
+ project_name: {{ proj }}
+ roles: {admin: {{ proj }}}
+ flavors:
+ - flavor:
+ os_creds_name: admin-creds
+ name: {{ flavor_name }}
+ ram: {{ flavor_ram }}
+ disk: {{ flavor_disk }}
+ vcpus: {{ flavor_cpus }}
+ qos_specs:
+ - qos_spec:
+ os_creds_name: admin-creds
+ name: {{ qos_name }}
+ consumer: both
+ volume_types:
+ - volume_type:
+ os_creds_name: admin-creds
+ name: {{ vol_type_name }}
+ encryption:
+ name: {{ vol_type_encryption_name }}
+ provider_class: LuksEncryptor
+ control_location: front-end
+ volumes:
+ - volume:
+ os_user:
+ name: {{ username }}
+ project_name: {{ proj }}
+ name: {{ volume_name }}
+ size: 10
+ images:
+ - image:
+ os_creds_name: admin-creds
+ name: {{ image_name }}
+ format: {{ image_format }}
+ image_user: {{ image_user }}
+ download_url: {{ image_url }}
+ image_file: {{ image_file }}
+ public: True
+ networks:
+ - network:
+ os_user:
+ name: {{ username }}
+ project_name: {{ proj }}
+ name: {{ net_name }}
+ project_name: {{ proj }}
+ subnets:
+ - subnet:
+ name: {{ subnet_name }}
+ project_name: {{ proj }}
+ cidr: {{ cidr }}
+ dns_nameservers: [8.8.8.8]
+ routers:
+ - router:
+ os_user:
+ name: {{ username }}
+ project_name: {{ proj }}
+ name: {{ router_name }}
+ external_gateway: {{ ext_net }}
+ internal_subnets:
+ - subnet:
+ project_name: {{ admin_proj }}
+ network_name: {{ net_name }}
+ subnet_name: {{ subnet_name }}
+ keypairs:
+ - keypair:
+ os_user:
+ name: {{ username }}
+ project_name: {{ proj }}
+ name: {{ kp_name }}
+ public_filepath: {{ kp_pub_path }}
+ private_filepath: {{ kp_priv_path }}
+ delete_on_clean: True
+ security_groups:
+ - security_group:
+ os_user:
+ name: {{ username }}
+ project_name: {{ proj }}
+ name: {{ sg_name }}
+ rules:
+ - direction: ingress
+ protocol: icmp
+ - direction: ingress
+ protocol: tcp
+ port_range_min: 22
+ port_range_max: 22
+ instances:
+ - instance:
+ os_user:
+ name: {{ username }}
+ project_name: {{ proj }}
+ name: {{ inst_name }}
+ flavor: {{ flavor_name }}
+ imageName: {{ image_name }}
+ keypair_name: {{ kp_name }}
+ security_group_names: [{{ sg_name }}]
+ volume_names:
+ - {{ volume_name }}
+ ports:
+ - port:
+ name: {{ port_name_prfx }}-1a
+ network_name: {{ net_name }}
+ floating_ips:
+ - floating_ip:
+ name: fip1
+ port_name: {{ port_name }}
+ router_name: {{ router_name }}
diff --git a/examples/launch.py b/examples/launch.py
index 76353a2..cfd7f65 100644
--- a/examples/launch.py
+++ b/examples/launch.py
@@ -18,544 +18,23 @@
# This script is responsible for deploying virtual environments
import argparse
import logging
-import re
-import time
from jinja2 import Environment, FileSystemLoader
import os
import yaml
from snaps import file_utils
-from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor
-from snaps.openstack.create_image import ImageSettings, OpenStackImage
-from snaps.openstack.create_instance import VmInstanceSettings
-from snaps.openstack.create_keypairs import KeypairSettings, OpenStackKeypair
-from snaps.openstack.create_network import (
- PortSettings, NetworkSettings, OpenStackNetwork)
-from snaps.openstack.create_project import OpenStackProject, ProjectSettings
-from snaps.openstack.create_router import RouterSettings, OpenStackRouter
-from snaps.openstack.create_security_group import (
- OpenStackSecurityGroup, SecurityGroupSettings)
-from snaps.openstack.create_user import OpenStackUser, UserSettings
-from snaps.openstack.os_credentials import OSCreds, ProxySettings
-from snaps.openstack.utils import deploy_utils
-from snaps.provisioning import ansible_utils
+from snaps.openstack.utils import launch_utils
__author__ = 'spisarski'
logger = logging.getLogger('snaps_launcher')
ARG_NOT_SET = "argument not set"
-DEFAULT_CREDS_KEY = 'admin'
-
-def __get_creds_dict(os_conn_config):
- """
- Returns a dict of OSCreds where the key is the creds name.
- For backwards compatibility, credentials not contained in a list (only
- one) will be returned with the key of None
- :param os_conn_config: the credential configuration
- :return: a dict of OSCreds objects
- """
- if 'connection' in os_conn_config:
- return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)}
- elif 'connections' in os_conn_config:
- out = dict()
- for os_conn_dict in os_conn_config['connections']:
- config = os_conn_dict.get('connection')
- if not config:
- raise Exception('Invalid connection format')
-
- name = config.get('name')
- if not name:
- raise Exception('Connection config requires a name field')
-
- out[name] = __get_os_credentials(os_conn_dict)
- return out
-
-
-def __get_creds(os_creds_dict, os_user_dict, inst_config):
- """
- Returns the appropriate credentials
- :param os_creds_dict: a dictionary of OSCreds objects where the name is the
- key
- :param os_user_dict: a dictionary of OpenStackUser objects where the name
- is the key
- :param inst_config:
- :return: an OSCreds instance or None
- """
- os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY)
- if 'os_user' in inst_config:
- os_user_conf = inst_config['os_user']
- if 'name' in os_user_conf:
- user_creator = os_user_dict.get(os_user_conf['name'])
- if user_creator:
- return user_creator.get_os_creds(
- project_name=os_user_conf.get('project_name'))
- elif 'os_creds_name' in inst_config:
- if 'os_creds_name' in inst_config:
- os_creds = os_creds_dict[inst_config['os_creds_name']]
- return os_creds
-
-
-def __get_os_credentials(os_conn_config):
- """
- Returns an object containing all of the information required to access
- OpenStack APIs
- :param os_conn_config: The configuration holding the credentials
- :return: an OSCreds instance
- """
- config = os_conn_config.get('connection')
- if not config:
- raise Exception('Invalid connection configuration')
-
- proxy_settings = None
- http_proxy = config.get('http_proxy')
- if http_proxy:
- tokens = re.split(':', http_proxy)
- ssh_proxy_cmd = config.get('ssh_proxy_cmd')
- proxy_settings = ProxySettings(host=tokens[0], port=tokens[1],
- ssh_proxy_cmd=ssh_proxy_cmd)
- else:
- if 'proxy_settings' in config:
- host = config['proxy_settings'].get('host')
- port = config['proxy_settings'].get('port')
- if host and host != 'None' and port and port != 'None':
- proxy_settings = ProxySettings(**config['proxy_settings'])
-
- if proxy_settings:
- config['proxy_settings'] = proxy_settings
- else:
- del config['proxy_settings']
-
- return OSCreds(**config)
-
-
-def __parse_ports_config(config):
- """
- Parses the "ports" configuration
- :param config: The dictionary to parse
- :return: a list of PortConfig objects
- """
- out = list()
- for port_config in config:
- out.append(PortSettings(**port_config.get('port')))
- return out
-
-
-def __create_instances(os_creds_dict, creator_class, config_class, config,
- config_key, cleanup=False, os_users_dict=None):
- """
- Returns a dictionary of SNAPS creator objects where the key is the name
- :param os_creds_dict: Dictionary of OSCreds objects where the key is the
- name
- :param config: The list of configurations for the same type
- :param config_key: The list of configurations for the same type
- :param cleanup: Denotes whether or not this is being called for cleanup
- :return: dictionary
- """
- out = {}
-
- if config:
- try:
- for config_dict in config:
- inst_config = config_dict.get(config_key)
- if inst_config:
- creator = creator_class(
- __get_creds(os_creds_dict, os_users_dict, inst_config),
- config_class(**inst_config))
-
- if cleanup:
- creator.initialize()
- else:
- creator.create()
- out[inst_config['name']] = creator
- logger.info('Created configured %s', config_key)
- except Exception as e:
- logger.error('Unexpected error instantiating creator [%s] '
- 'with exception %s', creator_class, e)
-
- return out
-
-
-def __create_vm_instances(os_creds_dict, os_users_dict, instances_config,
- image_dict, keypairs_dict, cleanup=False):
- """
- Returns a dictionary of OpenStackVmInstance objects where the key is the
- instance name
- :param os_creds_dict: Dictionary of OSCreds objects where the key is the
- name
- :param os_users_dict: Dictionary of OpenStackUser objects where the key is
- the username
- :param instances_config: The list of VM instance configurations
- :param image_dict: A dictionary of images that will probably be used to
- instantiate the VM instance
- :param keypairs_dict: A dictionary of keypairs that will probably be used
- to instantiate the VM instance
- :param cleanup: Denotes whether or not this is being called for cleanup
- :return: dictionary
- """
- vm_dict = {}
-
- if instances_config:
- try:
- for instance_config in instances_config:
- conf = instance_config.get('instance')
- if conf:
- if image_dict:
- image_creator = image_dict.get(conf.get('imageName'))
- if image_creator:
- instance_settings = VmInstanceSettings(
- **instance_config['instance'])
- kp_name = conf.get('keypair_name')
- vm_dict[conf[
- 'name']] = deploy_utils.create_vm_instance(
- __get_creds(
- os_creds_dict, os_users_dict, conf),
- instance_settings,
- image_creator.image_settings,
- keypair_creator=keypairs_dict[kp_name],
- init_only=cleanup)
- else:
- raise Exception('Image creator instance not found.'
- ' Cannot instantiate')
- else:
- raise Exception('Image dictionary is None. Cannot '
- 'instantiate')
- else:
- raise Exception('Instance configuration is None. Cannot '
- 'instantiate')
- logger.info('Created configured instances')
- except Exception as e:
- logger.error('Unexpected error creating VM instances - %s', e)
- return vm_dict
-
-
-def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict,
- image_dict, flavor_dict, env_file):
- """
- Applies ansible playbooks to running VMs with floating IPs
- :param ansible_configs: a list of Ansible configurations
- :param os_creds_dict: Dictionary of OSCreds objects where the key is the
- name
- :param vm_dict: the dictionary of newly instantiated VMs where the name is
- the key
- :param image_dict: the dictionary of newly instantiated images where the
- name is the key
- :param flavor_dict: the dictionary of newly instantiated flavors where the
- name is the key
- :param env_file: the path of the environment for setting the CWD so
- playbook location is relative to the deployment file
- :return: t/f - true if successful
- """
- logger.info("Applying Ansible Playbooks")
- if ansible_configs:
- # Ensure all hosts are accepting SSH session requests
- for vm_inst in list(vm_dict.values()):
- if not vm_inst.vm_ssh_active(block=True):
- logger.warning(
- "Timeout waiting for instance to respond to SSH requests")
- return False
-
- # Set CWD so the deployment file's playbook location can leverage
- # relative paths
- orig_cwd = os.getcwd()
- env_dir = os.path.dirname(env_file)
- os.chdir(env_dir)
-
- # Apply playbooks
- for ansible_config in ansible_configs:
- if 'pre_sleep_time' in ansible_config:
- try:
- sleep_time = int(ansible_config['pre_sleep_time'])
- logger.info('Waiting %s seconds to apply playbooks',
- sleep_time)
- time.sleep(sleep_time)
- except:
- pass
-
- os_creds = os_creds_dict.get(None, 'admin')
- __apply_ansible_playbook(ansible_config, os_creds, vm_dict,
- image_dict, flavor_dict)
-
- # Return to original directory
- os.chdir(orig_cwd)
-
- return True
-
-
-def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict,
- flavor_dict):
- """
- Applies an Ansible configuration setting
- :param ansible_config: the configuration settings
- :param os_creds: the OpenStack credentials object
- :param vm_dict: the dictionary of newly instantiated VMs where the name is
- the key
- :param image_dict: the dictionary of newly instantiated images where the
- name is the key
- :param flavor_dict: the dictionary of newly instantiated flavors where the
- name is the key
- """
- if ansible_config:
- (remote_user, floating_ips, private_key_filepath,
- proxy_settings) = __get_connection_info(
- ansible_config, vm_dict)
- if floating_ips:
- retval = ansible_utils.apply_playbook(
- ansible_config['playbook_location'], floating_ips, remote_user,
- private_key_filepath,
- variables=__get_variables(ansible_config.get('variables'),
- os_creds, vm_dict, image_dict,
- flavor_dict),
- proxy_setting=proxy_settings)
- if retval != 0:
- # Not a fatal type of event
- logger.warning(
- 'Unable to apply playbook found at location - %s',
- ansible_config.get('playbook_location'))
-
-
-def __get_connection_info(ansible_config, vm_dict):
- """
- Returns a tuple of data required for connecting to the running VMs
- (remote_user, [floating_ips], private_key_filepath, proxy_settings)
- :param ansible_config: the configuration settings
- :param vm_dict: the dictionary of VMs where the VM name is the key
- :return: tuple where the first element is the user and the second is a list
- of floating IPs and the third is the
- private key file location and the fourth is an instance of the
- snaps.ProxySettings class
- (note: in order to work, each of the hosts need to have the same sudo_user
- and private key file location values)
- """
- if ansible_config.get('hosts'):
- hosts = ansible_config['hosts']
- if len(hosts) > 0:
- floating_ips = list()
- remote_user = None
- pk_file = None
- proxy_settings = None
- for host in hosts:
- vm = vm_dict.get(host)
- if vm:
- fip = vm.get_floating_ip()
- if fip:
- remote_user = vm.get_image_user()
-
- if fip:
- floating_ips.append(fip.ip)
- else:
- raise Exception(
- 'Could not find floating IP for VM - ' +
- vm.name)
-
- pk_file = vm.keypair_settings.private_filepath
- proxy_settings = vm.get_os_creds().proxy_settings
- else:
- logger.error('Could not locate VM with name - ' + host)
-
- return remote_user, floating_ips, pk_file, proxy_settings
- return None
-
-
-def __get_variables(var_config, os_creds, vm_dict, image_dict, flavor_dict):
- """
- Returns a dictionary of substitution variables to be used for Ansible
- templates
- :param var_config: the variable configuration settings
- :param os_creds: the OpenStack credentials object
- :param vm_dict: the dictionary of newly instantiated VMs where the name is
- the key
- :param image_dict: the dictionary of newly instantiated images where the
- name is the key
- :param flavor_dict: the dictionary of newly instantiated flavors where the
- name is the key
- :return: dictionary or None
- """
- if var_config and vm_dict and len(vm_dict) > 0:
- variables = dict()
- for key, value in var_config.items():
- value = __get_variable_value(value, os_creds, vm_dict, image_dict,
- flavor_dict)
- if key and value:
- variables[key] = value
- logger.info(
- "Set Jinga2 variable with key [%s] the value [%s]",
- key, value)
- else:
- logger.warning('Key [%s] or Value [%s] must not be None',
- str(key), str(value))
- return variables
- return None
-
-
-def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict,
- flavor_dict):
- """
- Returns the associated variable value for use by Ansible for substitution
- purposes
- :param var_config_values: the configuration dictionary
- :param os_creds: the OpenStack credentials object
- :param vm_dict: the dictionary of newly instantiated VMs where the name is
- the key
- :param image_dict: the dictionary of newly instantiated images where the
- name is the key
- :param flavor_dict: the dictionary of newly instantiated flavors where the
- name is the key
- :return:
- """
- if var_config_values['type'] == 'string':
- return __get_string_variable_value(var_config_values)
- if var_config_values['type'] == 'vm-attr':
- return __get_vm_attr_variable_value(var_config_values, vm_dict)
- if var_config_values['type'] == 'os_creds':
- return __get_os_creds_variable_value(var_config_values, os_creds)
- if var_config_values['type'] == 'port':
- return __get_vm_port_variable_value(var_config_values, vm_dict)
- if var_config_values['type'] == 'floating_ip':
- return __get_vm_fip_variable_value(var_config_values, vm_dict)
- if var_config_values['type'] == 'image':
- return __get_image_variable_value(var_config_values, image_dict)
- if var_config_values['type'] == 'flavor':
- return __get_flavor_variable_value(var_config_values, flavor_dict)
- return None
-
-
-def __get_string_variable_value(var_config_values):
- """
- Returns the associated string value
- :param var_config_values: the configuration dictionary
- :return: the value contained in the dictionary with the key 'value'
- """
- return var_config_values['value']
-
-
-def __get_vm_attr_variable_value(var_config_values, vm_dict):
- """
- Returns the associated value contained on a VM instance
- :param var_config_values: the configuration dictionary
- :param vm_dict: the dictionary containing all VMs where the key is the VM's
- name
- :return: the value
- """
- vm = vm_dict.get(var_config_values['vm_name'])
- if vm:
- if var_config_values['value'] == 'floating_ip':
- return vm.get_floating_ip().ip
- if var_config_values['value'] == 'image_user':
- return vm.get_image_user()
-
-
-def __get_os_creds_variable_value(var_config_values, os_creds):
- """
- Returns the associated OS credentials value
- :param var_config_values: the configuration dictionary
- :param os_creds: the credentials
- :return: the value
- """
- logger.info("Retrieving OS Credentials")
- if os_creds:
- if var_config_values['value'] == 'username':
- logger.info("Returning OS username")
- return os_creds.username
- elif var_config_values['value'] == 'password':
- logger.info("Returning OS password")
- return os_creds.password
- elif var_config_values['value'] == 'auth_url':
- logger.info("Returning OS auth_url")
- return os_creds.auth_url
- elif var_config_values['value'] == 'project_name':
- logger.info("Returning OS project_name")
- return os_creds.project_name
-
- logger.info("Returning none")
- return None
-
-
-def __get_vm_port_variable_value(var_config_values, vm_dict):
- """
- Returns the associated OS credentials value
- :param var_config_values: the configuration dictionary
- :param vm_dict: the dictionary containing all VMs where the key is the VM's
- name
- :return: the value
- """
- port_name = var_config_values.get('port_name')
- vm_name = var_config_values.get('vm_name')
-
- if port_name and vm_name:
- vm = vm_dict.get(vm_name)
- if vm:
- port_value_id = var_config_values.get('port_value')
- if port_value_id:
- if port_value_id == 'mac_address':
- return vm.get_port_mac(port_name)
- if port_value_id == 'ip_address':
- return vm.get_port_ip(port_name)
-
-
-def __get_vm_fip_variable_value(var_config_values, vm_dict):
- """
- Returns the floating IP value if found
- :param var_config_values: the configuration dictionary
- :param vm_dict: the dictionary containing all VMs where the key is the VM's
- name
- :return: the floating IP string value or None
- """
- fip_name = var_config_values.get('fip_name')
- vm_name = var_config_values.get('vm_name')
-
- if vm_name:
- vm = vm_dict.get(vm_name)
- if vm:
- fip = vm.get_floating_ip(fip_name)
- if fip:
- return fip.ip
-
-
-def __get_image_variable_value(var_config_values, image_dict):
- """
- Returns the associated image value
- :param var_config_values: the configuration dictionary
- :param image_dict: the dictionary containing all images where the key is
- the name
- :return: the value
- """
- logger.info("Retrieving image values")
-
- if image_dict:
- if var_config_values.get('image_name'):
- image_creator = image_dict.get(var_config_values['image_name'])
- if image_creator:
- if var_config_values.get('value') and \
- var_config_values['value'] == 'id':
- return image_creator.get_image().id
- if var_config_values.get('value') and \
- var_config_values['value'] == 'user':
- return image_creator.image_settings.image_user
-
- logger.info("Returning none")
- return None
-
-
-def __get_flavor_variable_value(var_config_values, flavor_dict):
- """
- Returns the associated flavor value
- :param var_config_values: the configuration dictionary
- :param flavor_dict: the dictionary containing all flavor creators where the
- key is the name
- :return: the value or None
- """
- logger.info("Retrieving flavor values")
-
- if flavor_dict:
- if var_config_values.get('flavor_name'):
- flavor_creator = flavor_dict.get(var_config_values['flavor_name'])
- if flavor_creator:
- if var_config_values.get('value') and \
- var_config_values['value'] == 'id':
- return flavor_creator.get_flavor().id
+from warnings import warn
+warn('This script will be removed in a subsequent release',
+ DeprecationWarning)
def main(arguments):
@@ -591,108 +70,11 @@ def main(arguments):
config = yaml.load(output)
if config:
- os_config = config.get('openstack')
-
- creators = list()
- vm_dict = dict()
- images_dict = dict()
- flavors_dict = dict()
- os_creds_dict = dict()
clean = arguments.clean is not ARG_NOT_SET
-
- if os_config:
- os_creds_dict = __get_creds_dict(os_config)
-
- try:
- # Create projects
- projects_dict = __create_instances(
- os_creds_dict, OpenStackProject, ProjectSettings,
- os_config.get('projects'), 'project', clean)
- creators.append(projects_dict)
-
- # Create users
- users_dict = __create_instances(
- os_creds_dict, OpenStackUser, UserSettings,
- os_config.get('users'), 'user', clean)
- creators.append(users_dict)
-
- # Associate new users to projects
- if not clean:
- for project_creator in projects_dict.values():
- users = project_creator.project_settings.users
- for user_name in users:
- user_creator = users_dict.get(user_name)
- if user_creator:
- project_creator.assoc_user(
- user_creator.get_user())
-
- # Create flavors
- flavors_dict = __create_instances(
- os_creds_dict, OpenStackFlavor, FlavorSettings,
- os_config.get('flavors'), 'flavor', clean, users_dict)
- creators.append(flavors_dict)
-
- # Create images
- images_dict = __create_instances(
- os_creds_dict, OpenStackImage, ImageSettings,
- os_config.get('images'), 'image', clean, users_dict)
- creators.append(images_dict)
-
- # Create networks
- creators.append(__create_instances(
- os_creds_dict, OpenStackNetwork, NetworkSettings,
- os_config.get('networks'), 'network', clean, users_dict))
-
- # Create routers
- creators.append(__create_instances(
- os_creds_dict, OpenStackRouter, RouterSettings,
- os_config.get('routers'), 'router', clean, users_dict))
-
- # Create keypairs
- keypairs_dict = __create_instances(
- os_creds_dict, OpenStackKeypair, KeypairSettings,
- os_config.get('keypairs'), 'keypair', clean, users_dict)
- creators.append(keypairs_dict)
-
- # Create security groups
- creators.append(__create_instances(
- os_creds_dict, OpenStackSecurityGroup,
- SecurityGroupSettings,
- os_config.get('security_groups'), 'security_group', clean,
- users_dict))
-
- # Create instance
- vm_dict = __create_vm_instances(
- os_creds_dict, users_dict, os_config.get('instances'),
- images_dict, keypairs_dict,
- arguments.clean is not ARG_NOT_SET)
- creators.append(vm_dict)
- logger.info(
- 'Completed creating/retrieving all configured instances')
- except Exception as e:
- logger.error(
- 'Unexpected error deploying environment. Rolling back due'
- ' to - ' + str(e))
- raise
-
- # Must enter either block
- if arguments.clean is not ARG_NOT_SET:
- # Cleanup Environment
- __cleanup(creators, arguments.clean_image is not ARG_NOT_SET)
- elif arguments.deploy is not ARG_NOT_SET:
- logger.info('Configuring NICs where required')
- for vm in vm_dict.values():
- vm.config_nics()
- logger.info('Completed NIC configuration')
-
- # Provision VMs
- ansible_config = config.get('ansible')
- if ansible_config and vm_dict:
- if not __apply_ansible_playbooks(ansible_config,
- os_creds_dict, vm_dict,
- images_dict, flavors_dict,
- arguments.tmplt_file):
- logger.error("Problem applying ansible playbooks")
+ clean_image = arguments.clean_image is not ARG_NOT_SET
+ deploy = arguments.deploy is not ARG_NOT_SET
+ launch_utils.launch_config(
+ config, arguments.tmplt_file, deploy, clean, clean_image)
else:
logger.error(
'Unable to read configuration file - ' + arguments.tmplt_file)
@@ -701,17 +83,6 @@ def main(arguments):
exit(0)
-def __cleanup(creators, clean_image=False):
- for creator_dict in reversed(creators):
- for key, creator in creator_dict.items():
- if ((isinstance(creator, OpenStackImage) and clean_image)
- or not isinstance(creator, OpenStackImage)):
- try:
- creator.clean()
- except Exception as e:
- logger.warning('Error cleaning component - %s', e)
-
-
if __name__ == '__main__':
# To ensure any files referenced via a relative path will begin from the
# directory in which this file resides
diff --git a/examples/simple/deploy-simple.yaml b/examples/simple/deploy-simple.yaml
index 3124210..fa4c8b2 100644
--- a/examples/simple/deploy-simple.yaml
+++ b/examples/simple/deploy-simple.yaml
@@ -41,7 +41,10 @@ openstack:
name: simple-router
external_gateway: external
internal_subnets:
- - simple-subnet
+ - subnet:
+ project_name: admin
+ network_name: simple-net
+ subnet_name: simple-subnet
keypairs:
- keypair:
name: simple-kp
diff --git a/examples/two-network/deploy-two-net-centos.yaml b/examples/two-network/deploy-two-net-centos.yaml
index 4b5ba89..d23fff9 100644
--- a/examples/two-network/deploy-two-net-centos.yaml
+++ b/examples/two-network/deploy-two-net-centos.yaml
@@ -48,7 +48,10 @@ openstack:
name: router-1
external_gateway: external
internal_subnets:
- - subnet-1
+ - subnet:
+ project_name: admin
+ network_name: net-1
+ subnet_name: subnet-1
keypairs:
- keypair:
name: two-net
diff --git a/examples/two-network/deploy-two-net-ubuntu.yaml b/examples/two-network/deploy-two-net-ubuntu.yaml
index 0ad471c..0381bff 100644
--- a/examples/two-network/deploy-two-net-ubuntu.yaml
+++ b/examples/two-network/deploy-two-net-ubuntu.yaml
@@ -48,7 +48,10 @@ openstack:
name: router-1
external_gateway: external
internal_subnets:
- - subnet-1
+ - subnet:
+ project_name: admin
+ network_name: net-1
+ subnet_name: subnet-1
keypairs:
- keypair:
name: simple
diff --git a/requirements.txt b/requirements.txt
index b0b60c0..6721540 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,17 @@
-python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0
-python-neutronclient>=5.1.0 # Apache-2.0
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+python-novaclient>=9.1.0 # Apache-2.0
+python-neutronclient>=6.7.0 # Apache-2.0
python-keystoneclient>=3.8.0 # Apache-2.0
-python-glanceclient>=2.5.0 # Apache-2.0
-python-heatclient>=1.6.1 # Apache-2.0
-python-cinderclient
-ansible>=2.1.0,<2.4
+python-glanceclient>=2.8.0 # Apache-2.0
+python-heatclient>=1.10.0 # Apache-2.0
+python-cinderclient>=3.3.0 # Apache-2.0
+python-magnumclient>=2.1.0,<2.3 # Apache-2.0
+ansible>=2.4
wrapt>=1.7.0 # BSD License
scp
-cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
+cryptography>=2.1 # BSD/Apache-2.0
+concurrencytest
+Jinja2 # BSD License (3 clause)
+keystoneauth1 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index c999c81..3d42cd7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
-name = snaps
-version = 1.0
+name = snaps-oo
+version = 7.0.0
home-page = https://gerrit.opnfv.org/gerrit/gitweb?p=snaps.git;a=summary
author = Steve Pisarski
author-email = s.pisarski@cablelabs.com
diff --git a/setup.py b/setup.py
index f0cf5e8..566d844 100644
--- a/setup.py
+++ b/setup.py
@@ -1,17 +1,29 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2016 Cable Television Laboratories and others.
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# 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
#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available 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 setuptools import setup
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
+import setuptools
-__author__ = 'spisarski'
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+ import multiprocessing # noqa
+except ImportError:
+ pass
-setup(
- setup_requires=['pbr>=1.9', 'setuptools>=17.1'],
- pbr=True,
-)
+setuptools.setup(
+ setup_requires=['pbr>=2.0.0'],
+ pbr=True)
diff --git a/snaps/config/__init__.py b/snaps/config/__init__.py
new file mode 100644
index 0000000..271c742
--- /dev/null
+++ b/snaps/config/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+__author__ = 'spisarski'
diff --git a/snaps/config/cluster_template.py b/snaps/config/cluster_template.py
new file mode 100644
index 0000000..0acf25a
--- /dev/null
+++ b/snaps/config/cluster_template.py
@@ -0,0 +1,308 @@
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import enum
+from neutronclient.common.utils import str2bool
+
+
+class ServerType(enum.Enum):
+ """
+ The cluter server types supported
+ """
+ vm = 'vm'
+ baremetal = 'baremetal'
+
+
+class ContainerOrchestrationEngine(enum.Enum):
+ """
+ The types of supported COEs
+ """
+ kubernetes = 'kubernetes'
+ swarm = 'swarm'
+ mesos = 'mesos'
+
+
+class DockerStorageDriver(enum.Enum):
+ """
+ Drivers for managing storage for the images in the container's writable
+ layer
+ """
+ devicemapper = 'devicemapper'
+ overlay = 'overlay'
+
+
+class ClusterTemplateConfig(object):
+ """
+ Configuration settings for OpenStack cluster template creation
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the cluster template's name (required)
+ :param image: name or ID of the base image in Glance used to boot the
+ cluster's servers. The image must have the attribute
+ 'os-distro' defined as appropriate for the cluster
+ driver (required)
+ :param keypair: name or ID of the keypair to gain cluster machine
+ access (required)
+ :param network_driver: The name of a network driver for providing the
+ networks for the containers. Note that this is
+ different and separate from the Neutron network
+ for the bay/cluster. The operation and
+ networking model are specific to the particular
+ driver (optional)
+ :param external_net: name or IDof the external Neutron network to
+ provide connectivity to the cluster (required)
+ :param floating_ip_enabled: Whether enable or not using the floating IP
+ of cloud provider. Some cloud providers
+ used floating IP, some used public IP,
+ thus Magnum provide this option for
+ specifying the choice of using floating IP
+ (default - True)
+ :param docker_volume_size: The size in GB for the local storage on each
+ server for the Docker daemon to cache the
+ images and host the containers. Cinder
+ volumes provide the storage. The default is
+ 25 GB. For the devicemapper storage driver,
+ the minimum value is 3GB. For the overlay
+ storage driver, the minimum value is 1GB.
+ (default - 3)
+ :param server_type: ServerType enumeration (default - vm)
+ :param flavor: name or ID of the nova flavor for booting the node
+ servers (default - m1.small)
+ :param master_flavor: name or ID of the nova flavor of the master node
+ for this cluster (optional)
+ :param coe: ContainerOrchestrationEngine enum instance
+ (default - kubernetes)
+ :param fixed_net: name of a Neutron network to provide connectivity
+ to the internal network for the cluster
+ (optional)
+ :param fixed_subnet: Fixed subnet that are using to allocate network
+ address for nodes in bay/cluster (optional)
+ :param registry_enabled: Docker images by default are pulled from the
+ public Docker registry, but in some cases,
+ users may want to use a private registry.
+ This option provides an alternative registry
+ based on the Registry V2: Magnum will create a
+ local registry in the bay/cluster backed by
+ swift to host the images (default - True)
+ :param insecure_registry: The URL pointing to the user's own private
+ insecure docker registry to deploy and run
+ docker containers (optional)
+ :param docker_storage_driver: DockerStorageDriver enum instance to
+ manage storage for the images and
+ container's writable layer
+ (default - devicemapper)
+ :param dns_nameserver: The DNS nameserver for the servers and
+ containers in the bay/cluster to use.
+ This is configured in the private Neutron
+ network for the bay/cluster.
+ (default provided by Magnum - 8.8.8.8)
+ :param public: denotes whether or not the cluster template is public
+ (default False)
+ :param tls_disabled: denotes whether or not TLS should be enabled
+ (default False)
+ :param http_proxy: host:port for a proxy to use when direct HTTP
+ access from the servers to sites on the external
+ internet is blocked (optional)
+ :param https_proxy: host:port for a proxy to use when direct HTTPS
+ access from the servers to sites on the external
+ internet is blocked (optional)
+ :param no_proxy: comma separated list of IPs that should not be
+ redirected through the proxy (optional)
+ :param volume_driver: The name of a volume driver for managing the
+ persistent storage for the containers. The
+ functionality supported are specific to the
+ driver (optional)
+ :param master_lb_enabled: Since multiple masters may exist in a
+ bay/cluster, a Neutron load balancer is
+ created to provide the API endpoint for the
+ bay/cluster and to direct requests to the
+ masters. In some cases, such as when the
+ LBaaS service is not available, this option
+ can be set to false to create a bay/cluster
+ without the load balancer. In this case, one
+ of the masters will serve as the API endpoint
+ (default - True)
+ :param labels: Arbitrary labels in the form of a dict. The accepted
+ keys and valid values are defined in the bay/cluster
+ drivers. They are used as a way to pass additional
+ parameters that are specific to a bay/cluster driver.
+ (optional)
+ """
+ self.name = kwargs.get('name')
+ self.image = kwargs.get('image')
+ self.keypair = kwargs.get('keypair')
+ self.network_driver = kwargs.get('network_driver')
+ self.external_net = kwargs.get('external_net')
+ self.floating_ip_enabled = str2bool(
+ str(kwargs.get('floating_ip_enabled', True)))
+ self.docker_volume_size = int(kwargs.get('docker_volume_size', 3))
+ self.server_type = map_server_type(
+ kwargs.get('server_type', ServerType.vm))
+ self.flavor = kwargs.get('flavor')
+ self.master_flavor = kwargs.get('master_flavor')
+ self.coe = map_coe(
+ kwargs.get('coe', ContainerOrchestrationEngine.kubernetes))
+ self.fixed_net = kwargs.get('fixed_net')
+ self.fixed_subnet = kwargs.get('fixed_subnet')
+ self.registry_enabled = str2bool(
+ str(kwargs.get('registry_enabled', True)))
+ self.insecure_registry = kwargs.get('insecure_registry')
+ self.docker_storage_driver = map_docker_storage_driver(
+ kwargs.get('docker_storage_driver',
+ DockerStorageDriver.devicemapper))
+ self.dns_nameserver = kwargs.get('dns_nameserver')
+ self.public = str2bool(str(kwargs.get('public', False)))
+ self.tls_disabled = str2bool(str(kwargs.get('tls_disabled', False)))
+ self.http_proxy = kwargs.get('http_proxy')
+ self.https_proxy = kwargs.get('https_proxy')
+ self.no_proxy = kwargs.get('no_proxy')
+ self.volume_driver = kwargs.get('volume_driver')
+ self.master_lb_enabled = str2bool(
+ str(kwargs.get('master_lb_enabled', True)))
+ self.labels = kwargs.get('labels')
+
+ if (not self.name or not self.image or not self.keypair
+ or not self.external_net):
+ raise ClusterTemplateConfigError(
+ 'The attributes name, image, keypair, and '
+ 'external_net are required for ClusterTemplateConfig')
+
+ def magnum_dict(self):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be sent into as kwargs into the Magnum client
+
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.name:
+ out['name'] = self.name
+ if self.image:
+ out['image_id'] = self.image
+ if self.keypair:
+ out['keypair_id'] = self.keypair
+ if self.network_driver:
+ out['network_driver'] = self.network_driver
+ if self.external_net:
+ out['external_network_id'] = self.external_net
+ if self.floating_ip_enabled:
+ out['floating_ip_enabled'] = self.floating_ip_enabled
+ if self.docker_volume_size:
+ out['docker_volume_size'] = self.docker_volume_size
+ if self.server_type:
+ out['server_type'] = self.server_type.value
+ if self.flavor:
+ out['flavor_id'] = self.flavor
+ if self.master_flavor:
+ out['master_flavor_id'] = self.master_flavor
+ if self.coe:
+ out['coe'] = self.coe.value
+ if self.fixed_net:
+ out['fixed_network'] = self.fixed_net
+ if self.fixed_subnet:
+ out['fixed_subnet'] = self.fixed_subnet
+ if self.registry_enabled:
+ out['registry_enabled'] = self.registry_enabled
+ if self.insecure_registry:
+ out['insecure_registry'] = self.insecure_registry
+ if self.docker_storage_driver:
+ out['docker_storage_driver'] = self.docker_storage_driver.value
+ if self.dns_nameserver:
+ out['dns_nameserver'] = self.dns_nameserver
+ if self.public:
+ out['public'] = self.public
+ if self.tls_disabled:
+ out['tls_disabled'] = self.tls_disabled
+ if self.http_proxy:
+ out['http_proxy'] = self.http_proxy
+ if self.https_proxy:
+ out['https_proxy'] = self.https_proxy
+ if self.no_proxy:
+ out['no_proxy'] = self.no_proxy
+ if self.volume_driver:
+ out['volume_driver'] = self.volume_driver
+ if self.master_lb_enabled:
+ out['master_lb_enabled'] = self.master_lb_enabled
+ if self.labels:
+ out['labels'] = self.labels
+ return out
+
+
+class ClusterTemplateConfigError(Exception):
+ """
+ Exception to be thrown when a cluster template configuration is incorrect
+ """
+
+
+def map_server_type(server_type):
+ """
+ Takes a the server_type value maps it to the ServerType enum. When None
+ return None
+ :param server_type: the server_type value to map
+ :return: the ServerType enum object
+ :raise: ClusterTemplateConfigError if value is invalid
+ """
+ if not server_type:
+ return None
+ if isinstance(server_type, ServerType):
+ return server_type
+ elif isinstance(server_type, str):
+ for this_type in ServerType:
+ if this_type.value == server_type:
+ return this_type
+ raise ClusterTemplateConfigError(
+ 'Invalid server type - ' + server_type)
+
+
+def map_coe(coe):
+ """
+ Takes a the coe value maps it to the ContainerOrchestrationEngine enum.
+ When None return None
+ :param coe: the COE value to map
+ :return: the ContainerOrchestrationEngine enum object
+ :raise: ClusterTemplateConfigError if value is invalid
+ """
+ if not coe:
+ return None
+ if isinstance(coe, ContainerOrchestrationEngine):
+ return coe
+ elif isinstance(coe, str):
+ for this_type in ContainerOrchestrationEngine:
+ if this_type.value == coe:
+ return this_type
+ raise ClusterTemplateConfigError('Invalid COE - ' + coe)
+
+
+def map_docker_storage_driver(driver):
+ """
+ Takes a the coe value maps it to the ContainerOrchestrationEngine enum.
+ When None return None
+ :param driver: the docker storage driver value to map
+ :return: the DockerStorageDriver enum object
+ :raise: ClusterTemplateConfigError if value is invalid
+ """
+ if not driver:
+ return None
+ if isinstance(driver, DockerStorageDriver):
+ return driver
+ elif isinstance(driver, str):
+ for this_type in DockerStorageDriver:
+ if this_type.value == driver:
+ return this_type
+ raise ClusterTemplateConfigError(
+ 'Invalid DockerStorageDriver - ' + driver)
diff --git a/snaps/config/flavor.py b/snaps/config/flavor.py
new file mode 100644
index 0000000..b29fb7c
--- /dev/null
+++ b/snaps/config/flavor.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+
+class FlavorConfig(object):
+ """
+ Configuration settings for OpenStack flavor creation
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the flavor's name (required)
+ :param flavor_id: the string ID (default 'auto')
+ :param ram: the required RAM in MB (required)
+ :param disk: the size of the root disk in GB (required)
+ :param vcpus: the number of virtual CPUs (required)
+ :param ephemeral: the size of the ephemeral disk in GB (default 0)
+ :param swap: the size of the dedicated swap disk in GB (default 0)
+ :param rxtx_factor: the receive/transmit factor to be set on ports if
+ backend supports QoS extension (default 1.0)
+ :param is_public: denotes whether or not the flavor is public
+ (default True)
+ :param metadata: freeform dict() for special metadata
+ """
+ self.name = kwargs.get('name')
+
+ if kwargs.get('flavor_id'):
+ self.flavor_id = kwargs['flavor_id']
+ else:
+ self.flavor_id = 'auto'
+
+ self.ram = kwargs.get('ram')
+ self.disk = kwargs.get('disk')
+ self.vcpus = kwargs.get('vcpus')
+
+ if kwargs.get('ephemeral'):
+ self.ephemeral = kwargs['ephemeral']
+ else:
+ self.ephemeral = 0
+
+ if kwargs.get('swap'):
+ self.swap = kwargs['swap']
+ else:
+ self.swap = 0
+
+ if kwargs.get('rxtx_factor'):
+ self.rxtx_factor = kwargs['rxtx_factor']
+ else:
+ self.rxtx_factor = 1.0
+
+ if kwargs.get('is_public') is not None:
+ self.is_public = kwargs['is_public']
+ else:
+ self.is_public = True
+
+ if kwargs.get('metadata'):
+ self.metadata = kwargs['metadata']
+ else:
+ self.metadata = None
+
+ if not self.name or not self.ram or not self.disk or not self.vcpus:
+ raise FlavorConfigError(
+ 'The attributes name, ram, disk, and vcpus are required for'
+ 'FlavorConfig')
+
+ if not isinstance(self.ram, int):
+ raise FlavorConfigError('The ram attribute must be a integer')
+
+ if not isinstance(self.disk, int):
+ raise FlavorConfigError('The ram attribute must be a integer')
+
+ if not isinstance(self.vcpus, int):
+ raise FlavorConfigError('The vcpus attribute must be a integer')
+
+ if self.ephemeral and not isinstance(self.ephemeral, int):
+ raise FlavorConfigError(
+ 'The ephemeral attribute must be an integer')
+
+ if self.swap and not isinstance(self.swap, int):
+ raise FlavorConfigError('The swap attribute must be an integer')
+
+ if self.rxtx_factor and not isinstance(self.rxtx_factor, (int, float)):
+ raise FlavorConfigError(
+ 'The is_public attribute must be an integer or float')
+
+ if self.is_public and not isinstance(self.is_public, bool):
+ raise FlavorConfigError(
+ 'The is_public attribute must be a boolean')
+
+
+class FlavorConfigError(Exception):
+ """
+ Exception to be thrown when a flavor configuration is incorrect
+ """
diff --git a/snaps/config/image.py b/snaps/config/image.py
new file mode 100644
index 0000000..fe1c913
--- /dev/null
+++ b/snaps/config/image.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+
+class ImageConfig(object):
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the image's name (required)
+ :param image_user: the image's default sudo user (required)
+ :param format or img_format: the image format type (required)
+ :param url or download_url: the image download location (requires url
+ or img_file)
+ :param image_file: the image file location (requires url or img_file)
+ :param extra_properties: dict() object containing extra parameters to
+ pass when loading the image;
+ can be ids of kernel and initramfs images for
+ a 3-part image
+ :param nic_config_pb_loc: the file location to the Ansible Playbook
+ that can configure multiple NICs
+ :param kernel_image_settings: the settings for a kernel image
+ :param ramdisk_image_settings: the settings for a ramdisk image
+ :param exists: When True, an image with the given name must exist
+ :param public: When True, an image will be created with public
+ visibility
+ """
+
+ self.name = kwargs.get('name')
+ self.image_user = kwargs.get('image_user')
+ self.format = kwargs.get('format')
+ if not self.format:
+ self.format = kwargs.get('img_format')
+
+ self.url = kwargs.get('url')
+ if not self.url:
+ self.url = kwargs.get('download_url')
+ if self.url == 'None':
+ self.url = None
+
+ self.image_file = kwargs.get('image_file')
+ if self.image_file == 'None':
+ self.image_file = None
+
+ self.extra_properties = kwargs.get('extra_properties')
+ self.nic_config_pb_loc = kwargs.get('nic_config_pb_loc')
+
+ kernel_image_settings = kwargs.get('kernel_image_settings')
+ if kernel_image_settings:
+ if isinstance(kernel_image_settings, dict):
+ self.kernel_image_settings = ImageConfig(
+ **kernel_image_settings)
+ else:
+ self.kernel_image_settings = kernel_image_settings
+ else:
+ self.kernel_image_settings = None
+
+ ramdisk_image_settings = kwargs.get('ramdisk_image_settings')
+ if ramdisk_image_settings:
+ if isinstance(ramdisk_image_settings, dict):
+ self.ramdisk_image_settings = ImageConfig(
+ **ramdisk_image_settings)
+ else:
+ self.ramdisk_image_settings = ramdisk_image_settings
+ else:
+ self.ramdisk_image_settings = None
+
+ if 'exists' in kwargs and kwargs['exists'] is True:
+ self.exists = True
+ else:
+ self.exists = False
+
+ if 'public' in kwargs and kwargs['public'] is True:
+ self.public = True
+ else:
+ self.public = False
+
+ if not self.name:
+ raise ImageConfigError("The attribute name is required")
+
+ if not (self.url or self.image_file) and not self.exists:
+ raise ImageConfigError(
+ 'URL or image file must be set or image must already exist')
+
+ if not self.image_user:
+ raise ImageConfigError('Image user is required')
+
+ if not self.format and not self.exists:
+ raise ImageConfigError(
+ 'Format is required when the image should not already exist')
+
+
+class ImageConfigError(Exception):
+ """
+ Exception to be thrown when an image settings are incorrect
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
diff --git a/snaps/config/keypair.py b/snaps/config/keypair.py
new file mode 100644
index 0000000..2304c6e
--- /dev/null
+++ b/snaps/config/keypair.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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 neutronclient.common.utils import str2bool
+
+
+class KeypairConfig(object):
+ """
+ Class representing a keypair configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor - all parameters are optional
+ :param name: The keypair name.
+ :param public_filepath: The path to/from the filesystem where the
+ public key file is or will be stored
+ :param private_filepath: The path where the generated private key file
+ will be stored
+ :param key_size: The number of bytes for the key size when it needs to
+ be generated (Must be >=512 default 1024)
+ :param delete_on_clean: when True, the key files will be deleted when
+ OpenStackKeypair#clean() is called
+ :return:
+ """
+
+ self.name = kwargs.get('name')
+ self.public_filepath = kwargs.get('public_filepath')
+ self.private_filepath = kwargs.get('private_filepath')
+ self.key_size = int(kwargs.get('key_size', 1024))
+
+ if kwargs.get('delete_on_clean') is not None:
+ if isinstance(kwargs.get('delete_on_clean'), bool):
+ self.delete_on_clean = kwargs.get('delete_on_clean')
+ else:
+ self.delete_on_clean = str2bool(kwargs.get('delete_on_clean'))
+ else:
+ self.delete_on_clean = None
+
+ if not self.name:
+ raise KeypairConfigError('Name is a required attribute')
+
+ if self.key_size < 512:
+ raise KeypairConfigError('key_size must be >=512')
+
+
+class KeypairConfigError(Exception):
+ """
+ Exception to be thrown when keypair settings are incorrect
+ """
diff --git a/snaps/config/network.py b/snaps/config/network.py
new file mode 100644
index 0000000..b142480
--- /dev/null
+++ b/snaps/config/network.py
@@ -0,0 +1,547 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import enum
+from neutronclient.common.utils import str2bool
+
+from snaps.openstack.utils import keystone_utils, neutron_utils
+
+
+class NetworkConfig(object):
+ """
+ Class representing a network configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor - all parameters are optional
+ :param name: The network name.
+ :param admin_state_up: The administrative status of the network.
+ True = up / False = down (default True)
+ :param shared: Boolean value indicating whether this network is shared
+ across all projects/tenants. By default, only
+ administrative users can change this value.
+ :param project_name: Admin-only. The name of the project that will own
+ the network. This project can be different from
+ the project that makes the create network request.
+ However, only administrative users can specify a
+ project ID other than their own. You cannot change
+ this value through authorization policies.
+ :param external: when true, will setup an external network
+ (default False).
+ :param network_type: the type of network (i.e. vlan|flat).
+ :param physical_network: the name of the physical network
+ (required when network_type is 'flat')
+ :param segmentation_id: the id of the segmentation
+ (this is required when network_type is 'vlan')
+ :param subnets or subnet_settings: List of SubnetConfig objects.
+ :param mtu: MTU setting (optional)
+ :return:
+ """
+
+ self.project_id = None
+
+ self.name = kwargs.get('name')
+ if kwargs.get('admin_state_up') is not None:
+ self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
+ else:
+ self.admin_state_up = True
+
+ if kwargs.get('shared') is not None:
+ self.shared = str2bool(str(kwargs['shared']))
+ else:
+ self.shared = None
+
+ self.project_name = kwargs.get('project_name')
+
+ if kwargs.get('external') is not None:
+ self.external = str2bool(str(kwargs.get('external')))
+ else:
+ self.external = False
+
+ self.network_type = kwargs.get('network_type')
+ self.physical_network = kwargs.get('physical_network')
+ self.segmentation_id = kwargs.get('segmentation_id')
+
+ self.subnet_settings = list()
+ subnet_settings = kwargs.get('subnets')
+ if not subnet_settings:
+ subnet_settings = kwargs.get('subnet_settings', list())
+ if subnet_settings:
+ for subnet_config in subnet_settings:
+ if isinstance(subnet_config, SubnetConfig):
+ self.subnet_settings.append(subnet_config)
+ else:
+ self.subnet_settings.append(
+ SubnetConfig(**subnet_config['subnet']))
+
+ if not self.name or len(self.name) < 1:
+ raise NetworkConfigError('Name required for networks')
+
+ self.mtu = kwargs.get('mtu')
+
+ def get_project_id(self, os_creds):
+ """
+ Returns the project ID for a given project_name or None
+ :param os_creds: the credentials required for keystone client retrieval
+ :return: the ID or None
+ """
+ if self.project_id:
+ return self.project_id
+ else:
+ if self.project_name:
+ session = keystone_utils.keystone_session(os_creds)
+ keystone = keystone_utils.keystone_client(os_creds, session)
+ try:
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
+ if project:
+ return project.id
+ finally:
+ keystone_utils.close_session(session)
+
+ return None
+
+ def dict_for_neutron(self, os_creds):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron
+ API
+ TODO - expand automated testing to exercise all parameters
+
+ :param os_creds: the OpenStack credentials
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.name:
+ out['name'] = self.name
+ if self.admin_state_up is not None:
+ out['admin_state_up'] = self.admin_state_up
+ if self.shared:
+ out['shared'] = self.shared
+ if self.project_name:
+ project_id = self.get_project_id(os_creds)
+ if project_id:
+ out['tenant_id'] = project_id
+ else:
+ raise NetworkConfigError(
+ 'Could not find project ID for project named - ' +
+ self.project_name)
+ if self.network_type:
+ out['provider:network_type'] = self.network_type
+ if self.physical_network:
+ out['provider:physical_network'] = self.physical_network
+ if self.segmentation_id:
+ out['provider:segmentation_id'] = self.segmentation_id
+ if self.external:
+ out['router:external'] = self.external
+ if self.mtu:
+ out['mtu'] = self.mtu
+ return {'network': out}
+
+
+class NetworkConfigError(Exception):
+ """
+ Exception to be thrown when networks settings attributes are incorrect
+ """
+
+
+class IPv6Mode(enum.Enum):
+ """
+ A rule's direction
+ """
+ slaac = 'slaac'
+ stateful = 'dhcpv6-stateful'
+ stateless = 'dhcpv6-stateless'
+
+
+class SubnetConfig(object):
+ """
+ Class representing a subnet configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor - all parameters are optional except cidr (subnet mask)
+ :param name: The subnet name (required)
+ :param cidr: The CIDR (required)
+ :param ip_version: The IP version, which is 4 or 6 (required)
+ :param project_name: The name of the project who owns the network.
+ Only administrative users can specify a project ID
+ other than their own. You cannot change this value
+ through authorization policies (optional)
+ :param start: The start address for the allocation pools (optional)
+ :param end: The end address for the allocation pools (optional)
+ :param gateway_ip: The gateway IP address (optional). When not
+ configured, the IP address will be automatically
+ assigned; when 'none', no gateway address will be
+ assigned, else the value must be valid
+ :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
+ disabled (optional)
+ :param dns_nameservers: A list of DNS name servers for the subnet.
+ Specify each name server as an IP address
+ and separate multiple entries with a space.
+ For example [8.8.8.7 8.8.8.8]
+ (default [])
+ :param host_routes: A list of host route dictionaries for the subnet.
+ For example:
+ "host_routes":[
+ {
+ "destination":"0.0.0.0/0",
+ "nexthop":"123.456.78.9"
+ },
+ {
+ "destination":"192.168.0.0/24",
+ "nexthop":"192.168.0.1"
+ }
+ ]
+ :param destination: The destination for static route (optional)
+ :param nexthop: The next hop for the destination (optional)
+ :param ipv6_ra_mode: an instance of the IPv6Mode enum
+ (optional when enable_dhcp is True)
+ :param ipv6_address_mode: an instance of the IPv6Mode enum
+ (optional when enable_dhcp is True)
+ :raise: SubnetConfigError when config does not have or cidr values
+ are None
+ """
+ self.cidr = kwargs.get('cidr')
+ if kwargs.get('ip_version'):
+ self.ip_version = kwargs['ip_version']
+ else:
+ self.ip_version = 4
+
+ # Optional attributes that can be set after instantiation
+ self.name = kwargs.get('name')
+ self.project_name = kwargs.get('project_name')
+ self.start = kwargs.get('start')
+ self.end = kwargs.get('end')
+ self.gateway_ip = kwargs.get('gateway_ip')
+ self.enable_dhcp = kwargs.get('enable_dhcp')
+
+ if 'dns_nameservers' in kwargs:
+ self.dns_nameservers = kwargs.get('dns_nameservers')
+ else:
+ self.dns_nameservers = list()
+
+ self.host_routes = kwargs.get('host_routes')
+ self.destination = kwargs.get('destination')
+ self.nexthop = kwargs.get('nexthop')
+ self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
+ self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
+
+ if not self.name or not self.cidr:
+ raise SubnetConfigError('Name and cidr required for subnets')
+
+ def dict_for_neutron(self, os_creds, network=None):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron
+ API
+ :param os_creds: the OpenStack credentials
+ :param network: The network object on which the subnet will be created
+ (optional)
+ :return: the dictionary object
+ """
+ out = {
+ 'cidr': self.cidr,
+ 'ip_version': self.ip_version,
+ }
+
+ if network:
+ out['network_id'] = network.id
+ if self.name:
+ out['name'] = self.name
+ if self.project_name:
+ session = keystone_utils.keystone_session(os_creds)
+ keystone = keystone_utils.keystone_client(os_creds, session)
+ try:
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
+ finally:
+ keystone_utils.close_session(session)
+ project_id = None
+ if project:
+ project_id = project.id
+ if project_id:
+ out['tenant_id'] = project_id
+ else:
+ raise SubnetConfigError(
+ 'Could not find project ID for project named - ' +
+ self.project_name)
+ if self.start and self.end:
+ out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
+ if self.gateway_ip:
+ if self.gateway_ip == 'none':
+ out['gateway_ip'] = None
+ else:
+ out['gateway_ip'] = self.gateway_ip
+ if self.enable_dhcp is not None:
+ out['enable_dhcp'] = self.enable_dhcp
+ if self.dns_nameservers and len(self.dns_nameservers) > 0:
+ out['dns_nameservers'] = self.dns_nameservers
+ if self.host_routes and len(self.host_routes) > 0:
+ out['host_routes'] = self.host_routes
+ if self.destination:
+ out['destination'] = self.destination
+ if self.nexthop:
+ out['nexthop'] = self.nexthop
+ if self.ipv6_ra_mode:
+ out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
+ if self.ipv6_address_mode:
+ out['ipv6_address_mode'] = self.ipv6_address_mode.value
+ return out
+
+
+def map_mode(mode):
+ """
+ Takes a the direction value maps it to the Direction enum. When None return
+ None
+ :param mode: the mode value
+ :return: the IPv6Mode enum object
+ :raise: SubnetConfigError if value is invalid
+ """
+ if not mode:
+ return None
+ if isinstance(mode, IPv6Mode):
+ return mode
+ elif isinstance(mode, str):
+ mode_str = str(mode)
+ if mode_str == 'slaac':
+ return IPv6Mode.slaac
+ elif mode_str == 'dhcpv6-stateful':
+ return IPv6Mode.stateful
+ elif mode_str == 'stateful':
+ return IPv6Mode.stateful
+ elif mode_str == 'dhcpv6-stateless':
+ return IPv6Mode.stateless
+ elif mode_str == 'stateless':
+ return IPv6Mode.stateless
+ else:
+ raise SubnetConfigError('Invalid mode - ' + mode_str)
+ else:
+ return map_mode(mode.value)
+
+
+class SubnetConfigError(Exception):
+ """
+ Exception to be thrown when subnet settings attributes are incorrect
+ """
+
+
+class PortConfig(object):
+ """
+ Class representing a port configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: A symbolic name for the port (optional).
+ :param network_name: The name of the network on which to create the
+ port (required).
+ :param admin_state_up: A boolean value denoting the administrative
+ status of the port (default = True)
+ :param project_name: The name of the project who owns the network.
+ Only administrative users can specify a project ID
+ other than their own. You cannot change this value
+ through authorization policies (optional)
+ :param mac_address: The MAC address. If you specify an address that is
+ not valid, a Bad Request (400) status code is
+ returned. If you do not specify a MAC address,
+ OpenStack Networking tries to allocate one. If a
+ failure occurs, a Service Unavailable (503) status
+ code is returned (optional)
+ :param ip_addrs: A list of dict objects where each contains two keys
+ 'subnet_name' and 'ip' values which will get mapped to
+ self.fixed_ips. These values will be directly
+ translated into the fixed_ips dict (optional)
+ :param security_groups: One or more security group IDs.
+ :param port_security_enabled: When True, security groups will be
+ applied to the port else not
+ (default - True)
+ :param allowed_address_pairs: A dictionary containing a set of zero or
+ more allowed address pairs. An address
+ pair contains an IP address and MAC
+ address (optional)
+ :param opt_value: The extra DHCP option value (optional)
+ :param opt_name: The extra DHCP option name (optional)
+ :param device_owner: The ID of the entity that uses this port.
+ For example, a DHCP agent (optional)
+ :param device_id: The ID of the device that uses this port.
+ For example, a virtual server (optional)
+ :param extra_dhcp_opts: k/v of options to use with your DHCP (optional)
+ :return:
+ """
+ if 'port' in kwargs:
+ kwargs = kwargs['port']
+
+ self.name = kwargs.get('name')
+ self.network_name = kwargs.get('network_name')
+
+ if kwargs.get('admin_state_up') is not None:
+ self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
+ else:
+ self.admin_state_up = True
+
+ self.project_name = kwargs.get('project_name')
+ self.mac_address = kwargs.get('mac_address')
+ self.ip_addrs = kwargs.get('ip_addrs')
+ self.security_groups = kwargs.get('security_groups')
+
+ if kwargs.get('port_security_enabled') is not None:
+ self.port_security_enabled = str2bool(
+ str(kwargs['port_security_enabled']))
+ else:
+ self.port_security_enabled = None
+
+ self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
+ self.opt_value = kwargs.get('opt_value')
+ self.opt_name = kwargs.get('opt_name')
+ self.device_owner = kwargs.get('device_owner')
+ self.device_id = kwargs.get('device_id')
+ self.extra_dhcp_opts = kwargs.get('extra_dhcp_opts')
+
+ if not self.network_name:
+ raise PortConfigError(
+ 'The attribute network_name is required')
+
+ def __get_fixed_ips(self, neutron, network):
+ """
+ Sets the self.fixed_ips value
+ :param neutron: the Neutron client
+ :param network: the SNAPS-OO network domain object
+ :return: None
+ """
+ fixed_ips = list()
+ if self.ip_addrs:
+
+ for ip_addr_dict in self.ip_addrs:
+ subnet = neutron_utils.get_subnet(
+ neutron, network, subnet_name=ip_addr_dict['subnet_name'])
+ if subnet:
+ if 'ip' in ip_addr_dict:
+ fixed_ips.append({'ip_address': ip_addr_dict['ip'],
+ 'subnet_id': subnet.id})
+ else:
+ raise PortConfigError(
+ 'Invalid port configuration, subnet does not exist '
+ 'with name - ' + ip_addr_dict['subnet_name'])
+
+ return fixed_ips
+
+ def dict_for_neutron(self, neutron, os_creds):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron
+ API
+
+ TODO - expand automated testing to exercise all parameters
+ :param neutron: the Neutron client
+ :param os_creds: the OpenStack credentials
+ :return: the dictionary object
+ """
+ out = dict()
+ session = keystone_utils.keystone_session(os_creds)
+ keystone = keystone_utils.keystone_client(os_creds, session)
+
+ project_name = os_creds.project_name
+ if self.project_name:
+ project_name = project_name
+ try:
+ network = neutron_utils.get_network(
+ neutron, keystone, network_name=self.network_name)
+ if network and not (network.shared or network.external):
+ network = neutron_utils.get_network(
+ neutron, keystone, network_name=self.network_name,
+ project_name=project_name)
+ finally:
+ if session:
+ keystone_utils.close_session(session)
+
+ if not network:
+ raise PortConfigError(
+ 'Cannot locate network with name - ' + self.network_name
+ + ' in project - ' + str(project_name))
+
+ out['network_id'] = network.id
+
+ if self.admin_state_up is not None:
+ out['admin_state_up'] = self.admin_state_up
+ if self.name:
+ out['name'] = self.name
+ if self.project_name:
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
+ project_id = None
+ if project:
+ project_id = project.id
+ if project_id:
+ out['tenant_id'] = project_id
+ else:
+ raise PortConfigError(
+ 'Could not find project ID for project named - ' +
+ self.project_name)
+ if self.mac_address:
+ out['mac_address'] = self.mac_address
+
+ fixed_ips = self.__get_fixed_ips(neutron, network)
+ if fixed_ips and len(fixed_ips) > 0:
+ out['fixed_ips'] = fixed_ips
+
+ if self.security_groups:
+ sec_grp_ids = list()
+ for sec_grp_name in self.security_groups:
+ sec_grp = neutron_utils.get_security_group(
+ neutron, keystone, sec_grp_name=sec_grp_name,
+ project_name=self.project_name)
+ if sec_grp:
+ sec_grp_ids.append(sec_grp.id)
+ out['security_groups'] = sec_grp_ids
+ if self.port_security_enabled is not None:
+ out['port_security_enabled'] = self.port_security_enabled
+ if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
+ out['allowed_address_pairs'] = self.allowed_address_pairs
+ if self.opt_value:
+ out['opt_value'] = self.opt_value
+ if self.opt_name:
+ out['opt_name'] = self.opt_name
+ if self.device_owner:
+ out['device_owner'] = self.device_owner
+ if self.device_id:
+ out['device_id'] = self.device_id
+ if self.extra_dhcp_opts:
+ out['extra_dhcp_opts'] = self.extra_dhcp_opts
+ return {'port': out}
+
+ def __eq__(self, other):
+ return (self.name == other.name and
+ self.network_name == other.network_name and
+ self.admin_state_up == other.admin_state_up and
+ self.project_name == other.project_name and
+ self.mac_address == other.mac_address and
+ self.ip_addrs == other.ip_addrs and
+ # self.fixed_ips == other.fixed_ips and
+ self.security_groups == other.security_groups and
+ self.allowed_address_pairs == other.allowed_address_pairs and
+ self.opt_value == other.opt_value and
+ self.opt_name == other.opt_name and
+ self.device_owner == other.device_owner and
+ self.device_id == other.device_id)
+
+
+class PortConfigError(Exception):
+ """
+ Exception to be thrown when port settings attributes are incorrect
+ """
diff --git a/snaps/config/project.py b/snaps/config/project.py
new file mode 100644
index 0000000..d6d175f
--- /dev/null
+++ b/snaps/config/project.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+
+class ProjectConfig(object):
+ """
+ Class to hold the configuration settings required for creating OpenStack
+ project objects
+ """
+
+ def __init__(self, **kwargs):
+
+ """
+ Constructor
+ :param name: the project's name (required)
+ :param domain or domain_name: the project's domain name
+ (default = 'Default').
+ Field is used for v3 clients
+ :param description: the description (optional)
+ :param users: list of users to associate project to (optional)
+ :param enabled: denotes whether or not the project is enabled
+ (default True)
+ :param quotas: quota values to override (optional)
+ """
+
+ self.name = kwargs.get('name')
+ self.domain_name = kwargs.get(
+ 'domain_name', kwargs.get('domain', 'Default'))
+
+ self.description = kwargs.get('description')
+ if kwargs.get('enabled') is not None:
+ self.enabled = kwargs['enabled']
+ else:
+ self.enabled = True
+
+ self.users = kwargs.get('users', list())
+
+ self.quotas = kwargs.get('quotas')
+
+ if not self.name:
+ raise ProjectConfigError(
+ "The attribute name is required for ProjectConfig")
+
+
+class ProjectConfigError(Exception):
+ """
+ Exception to be thrown when project settings attributes are incorrect
+ """
diff --git a/snaps/config/qos.py b/snaps/config/qos.py
new file mode 100644
index 0000000..e507d2d
--- /dev/null
+++ b/snaps/config/qos.py
@@ -0,0 +1,92 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import enum
+
+
+class Consumer(enum.Enum):
+ """
+ QoS Specification consumer types
+ """
+ front_end = 'front-end'
+ back_end = 'back-end'
+ both = 'both'
+
+
+class QoSConfig(object):
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the qos's name (required)
+ :param consumer: the qos's consumer type of the enum type Consumer
+ (required)
+ :param specs: dict of key/values
+ """
+
+ self.name = kwargs.get('name')
+
+ if kwargs.get('consumer'):
+ self.consumer = map_consumer(kwargs['consumer'])
+ else:
+ self.consumer = None
+
+ self.specs = kwargs.get('specs')
+ if not self.specs:
+ self.specs = dict()
+
+ if not self.name or not self.consumer:
+ raise QoSConfigError(
+ "The attributes name and consumer are required")
+
+
+def map_consumer(consumer):
+ """
+ Takes a the protocol value maps it to the Consumer enum. When None return
+ None
+ :param consumer: the value to map to the Enum
+ :return: the Protocol enum object
+ :raise: Exception if value is invalid
+ """
+ if not consumer:
+ return None
+ elif isinstance(consumer, Consumer):
+ return consumer
+ elif isinstance(consumer, str):
+ proto_str = str(consumer)
+ if proto_str == 'front-end':
+ return Consumer.front_end
+ elif proto_str == 'back-end':
+ return Consumer.back_end
+ elif proto_str == 'both':
+ return Consumer.both
+ else:
+ raise QoSConfigError('Invalid Consumer - ' + proto_str)
+ else:
+ if consumer.value == 'front-end':
+ return Consumer.front_end
+ elif consumer.value == 'back-end':
+ return Consumer.back_end
+ elif consumer.value == 'both':
+ return Consumer.both
+ else:
+ raise QoSConfigError('Invalid Consumer - ' + consumer.value)
+
+
+class QoSConfigError(Exception):
+ """
+ Exception to be thrown when an qos settings are incorrect
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
diff --git a/snaps/config/router.py b/snaps/config/router.py
new file mode 100644
index 0000000..2a0b6a4
--- /dev/null
+++ b/snaps/config/router.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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 snaps.config.network import PortConfig
+from snaps.openstack.utils import neutron_utils, keystone_utils
+
+
+class RouterConfig(object):
+ """
+ Class representing a router configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor - all parameters are optional
+ :param name: The router name.
+ :param project_name: The name of the project who owns the network. Only
+ administrative users can specify a project ID
+ other than their own. You cannot change this value
+ through authorization policies.
+ :param external_gateway: Name of the external network to which to route
+ :param admin_state_up: The administrative status of the router.
+ True = up / False = down (default True)
+ :param internal_subnets: List of subnet names to which to connect this
+ router (this way is deprecated).
+ *** NEW WAY ***
+ List of dict where the key is 'subnet' that
+ contains members with the following keys:
+ project_name, network_name, and subnet_name
+ :param port_settings: List of PortConfig objects
+ :return:
+ """
+ self.name = kwargs.get('name')
+ self.project_name = kwargs.get('project_name')
+ self.external_gateway = kwargs.get('external_gateway')
+
+ self.admin_state_up = kwargs.get('admin_state_up', True)
+ self.enable_snat = kwargs.get('enable_snat')
+ if kwargs.get('internal_subnets'):
+ self.internal_subnets = kwargs['internal_subnets']
+ if isinstance(self.internal_subnets, dict):
+ if 'subnet' not in self.internal_subnets:
+ raise RouterConfigError(
+ 'subnet is a required key to internal_subnets')
+ if 'project_name' not in self.internal_subnets['subnet']:
+ raise RouterConfigError(
+ 'subnet.project is a required key to subnet')
+ if 'network_name' not in self.internal_subnets['subnet']:
+ raise RouterConfigError(
+ 'network_name is a required key to subnet')
+ if 'subnet_name' not in self.internal_subnets['subnet']:
+ raise RouterConfigError(
+ 'subnet_name is a required key to subnet')
+ else:
+ self.internal_subnets = list()
+
+ self.port_settings = list()
+ if kwargs.get('interfaces', kwargs.get('port_settings')):
+ interfaces = kwargs.get('interfaces', kwargs.get('port_settings'))
+ for interface in interfaces:
+ if isinstance(interface, PortConfig):
+ self.port_settings.append(interface)
+ else:
+ self.port_settings.append(
+ PortConfig(**interface['port']))
+
+ if not self.name:
+ raise RouterConfigError('Name is required')
+
+ def dict_for_neutron(self, neutron, os_creds):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron
+ API
+
+ TODO - expand automated testing to exercise all parameters
+ :param neutron: The neutron client to retrieve external network
+ information if necessary
+ :param os_creds: The OpenStack credentials for retrieving the keystone
+ client for looking up the project ID when the
+ self.project_name is not None
+ :return: the dictionary object
+ """
+ out = dict()
+ ext_gw = dict()
+
+ session = keystone_utils.keystone_session(os_creds)
+ keystone = keystone_utils.keystone_client(os_creds, session)
+ try:
+ if self.name:
+ out['name'] = self.name
+ if self.project_name:
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
+ if project:
+ out['tenant_id'] = project.id
+ else:
+ raise RouterConfigError(
+ 'Could not find project ID for project named - ' +
+ self.project_name)
+ if self.admin_state_up is not None:
+ out['admin_state_up'] = self.admin_state_up
+ if self.external_gateway:
+ ext_net = neutron_utils.get_network(
+ neutron, keystone, network_name=self.external_gateway)
+ if ext_net:
+ ext_gw['network_id'] = ext_net.id
+ out['external_gateway_info'] = ext_gw
+ else:
+ raise RouterConfigError(
+ 'Could not find the external network named - ' +
+ self.external_gateway)
+ finally:
+ keystone_utils.close_session(session)
+
+ return {'router': out}
+
+
+class RouterConfigError(Exception):
+ """
+ Exception to be thrown when router settings attributes are incorrect
+ """
diff --git a/snaps/config/security_group.py b/snaps/config/security_group.py
new file mode 100644
index 0000000..9e485c3
--- /dev/null
+++ b/snaps/config/security_group.py
@@ -0,0 +1,399 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import enum
+
+from snaps.openstack.utils import keystone_utils, neutron_utils
+
+
+class SecurityGroupConfig(object):
+ """
+ Class representing a keypair configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: The security group's name (required)
+ :param description: The security group's description (optional)
+ :param project_name: The name of the project under which the security
+ group will be created
+ :param rule_settings: a list of SecurityGroupRuleConfig objects
+ :return:
+ """
+ self.name = kwargs.get('name')
+ self.description = kwargs.get('description')
+ self.project_name = kwargs.get('project_name')
+ self.rule_settings = list()
+
+ rule_settings = kwargs.get('rules')
+ if not rule_settings:
+ rule_settings = kwargs.get('rule_settings')
+
+ if rule_settings:
+ for rule_setting in rule_settings:
+ if isinstance(rule_setting, SecurityGroupRuleConfig):
+ self.rule_settings.append(rule_setting)
+ else:
+ rule_setting['sec_grp_name'] = self.name
+ self.rule_settings.append(SecurityGroupRuleConfig(
+ **rule_setting))
+
+ if not self.name:
+ raise SecurityGroupConfigError('The attribute name is required')
+
+ for rule_setting in self.rule_settings:
+ if rule_setting.sec_grp_name != self.name:
+ raise SecurityGroupConfigError(
+ 'Rule settings must correspond with the name of this '
+ 'security group')
+
+ def dict_for_neutron(self, keystone):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron
+ API
+
+ TODO - expand automated testing to exercise all parameters
+ :param keystone: the Keystone client
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.name:
+ out['name'] = self.name
+ if self.description:
+ out['description'] = self.description
+ if self.project_name:
+ project = keystone_utils.get_project(
+ keystone=keystone, project_name=self.project_name)
+ project_id = None
+ if project:
+ project_id = project.id
+ if project_id:
+ out['tenant_id'] = project_id
+ else:
+ raise SecurityGroupConfigError(
+ 'Could not find project ID for project named - ' +
+ self.project_name)
+
+ return {'security_group': out}
+
+
+class Direction(enum.Enum):
+ """
+ A rule's direction
+ """
+ ingress = 'ingress'
+ egress = 'egress'
+
+
+class Protocol(enum.Enum):
+ """
+ A rule's protocol
+ """
+ ah = 51
+ dccp = 33
+ egp = 8
+ esp = 50
+ gre = 47
+ icmp = 1
+ icmpv6 = 58
+ igmp = 2
+ ipv6_encap = 41
+ ipv6_frag = 44
+ ipv6_icmp = 58
+ ipv6_nonxt = 59
+ ipv6_opts = 60
+ ipv6_route = 43
+ ospf = 89
+ pgm = 113
+ rsvp = 46
+ sctp = 132
+ tcp = 6
+ udp = 17
+ udplite = 136
+ vrrp = 112
+ any = 'any'
+ null = 'null'
+
+
+class Ethertype(enum.Enum):
+ """
+ A rule's ethertype
+ """
+ IPv4 = 4
+ IPv6 = 6
+
+
+class SecurityGroupConfigError(Exception):
+ """
+ Exception to be thrown when security group settings attributes are
+ invalid
+ """
+
+
+class SecurityGroupRuleConfig(object):
+ """
+ Class representing a keypair configuration
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor - all parameters are optional
+ :param sec_grp_name: The security group's name on which to add the
+ rule. (required)
+ :param description: The rule's description
+ :param direction: An enumeration of type
+ create_security_group.RULE_DIRECTION (required)
+ :param remote_group_id: The group ID to associate with this rule
+ (this should be changed to group name once
+ snaps support Groups) (optional)
+ :param protocol: An enumeration of type
+ create_security_group.RULE_PROTOCOL or a string value
+ that will be mapped accordingly (optional)
+ :param ethertype: An enumeration of type
+ create_security_group.RULE_ETHERTYPE (optional)
+ :param port_range_min: The minimum port number in the range that is
+ matched by the security group rule. When the
+ protocol is TCP or UDP, this value must be <=
+ port_range_max.
+ :param port_range_max: The maximum port number in the range that is
+ matched by the security group rule. When the
+ protocol is TCP or UDP, this value must be <=
+ port_range_max.
+ :param remote_ip_prefix: The remote IP prefix to associate with this
+ metering rule packet (optional)
+
+ TODO - Need to support the tenant...
+ """
+
+ self.description = kwargs.get('description')
+ self.sec_grp_name = kwargs.get('sec_grp_name')
+ self.remote_group_id = kwargs.get('remote_group_id')
+ self.direction = None
+ if kwargs.get('direction'):
+ self.direction = map_direction(kwargs['direction'])
+
+ self.protocol = None
+ if kwargs.get('protocol'):
+ self.protocol = map_protocol(kwargs['protocol'])
+ else:
+ self.protocol = Protocol.null
+
+ self.ethertype = None
+ if kwargs.get('ethertype'):
+ self.ethertype = map_ethertype(kwargs['ethertype'])
+
+ self.port_range_min = kwargs.get('port_range_min')
+ self.port_range_max = kwargs.get('port_range_max')
+ self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
+
+ if not self.direction or not self.sec_grp_name:
+ raise SecurityGroupRuleConfigError(
+ 'direction and sec_grp_name are required')
+
+ def dict_for_neutron(self, neutron, keystone, project_name):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron
+ API
+ :param neutron: the neutron client for performing lookups
+ :param keystone: the keystone client for performing lookups
+ :param project_name: the name of the project associated with the group
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.description:
+ out['description'] = self.description
+ if self.direction:
+ out['direction'] = self.direction.name
+ if self.port_range_min:
+ out['port_range_min'] = self.port_range_min
+ if self.port_range_max:
+ out['port_range_max'] = self.port_range_max
+ if self.ethertype:
+ out['ethertype'] = self.ethertype.name
+ if self.protocol and self.protocol.value != 'null':
+ out['protocol'] = self.protocol.value
+ if self.sec_grp_name:
+ sec_grp = neutron_utils.get_security_group(
+ neutron, keystone, sec_grp_name=self.sec_grp_name,
+ project_name=project_name)
+ if sec_grp:
+ out['security_group_id'] = sec_grp.id
+ else:
+ raise SecurityGroupRuleConfigError(
+ 'Cannot locate security group with name - ' +
+ self.sec_grp_name)
+ if self.remote_group_id:
+ out['remote_group_id'] = self.remote_group_id
+ if self.remote_ip_prefix:
+ out['remote_ip_prefix'] = self.remote_ip_prefix
+
+ return {'security_group_rule': out}
+
+ def rule_eq(self, rule):
+ """
+ Returns True if this setting created the rule
+ :param rule: the rule to evaluate
+ :return: T/F
+ """
+ if self.description is not None:
+ if rule.description is not None and rule.description != '':
+ return False
+ elif self.description != rule.description:
+ if rule.description != '':
+ return False
+
+ if self.direction.name != rule.direction:
+ return False
+
+ if self.ethertype and rule.ethertype:
+ if self.ethertype.name != rule.ethertype:
+ return False
+
+ if self.port_range_min and rule.port_range_min:
+ if self.port_range_min != rule.port_range_min:
+ return False
+
+ if self.port_range_max and rule.port_range_max:
+ if self.port_range_max != rule.port_range_max:
+ return False
+
+ if self.protocol and rule.protocol:
+ if self.protocol.name != rule.protocol:
+ return False
+
+ if self.remote_group_id and rule.remote_group_id:
+ if self.remote_group_id != rule.remote_group_id:
+ return False
+
+ if self.remote_ip_prefix and rule.remote_ip_prefix:
+ if self.remote_ip_prefix != rule.remote_ip_prefix:
+ return False
+
+ return True
+
+ def __eq__(self, other):
+ return (
+ self.description == other.description and
+ self.direction == other.direction and
+ self.port_range_min == other.port_range_min and
+ self.port_range_max == other.port_range_max and
+ self.ethertype == other.ethertype and
+ self.protocol == other.protocol and
+ self.sec_grp_name == other.sec_grp_name and
+ self.remote_group_id == other.remote_group_id and
+ self.remote_ip_prefix == other.remote_ip_prefix)
+
+ def __hash__(self):
+ return hash((self.sec_grp_name, self.description, self.direction,
+ self.remote_group_id,
+ self.protocol, self.ethertype, self.port_range_min,
+ self.port_range_max, self.remote_ip_prefix))
+
+
+def map_direction(direction):
+ """
+ Takes a the direction value maps it to the Direction enum. When None return
+ None
+ :param direction: the direction value
+ :return: the Direction enum object
+ :raise: Exception if value is invalid
+ """
+ if not direction or 'None' == str(direction):
+ return None
+ if isinstance(direction, enum.Enum):
+ if direction.__class__.__name__ == 'Direction':
+ return direction
+ else:
+ raise SecurityGroupRuleConfigError(
+ 'Invalid class - ' + direction.__class__.__name__)
+ elif isinstance(direction, str):
+ dir_str = str(direction)
+ if dir_str == 'egress':
+ return Direction.egress
+ elif dir_str == 'ingress':
+ return Direction.ingress
+ else:
+ raise SecurityGroupRuleConfigError(
+ 'Invalid Direction - ' + dir_str)
+ else:
+ return map_direction(str(direction))
+
+
+def map_protocol(protocol):
+ """
+ Takes a the protocol value maps it to the Protocol enum. When None return
+ None
+ :param protocol: the protocol value
+ :return: the Protocol enum object
+ :raise: Exception if value is invalid
+ """
+ if not protocol:
+ return None
+ elif isinstance(protocol, enum.Enum):
+ if protocol.__class__.__name__ == 'Protocol':
+ return protocol
+ else:
+ raise SecurityGroupRuleConfigError(
+ 'Invalid class - ' + protocol.__class__.__name__)
+ elif isinstance(protocol, str) or isinstance(protocol, int):
+ for proto_enum in Protocol:
+ if proto_enum.name == protocol or proto_enum.value == protocol:
+ if proto_enum == Protocol.any:
+ return Protocol.null
+ return proto_enum
+ raise SecurityGroupRuleConfigError(
+ 'Invalid Protocol - ' + str(protocol))
+ else:
+ return map_protocol(str(protocol))
+
+
+def map_ethertype(ethertype):
+ """
+ Takes a the ethertype value maps it to the Ethertype enum. When None return
+ None
+ :param ethertype: the ethertype value
+ :return: the Ethertype enum object
+ :raise: Exception if value is invalid
+ """
+ if not ethertype or 'None' == str(ethertype):
+ return None
+ elif isinstance(ethertype, enum.Enum):
+ if ethertype.__class__.__name__ == 'Ethertype':
+ return ethertype
+ else:
+ raise SecurityGroupRuleConfigError(
+ 'Invalid class - ' + ethertype.__class__.__name__)
+ elif isinstance(ethertype, str) or isinstance(ethertype, int):
+ eth_str = str(ethertype)
+ if eth_str == 'IPv6' or eth_str == '6':
+ return Ethertype.IPv6
+ elif eth_str == 'IPv4' or eth_str == '4':
+ return Ethertype.IPv4
+ else:
+ raise SecurityGroupRuleConfigError(
+ 'Invalid Ethertype - ' + eth_str)
+ else:
+ return map_ethertype(str(ethertype))
+
+
+class SecurityGroupRuleConfigError(Exception):
+ """
+ Exception to be thrown when security group rule settings attributes are
+ invalid
+ """
diff --git a/snaps/config/stack.py b/snaps/config/stack.py
new file mode 100644
index 0000000..3fc820e
--- /dev/null
+++ b/snaps/config/stack.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+STACK_DELETE_TIMEOUT = 1200
+STACK_COMPLETE_TIMEOUT = 1200
+STACK_UPDATE_TIMEOUT = 1200
+POLL_INTERVAL = 3
+STATUS_CREATE_FAILED = 'CREATE_FAILED'
+STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE'
+STATUS_UPDATE_FAILED = 'UPDATE_FAILED'
+STATUS_UPDATE_COMPLETE = 'UPDATE_COMPLETE'
+STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE'
+STATUS_DELETE_FAILED = 'DELETE_FAILED'
+
+
+class StackConfig(object):
+ """
+ Configuration for Heat stack
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the stack's name (required)
+ :param template: the heat template in dict() format (required if
+ template_path attribute is None)
+ :param template_path: the location of the heat template file (required
+ if template attribute is None)
+ :param resource_files: List of file paths to the resources referred to
+ by the template
+ :param env_values: dict() of strings for substitution of template
+ default values (optional)
+ """
+
+ self.name = kwargs.get('name')
+ self.template = kwargs.get('template')
+ self.template_path = kwargs.get('template_path')
+ self.resource_files = kwargs.get('resource_files')
+ self.env_values = kwargs.get('env_values')
+
+ if 'stack_create_timeout' in kwargs:
+ self.stack_create_timeout = kwargs['stack_create_timeout']
+ else:
+ self.stack_create_timeout = STACK_COMPLETE_TIMEOUT
+
+ if not self.name:
+ raise StackConfigError('name is required')
+
+ if not self.template and not self.template_path:
+ raise StackConfigError('A Heat template is required')
+
+ if self.resource_files and not isinstance(self.resource_files, list):
+ raise StackConfigError(
+ 'resource_files must be a list when not None')
+
+ def __eq__(self, other):
+ return (self.name == other.name and
+ self.template == other.template and
+ self.template_path == other.template_path and
+ self.env_values == other.env_values and
+ self.stack_create_timeout == other.stack_create_timeout)
+
+
+class StackConfigError(Exception):
+ """
+ Exception to be thrown when an stack configuration are incorrect
+ """
diff --git a/snaps/config/tests/__init__.py b/snaps/config/tests/__init__.py
new file mode 100644
index 0000000..271c742
--- /dev/null
+++ b/snaps/config/tests/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+__author__ = 'spisarski'
diff --git a/snaps/config/tests/cluster_template_tests.py b/snaps/config/tests/cluster_template_tests.py
new file mode 100644
index 0000000..e06b783
--- /dev/null
+++ b/snaps/config/tests/cluster_template_tests.py
@@ -0,0 +1,180 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.cluster_template import (
+ ClusterTemplateConfig, ClusterTemplateConfigError, ServerType,
+ DockerStorageDriver, ContainerOrchestrationEngine)
+
+
+class ClusterTemplateConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the ClusterTemplateConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(ClusterTemplateConfigError):
+ ClusterTemplateConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(ClusterTemplateConfigError):
+ ClusterTemplateConfig(config=dict())
+
+ def test_name_only(self):
+ with self.assertRaises(ClusterTemplateConfigError):
+ ClusterTemplateConfig(name='foo')
+
+ def test_minimal_named(self):
+ config = ClusterTemplateConfig(
+ name='foo', image='bar', keypair='keys', external_net='external')
+ self.assertIsNotNone(config)
+ self.assertEqual('foo', config.name)
+ self.assertEqual('bar', config.image)
+ self.assertEqual('keys', config.keypair)
+ self.assertIsNone(config.network_driver)
+ self.assertEqual('external', config.external_net)
+ self.assertTrue(config.floating_ip_enabled)
+ self.assertEqual(3, config.docker_volume_size)
+ self.assertEqual(ServerType.vm, config.server_type)
+ self.assertIsNone(config.flavor)
+ self.assertIsNone(config.master_flavor)
+ self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe)
+ self.assertIsNone(config.fixed_net)
+ self.assertIsNone(config.fixed_subnet)
+ self.assertTrue(config.registry_enabled)
+ self.assertIsNone(config.insecure_registry)
+ self.assertEqual(DockerStorageDriver.devicemapper,
+ config.docker_storage_driver)
+ self.assertIsNone(config.dns_nameserver)
+ self.assertFalse(config.public)
+ self.assertFalse(config.tls_disabled)
+ self.assertIsNone(config.http_proxy)
+ self.assertIsNone(config.https_proxy)
+ self.assertIsNone(config.no_proxy)
+ self.assertIsNone(config.volume_driver)
+ self.assertTrue(config.master_lb_enabled)
+ self.assertIsNone(config.labels)
+
+ def test_minimal_config(self):
+ config = ClusterTemplateConfig(
+ **{'name': 'foo', 'image': 'bar', 'keypair': 'keys',
+ 'external_net': 'external'})
+ self.assertIsNotNone(config)
+ self.assertEqual('foo', config.name)
+ self.assertEqual('bar', config.image)
+ self.assertEqual('keys', config.keypair)
+ self.assertIsNone(config.network_driver)
+ self.assertEqual('external', config.external_net)
+ self.assertTrue(config.floating_ip_enabled)
+ self.assertEqual(3, config.docker_volume_size)
+ self.assertEqual(ServerType.vm, config.server_type)
+ self.assertIsNone(config.flavor)
+ self.assertIsNone(config.master_flavor)
+ self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe)
+ self.assertIsNone(config.fixed_net)
+ self.assertIsNone(config.fixed_subnet)
+ self.assertTrue(config.registry_enabled)
+ self.assertIsNone(config.insecure_registry)
+ self.assertEqual(DockerStorageDriver.devicemapper,
+ config.docker_storage_driver)
+ self.assertIsNone(config.dns_nameserver)
+ self.assertFalse(config.public)
+ self.assertFalse(config.tls_disabled)
+ self.assertIsNone(config.http_proxy)
+ self.assertIsNone(config.https_proxy)
+ self.assertIsNone(config.no_proxy)
+ self.assertIsNone(config.volume_driver)
+ self.assertTrue(config.master_lb_enabled)
+ self.assertIsNone(config.labels)
+
+ def test_all_named(self):
+ labels = {'foo': 'bar'}
+ config = ClusterTemplateConfig(
+ name='foo', image='bar', keypair='keys', network_driver='driver',
+ external_net='external', docker_volume_size=99,
+ server_type=ServerType.baremetal, flavor='testFlavor',
+ master_flavor='masterFlavor',
+ coe=ContainerOrchestrationEngine.kubernetes, fixed_net='fixedNet',
+ fixed_subnet='fixedSubnet', registry_enabled=False,
+ docker_storage_driver=DockerStorageDriver.overlay,
+ dns_nameserver='8.8.4.4', public=True, tls=False,
+ http_proxy='http://foo:8080', https_proxy='https://foo:443',
+ no_proxy='foo,bar', volume_driver='volDriver',
+ master_lb_enabled=False, labels=labels)
+ self.assertIsNotNone(config)
+ self.assertEqual('foo', config.name)
+ self.assertEqual('bar', config.image)
+ self.assertEqual('keys', config.keypair)
+ self.assertEqual('driver', config.network_driver)
+ self.assertEqual('external', config.external_net)
+ self.assertEqual(99, config.docker_volume_size)
+ self.assertEqual(ServerType.baremetal, config.server_type)
+ self.assertEqual('testFlavor', config.flavor)
+ self.assertEqual('masterFlavor', config.master_flavor)
+ self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe)
+ self.assertEqual('fixedNet', config.fixed_net)
+ self.assertEqual('fixedSubnet', config.fixed_subnet)
+ self.assertFalse(config.registry_enabled)
+ self.assertEqual(DockerStorageDriver.overlay,
+ config.docker_storage_driver)
+ self.assertEqual('8.8.4.4', config.dns_nameserver)
+ self.assertTrue(config.public)
+ self.assertFalse(config.tls_disabled)
+ self.assertEqual('http://foo:8080', config.http_proxy)
+ self.assertEqual('https://foo:443', config.https_proxy)
+ self.assertEqual('foo,bar', config.no_proxy)
+ self.assertEqual('volDriver', config.volume_driver)
+ self.assertFalse(config.master_lb_enabled)
+ self.assertEqual(labels, config.labels)
+
+ def test_all_config(self):
+ labels = {'foo': 'bar'}
+ config = ClusterTemplateConfig(**{
+ 'name': 'foo', 'image': 'bar', 'keypair': 'keys',
+ 'network_driver': 'driver', 'external_net': 'external',
+ 'docker_volume_size': '99', 'server_type': 'baremetal',
+ 'flavor': 'testFlavor', 'master_flavor': 'masterFlavor',
+ 'coe': 'kubernetes', 'fixed_net': 'fixedNet',
+ 'fixed_subnet': 'fixedSubnet', 'registry_enabled': 'false',
+ 'docker_storage_driver': 'overlay', 'dns_nameserver': '8.8.4.4',
+ 'public': 'true', 'tls': 'false', 'http_proxy': 'http://foo:8080',
+ 'https_proxy': 'https://foo:443', 'no_proxy': 'foo,bar',
+ 'volume_driver': 'volDriver', 'master_lb_enabled': 'false',
+ 'labels': labels})
+ self.assertIsNotNone(config)
+ self.assertEqual('foo', config.name)
+ self.assertEqual('bar', config.image)
+ self.assertEqual('keys', config.keypair)
+ self.assertEqual('driver', config.network_driver)
+ self.assertEqual('external', config.external_net)
+ self.assertEqual(99, config.docker_volume_size)
+ self.assertEqual(ServerType.baremetal, config.server_type)
+ self.assertEqual('testFlavor', config.flavor)
+ self.assertEqual('masterFlavor', config.master_flavor)
+ self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe)
+ self.assertEqual('fixedNet', config.fixed_net)
+ self.assertEqual('fixedSubnet', config.fixed_subnet)
+ self.assertFalse(config.registry_enabled)
+ self.assertEqual(DockerStorageDriver.overlay,
+ config.docker_storage_driver)
+ self.assertEqual('8.8.4.4', config.dns_nameserver)
+ self.assertTrue(config.public)
+ self.assertFalse(config.tls_disabled)
+ self.assertEqual('http://foo:8080', config.http_proxy)
+ self.assertEqual('https://foo:443', config.https_proxy)
+ self.assertEqual('foo,bar', config.no_proxy)
+ self.assertEqual('volDriver', config.volume_driver)
+ self.assertFalse(config.master_lb_enabled)
+ self.assertEqual(labels, config.labels)
diff --git a/snaps/config/tests/flavor_tests.py b/snaps/config/tests/flavor_tests.py
new file mode 100644
index 0000000..15cd99a
--- /dev/null
+++ b/snaps/config/tests/flavor_tests.py
@@ -0,0 +1,254 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.flavor import FlavorConfig, FlavorConfigError
+
+
+class FlavorConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the FlavorConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(config=dict())
+
+ def test_name_only(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(config={'name': 'foo'})
+
+ def test_name_ram_only(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1)
+
+ def test_config_with_name_ram_only(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(config={'name': 'foo', 'ram': 1})
+
+ def test_name_ram_disk_only(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=1)
+
+ def test_config_with_name_ram_disk_only(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(config={'name': 'foo', 'ram': 1, 'disk': 1})
+
+ def test_ram_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram='bar', disk=2, vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor=6.0,
+ is_public=False)
+
+ def test_config_ram_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 'bar', 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_ram_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1.5, disk=2, vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor=6.0, is_public=False)
+
+ def test_config_ram_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1.5, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_disk_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk='bar', vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor=6.0,
+ is_public=False)
+
+ def test_config_disk_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 'bar', 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_disk_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2.5, vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor=6.0, is_public=False)
+
+ def test_config_disk_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2.5, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_vcpus_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus='bar', ephemeral=4,
+ swap=5, rxtx_factor=6.0,
+ is_public=False)
+
+ def test_config_vcpus_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 'bar',
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_ephemeral_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral='bar',
+ swap=5, rxtx_factor=6.0,
+ is_public=False)
+
+ def test_config_ephemeral_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 'bar', 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_ephemeral_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4.5,
+ swap=5, rxtx_factor=6.0, is_public=False)
+
+ def test_config_ephemeral_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4.5, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_swap_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
+ swap='bar', rxtx_factor=6.0,
+ is_public=False)
+
+ def test_config_swap_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 'bar',
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_swap_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
+ swap=5.5, rxtx_factor=6.0, is_public=False)
+
+ def test_config_swap_float(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5.5,
+ 'rxtx_factor': 6.0, 'is_public': False})
+
+ def test_rxtx_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor='bar', is_public=False)
+
+ def test_config_rxtx_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 'bar', 'is_public': False})
+
+ def test_is_pub_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor=6.0, is_public='bar')
+
+ def test_config_is_pub_string(self):
+ with self.assertRaises(FlavorConfigError):
+ FlavorConfig(
+ config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5,
+ 'rxtx_factor': 6.0, 'is_public': 'bar'})
+
+ def test_name_ram_disk_vcpus_only(self):
+ settings = FlavorConfig(name='foo', ram=1, disk=2, vcpus=3)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('auto', settings.flavor_id)
+ self.assertEqual(1, settings.ram)
+ self.assertEqual(2, settings.disk)
+ self.assertEqual(3, settings.vcpus)
+ self.assertEqual(0, settings.ephemeral)
+ self.assertEqual(0, settings.swap)
+ self.assertEqual(1.0, settings.rxtx_factor)
+ self.assertEqual(True, settings.is_public)
+ self.assertEqual(None, settings.metadata)
+
+ def test_config_with_name_ram_disk_vcpus_only(self):
+ settings = FlavorConfig(
+ **{'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('auto', settings.flavor_id)
+ self.assertEqual(1, settings.ram)
+ self.assertEqual(2, settings.disk)
+ self.assertEqual(3, settings.vcpus)
+ self.assertEqual(0, settings.ephemeral)
+ self.assertEqual(0, settings.swap)
+ self.assertEqual(1.0, settings.rxtx_factor)
+ self.assertEqual(True, settings.is_public)
+ self.assertEqual(None, settings.metadata)
+
+ def test_all(self):
+ metadata = {'foo': 'bar'}
+ settings = FlavorConfig(
+ name='foo', flavor_id='bar', ram=1, disk=2, vcpus=3, ephemeral=4,
+ swap=5, rxtx_factor=6.0, is_public=False, metadata=metadata)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.flavor_id)
+ self.assertEqual(1, settings.ram)
+ self.assertEqual(2, settings.disk)
+ self.assertEqual(3, settings.vcpus)
+ self.assertEqual(4, settings.ephemeral)
+ self.assertEqual(5, settings.swap)
+ self.assertEqual(6.0, settings.rxtx_factor)
+ self.assertEqual(False, settings.is_public)
+ self.assertEqual(metadata, settings.metadata)
+
+ def test_config_all(self):
+ metadata = {'foo': 'bar'}
+ settings = FlavorConfig(
+ **{'name': 'foo', 'flavor_id': 'bar', 'ram': 1, 'disk': 2,
+ 'vcpus': 3,
+ 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0,
+ 'is_public': False,
+ 'metadata': metadata})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.flavor_id)
+ self.assertEqual(1, settings.ram)
+ self.assertEqual(2, settings.disk)
+ self.assertEqual(3, settings.vcpus)
+ self.assertEqual(4, settings.ephemeral)
+ self.assertEqual(5, settings.swap)
+ self.assertEqual(6.0, settings.rxtx_factor)
+ self.assertEqual(False, settings.is_public)
+ self.assertEqual(metadata, settings.metadata)
diff --git a/snaps/config/tests/image_tests.py b/snaps/config/tests/image_tests.py
new file mode 100644
index 0000000..8dcd2b4
--- /dev/null
+++ b/snaps/config/tests/image_tests.py
@@ -0,0 +1,226 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.image import ImageConfigError, ImageConfig
+
+
+class ImageConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the ImageConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(**{'name': 'foo'})
+
+ def test_name_user_only(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(name='foo', image_user='bar')
+
+ def test_config_with_name_user_only(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(**{'name': 'foo', 'image_user': 'bar'})
+
+ def test_name_user_format_only(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(name='foo', image_user='bar', img_format='qcow2')
+
+ def test_config_with_name_user_format_only(self):
+ with self.assertRaises(ImageConfigError):
+ ImageConfig(
+ **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2'})
+
+ def test_name_user_format_url_only(self):
+ settings = ImageConfig(name='foo', image_user='bar',
+ img_format='qcow2', url='http://foo.com')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertEqual('http://foo.com', settings.url)
+ self.assertIsNone(settings.image_file)
+ self.assertFalse(settings.exists)
+ self.assertFalse(settings.public)
+ self.assertIsNone(settings.nic_config_pb_loc)
+
+ def test_name_user_format_url_only_properties(self):
+ properties = {'hw_video_model': 'vga'}
+ settings = ImageConfig(name='foo', image_user='bar',
+ img_format='qcow2', url='http://foo.com',
+ extra_properties=properties)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertEqual('http://foo.com', settings.url)
+ self.assertEqual(properties, settings.extra_properties)
+ self.assertIsNone(settings.image_file)
+ self.assertFalse(settings.exists)
+ self.assertFalse(settings.public)
+ self.assertIsNone(settings.nic_config_pb_loc)
+
+ def test_config_with_name_user_format_url_only(self):
+ settings = ImageConfig(
+ **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2',
+ 'download_url': 'http://foo.com'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertEqual('http://foo.com', settings.url)
+ self.assertIsNone(settings.image_file)
+ self.assertFalse(settings.exists)
+ self.assertFalse(settings.public)
+ self.assertIsNone(settings.nic_config_pb_loc)
+
+ def test_name_user_format_file_only(self):
+ settings = ImageConfig(name='foo', image_user='bar',
+ img_format='qcow2',
+ image_file='/foo/bar.qcow')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertIsNone(settings.url)
+ self.assertEqual('/foo/bar.qcow', settings.image_file)
+ self.assertFalse(settings.exists)
+ self.assertFalse(settings.public)
+ self.assertIsNone(settings.nic_config_pb_loc)
+
+ def test_config_with_name_user_format_file_only(self):
+ settings = ImageConfig(
+ **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2',
+ 'image_file': '/foo/bar.qcow'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertIsNone(settings.url)
+ self.assertEqual('/foo/bar.qcow', settings.image_file)
+ self.assertFalse(settings.exists)
+ self.assertFalse(settings.public)
+ self.assertIsNone(settings.nic_config_pb_loc)
+
+ def test_all_url(self):
+ properties = {'hw_video_model': 'vga'}
+ kernel_settings = ImageConfig(name='kernel', url='http://kernel.com',
+ image_user='bar', img_format='qcow2')
+ ramdisk_settings = ImageConfig(name='ramdisk',
+ url='http://ramdisk.com',
+ image_user='bar', img_format='qcow2')
+ settings = ImageConfig(name='foo', image_user='bar',
+ img_format='qcow2', url='http://foo.com',
+ extra_properties=properties,
+ nic_config_pb_loc='/foo/bar',
+ kernel_image_settings=kernel_settings,
+ ramdisk_image_settings=ramdisk_settings,
+ exists=True, public=True)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertEqual('http://foo.com', settings.url)
+ self.assertEqual(properties, settings.extra_properties)
+ self.assertIsNone(settings.image_file)
+ self.assertEqual('/foo/bar', settings.nic_config_pb_loc)
+ self.assertEqual('kernel', settings.kernel_image_settings.name)
+ self.assertEqual('http://kernel.com',
+ settings.kernel_image_settings.url)
+ self.assertEqual('bar', settings.kernel_image_settings.image_user)
+ self.assertEqual('qcow2', settings.kernel_image_settings.format)
+ self.assertEqual('ramdisk', settings.ramdisk_image_settings.name)
+ self.assertEqual('http://ramdisk.com',
+ settings.ramdisk_image_settings.url)
+ self.assertEqual('bar', settings.ramdisk_image_settings.image_user)
+ self.assertEqual('qcow2', settings.ramdisk_image_settings.format)
+ self.assertTrue(settings.exists)
+ self.assertTrue(settings.public)
+
+ def test_config_all_url(self):
+ settings = ImageConfig(
+ **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2',
+ 'download_url': 'http://foo.com',
+ 'extra_properties': '{\'hw_video_model\': \'vga\'}',
+ 'nic_config_pb_loc': '/foo/bar',
+ 'kernel_image_settings': {
+ 'name': 'kernel',
+ 'download_url': 'http://kernel.com',
+ 'image_user': 'bar',
+ 'format': 'qcow2'},
+ 'ramdisk_image_settings': {
+ 'name': 'ramdisk',
+ 'download_url': 'http://ramdisk.com',
+ 'image_user': 'bar',
+ 'format': 'qcow2'},
+ 'exists': True, 'public': True})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertEqual('http://foo.com', settings.url)
+ self.assertEqual('{\'hw_video_model\': \'vga\'}',
+ settings.extra_properties)
+ self.assertIsNone(settings.image_file)
+ self.assertEqual('/foo/bar', settings.nic_config_pb_loc)
+ self.assertEqual('kernel', settings.kernel_image_settings.name)
+ self.assertEqual('http://kernel.com',
+ settings.kernel_image_settings.url)
+ self.assertEqual('ramdisk', settings.ramdisk_image_settings.name)
+ self.assertEqual('http://ramdisk.com',
+ settings.ramdisk_image_settings.url)
+ self.assertTrue(settings.exists)
+ self.assertTrue(settings.public)
+
+ def test_all_file(self):
+ properties = {'hw_video_model': 'vga'}
+ settings = ImageConfig(name='foo', image_user='bar',
+ img_format='qcow2',
+ image_file='/foo/bar.qcow',
+ extra_properties=properties,
+ nic_config_pb_loc='/foo/bar', exists=True,
+ public=True)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertIsNone(settings.url)
+ self.assertEqual('/foo/bar.qcow', settings.image_file)
+ self.assertEqual(properties, settings.extra_properties)
+ self.assertEqual('/foo/bar', settings.nic_config_pb_loc)
+ self.assertTrue(settings.exists)
+ self.assertTrue(settings.public)
+
+ def test_config_all_file(self):
+ settings = ImageConfig(
+ **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2',
+ 'image_file': '/foo/bar.qcow',
+ 'extra_properties': '{\'hw_video_model\' : \'vga\'}',
+ 'nic_config_pb_loc': '/foo/bar', 'exists': True,
+ 'public': True})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.image_user)
+ self.assertEqual('qcow2', settings.format)
+ self.assertIsNone(settings.url)
+ self.assertEqual('/foo/bar.qcow', settings.image_file)
+ self.assertEqual('{\'hw_video_model\' : \'vga\'}',
+ settings.extra_properties)
+ self.assertEqual('/foo/bar', settings.nic_config_pb_loc)
+ self.assertTrue(settings.exists)
+ self.assertTrue(settings.public)
diff --git a/snaps/config/tests/keypair_tests.py b/snaps/config/tests/keypair_tests.py
new file mode 100644
index 0000000..6d0fec4
--- /dev/null
+++ b/snaps/config/tests/keypair_tests.py
@@ -0,0 +1,179 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.keypair import KeypairConfigError, KeypairConfig
+
+
+class KeypairConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the KeypairConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(KeypairConfigError):
+ KeypairConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(KeypairConfigError):
+ KeypairConfig(**dict())
+
+ def test_small_key_size(self):
+ with self.assertRaises(KeypairConfigError):
+ KeypairConfig(name='foo', key_size=511)
+
+ def test_name_only(self):
+ settings = KeypairConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertIsNone(settings.public_filepath)
+ self.assertIsNone(settings.private_filepath)
+ self.assertIsNone(settings.delete_on_clean)
+
+ def test_config_with_name_only(self):
+ settings = KeypairConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertIsNone(settings.public_filepath)
+ self.assertIsNone(settings.private_filepath)
+ self.assertIsNone(settings.delete_on_clean)
+
+ def test_name_pub_only(self):
+ settings = KeypairConfig(name='foo', public_filepath='/foo/bar.pub')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertIsNone(settings.private_filepath)
+ self.assertIsNone(settings.delete_on_clean)
+
+ def test_config_with_name_pub_only(self):
+ settings = KeypairConfig(
+ **{'name': 'foo', 'public_filepath': '/foo/bar.pub'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertIsNone(settings.private_filepath)
+ self.assertIsNone(settings.delete_on_clean)
+
+ def test_name_priv_only(self):
+ settings = KeypairConfig(name='foo', private_filepath='/foo/bar')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertIsNone(settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertIsNone(settings.delete_on_clean)
+
+ def test_config_with_name_priv_only(self):
+ settings = KeypairConfig(
+ **{'name': 'foo', 'private_filepath': '/foo/bar'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertIsNone(settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertIsNone(settings.delete_on_clean)
+
+ def test_all_delete_bool(self):
+ settings = KeypairConfig(
+ name='foo', public_filepath='/foo/bar.pub',
+ private_filepath='/foo/bar', delete_on_clean=True,
+ key_size=999)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(999, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertTrue(settings.delete_on_clean)
+
+ def test_all_delete_str_true_cap(self):
+ settings = KeypairConfig(
+ name='foo', public_filepath='/foo/bar.pub',
+ private_filepath='/foo/bar', delete_on_clean='True')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertTrue(settings.delete_on_clean)
+
+ def test_all_delete_str_true_lc(self):
+ settings = KeypairConfig(
+ name='foo', public_filepath='/foo/bar.pub',
+ private_filepath='/foo/bar', delete_on_clean='true')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertTrue(settings.delete_on_clean)
+
+ def test_all_delete_str_false_cap(self):
+ settings = KeypairConfig(
+ name='foo', public_filepath='/foo/bar.pub',
+ private_filepath='/foo/bar', delete_on_clean='False')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertFalse(settings.delete_on_clean)
+
+ def test_all_delete_str_false_lc(self):
+ settings = KeypairConfig(
+ name='foo', public_filepath='/foo/bar.pub',
+ private_filepath='/foo/bar', delete_on_clean='false',
+ key_size='999')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(999, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertFalse(settings.delete_on_clean)
+
+ def test_config_all_delete_false_bool(self):
+ settings = KeypairConfig(
+ **{'name': 'foo', 'public_filepath': '/foo/bar.pub',
+ 'private_filepath': '/foo/bar', 'delete_on_clean': False,
+ 'key_size': 999})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(999, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertFalse(settings.delete_on_clean)
+
+ def test_config_all_delete_false_str_cap(self):
+ settings = KeypairConfig(
+ **{'name': 'foo', 'public_filepath': '/foo/bar.pub',
+ 'private_filepath': '/foo/bar', 'delete_on_clean': 'False'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertFalse(settings.delete_on_clean)
+
+ def test_config_all_delete_false_str_lc(self):
+ settings = KeypairConfig(
+ **{'name': 'foo', 'public_filepath': '/foo/bar.pub',
+ 'private_filepath': '/foo/bar', 'delete_on_clean': 'false'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(1024, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertFalse(settings.delete_on_clean)
+
+ def test_config_all_delete_false_str_foo(self):
+ settings = KeypairConfig(
+ **{'name': 'foo', 'public_filepath': '/foo/bar.pub',
+ 'private_filepath': '/foo/bar', 'delete_on_clean': 'foo',
+ 'key_size': '999'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(999, settings.key_size)
+ self.assertEqual('/foo/bar.pub', settings.public_filepath)
+ self.assertEqual('/foo/bar', settings.private_filepath)
+ self.assertFalse(settings.delete_on_clean)
diff --git a/snaps/config/tests/network_tests.py b/snaps/config/tests/network_tests.py
new file mode 100644
index 0000000..cbc71af
--- /dev/null
+++ b/snaps/config/tests/network_tests.py
@@ -0,0 +1,339 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.network import (
+ NetworkConfigError, NetworkConfig, SubnetConfig, SubnetConfigError,
+ IPv6Mode, PortConfig, PortConfigError)
+
+
+class NetworkConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the NetworkConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(NetworkConfigError):
+ NetworkConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(NetworkConfigError):
+ NetworkConfig(**dict())
+
+ def test_name_only(self):
+ settings = NetworkConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertTrue(settings.admin_state_up)
+ self.assertIsNone(settings.shared)
+ self.assertIsNone(settings.project_name)
+ self.assertFalse(settings.external)
+ self.assertIsNone(settings.network_type)
+ self.assertIsNone(settings.segmentation_id)
+ self.assertEqual(0, len(settings.subnet_settings))
+ self.assertIsNone(settings.mtu)
+
+ def test_config_with_name_only(self):
+ settings = NetworkConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertTrue(settings.admin_state_up)
+ self.assertIsNone(settings.shared)
+ self.assertIsNone(settings.project_name)
+ self.assertFalse(settings.external)
+ self.assertIsNone(settings.network_type)
+ self.assertIsNone(settings.segmentation_id)
+ self.assertEqual(0, len(settings.subnet_settings))
+ self.assertIsNone(settings.mtu)
+
+ def test_all(self):
+ sub_settings = SubnetConfig(name='foo-subnet', cidr='10.0.0.0/24')
+ settings = NetworkConfig(
+ name='foo', admin_state_up=False, shared=True, project_name='bar',
+ external=True, network_type='vlan', physical_network='phy',
+ segmentation_id=2366, subnet_settings=[sub_settings], mtu=999)
+ self.assertEqual('foo', settings.name)
+ self.assertFalse(settings.admin_state_up)
+ self.assertTrue(settings.shared)
+ self.assertEqual('bar', settings.project_name)
+ self.assertTrue(settings.external)
+ self.assertEqual('vlan', settings.network_type)
+ self.assertEqual('phy', settings.physical_network)
+ self.assertEqual(2366, settings.segmentation_id)
+ self.assertEqual(1, len(settings.subnet_settings))
+ self.assertEqual('foo-subnet', settings.subnet_settings[0].name)
+ self.assertEqual(999, settings.mtu)
+
+ def test_config_all(self):
+ settings = NetworkConfig(
+ **{'name': 'foo', 'admin_state_up': False, 'shared': True,
+ 'project_name': 'bar', 'external': True, 'network_type': 'vlan',
+ 'physical_network': 'phy',
+ 'segmentation_id': 2366,
+ 'subnets':
+ [{'subnet': {'name': 'foo-subnet',
+ 'cidr': '10.0.0.0/24'}}],
+ 'mtu': 999})
+ self.assertEqual('foo', settings.name)
+ self.assertFalse(settings.admin_state_up)
+ self.assertTrue(settings.shared)
+ self.assertEqual('bar', settings.project_name)
+ self.assertTrue(settings.external)
+ self.assertEqual('vlan', settings.network_type)
+ self.assertEqual('phy', settings.physical_network)
+ self.assertEqual(2366, settings.segmentation_id)
+ self.assertEqual(1, len(settings.subnet_settings))
+ self.assertEqual('foo-subnet', settings.subnet_settings[0].name)
+ self.assertEqual(999, settings.mtu)
+
+
+class SubnetConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the SubnetConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(SubnetConfigError):
+ SubnetConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(SubnetConfigError):
+ SubnetConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(SubnetConfigError):
+ SubnetConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(SubnetConfigError):
+ SubnetConfig(**{'name': 'foo'})
+
+ def test_name_cidr_only(self):
+ settings = SubnetConfig(name='foo', cidr='10.0.0.0/24')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('10.0.0.0/24', settings.cidr)
+ self.assertEqual(4, settings.ip_version)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.start)
+ self.assertIsNone(settings.end)
+ self.assertIsNone(settings.enable_dhcp)
+ self.assertEqual(0, len(settings.dns_nameservers))
+ self.assertIsNone(settings.host_routes)
+ self.assertIsNone(settings.destination)
+ self.assertIsNone(settings.nexthop)
+ self.assertIsNone(settings.ipv6_ra_mode)
+ self.assertIsNone(settings.ipv6_address_mode)
+
+ def test_config_with_name_cidr_only(self):
+ settings = SubnetConfig(**{'name': 'foo', 'cidr': '10.0.0.0/24'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('10.0.0.0/24', settings.cidr)
+ self.assertEqual(4, settings.ip_version)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.start)
+ self.assertIsNone(settings.end)
+ self.assertIsNone(settings.gateway_ip)
+ self.assertIsNone(settings.enable_dhcp)
+ self.assertEqual(0, len(settings.dns_nameservers))
+ self.assertIsNone(settings.host_routes)
+ self.assertIsNone(settings.destination)
+ self.assertIsNone(settings.nexthop)
+ self.assertIsNone(settings.ipv6_ra_mode)
+ self.assertIsNone(settings.ipv6_address_mode)
+
+ def test_all_string_enums(self):
+ host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'}
+ settings = SubnetConfig(
+ name='foo', cidr='10.0.0.0/24', ip_version=6,
+ project_name='bar-project', start='10.0.0.2', end='10.0.0.101',
+ gateway_ip='10.0.0.1', enable_dhcp=False,
+ dns_nameservers=['8.8.8.8'], host_routes=[host_routes],
+ destination='dest', nexthop='hop', ipv6_ra_mode='dhcpv6-stateful',
+ ipv6_address_mode='slaac')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('10.0.0.0/24', settings.cidr)
+ self.assertEqual(6, settings.ip_version)
+ self.assertEqual('bar-project', settings.project_name)
+ self.assertEqual('10.0.0.2', settings.start)
+ self.assertEqual('10.0.0.101', settings.end)
+ self.assertEqual('10.0.0.1', settings.gateway_ip)
+ self.assertEqual(False, settings.enable_dhcp)
+ self.assertEqual(1, len(settings.dns_nameservers))
+ self.assertEqual('8.8.8.8', settings.dns_nameservers[0])
+ self.assertEqual(1, len(settings.host_routes))
+ self.assertEqual(host_routes, settings.host_routes[0])
+ self.assertEqual('dest', settings.destination)
+ self.assertEqual('hop', settings.nexthop)
+ self.assertEqual(IPv6Mode.stateful, settings.ipv6_ra_mode)
+ self.assertEqual(IPv6Mode.slaac, settings.ipv6_address_mode)
+
+ def test_all_type_enums(self):
+ host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'}
+ settings = SubnetConfig(
+ name='foo', cidr='10.0.0.0/24', ip_version=6,
+ project_name='bar-project', start='10.0.0.2', end='10.0.0.101',
+ gateway_ip='10.0.0.1', enable_dhcp=False,
+ dns_nameservers=['8.8.8.8'], host_routes=[host_routes],
+ destination='dest', nexthop='hop', ipv6_ra_mode=IPv6Mode.stateful,
+ ipv6_address_mode=IPv6Mode.slaac)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('10.0.0.0/24', settings.cidr)
+ self.assertEqual(6, settings.ip_version)
+ self.assertEqual('bar-project', settings.project_name)
+ self.assertEqual('10.0.0.2', settings.start)
+ self.assertEqual('10.0.0.101', settings.end)
+ self.assertEqual('10.0.0.1', settings.gateway_ip)
+ self.assertEqual(False, settings.enable_dhcp)
+ self.assertEqual(1, len(settings.dns_nameservers))
+ self.assertEqual('8.8.8.8', settings.dns_nameservers[0])
+ self.assertEqual(1, len(settings.host_routes))
+ self.assertEqual(host_routes, settings.host_routes[0])
+ self.assertEqual('dest', settings.destination)
+ self.assertEqual('hop', settings.nexthop)
+ self.assertEqual(IPv6Mode.stateful, settings.ipv6_ra_mode)
+ self.assertEqual(IPv6Mode.slaac, settings.ipv6_address_mode)
+
+ def test_config_all(self):
+ host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'}
+ settings = SubnetConfig(
+ **{'name': 'foo', 'cidr': '10.0.0.0/24', 'ip_version': 6,
+ 'project_name': 'bar-project', 'start': '10.0.0.2',
+ 'end': '10.0.0.101',
+ 'gateway_ip': '10.0.0.1', 'enable_dhcp': False,
+ 'dns_nameservers': ['8.8.8.8'], 'host_routes': [host_routes],
+ 'destination': 'dest', 'nexthop': 'hop',
+ 'ipv6_ra_mode': 'dhcpv6-stateless',
+ 'ipv6_address_mode': 'slaac'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('10.0.0.0/24', settings.cidr)
+ self.assertEqual(6, settings.ip_version)
+ self.assertEqual('bar-project', settings.project_name)
+ self.assertEqual('10.0.0.2', settings.start)
+ self.assertEqual('10.0.0.101', settings.end)
+ self.assertEqual('10.0.0.1', settings.gateway_ip)
+ self.assertEqual(False, settings.enable_dhcp)
+ self.assertEqual(1, len(settings.dns_nameservers))
+ self.assertEqual('8.8.8.8', settings.dns_nameservers[0])
+ self.assertEqual(1, len(settings.host_routes))
+ self.assertEqual(host_routes, settings.host_routes[0])
+ self.assertEqual('dest', settings.destination)
+ self.assertEqual('hop', settings.nexthop)
+ self.assertEqual(IPv6Mode.stateless, settings.ipv6_ra_mode)
+ self.assertEqual(IPv6Mode.slaac, settings.ipv6_address_mode)
+
+
+class PortConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the PortConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(PortConfigError):
+ PortConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(PortConfigError):
+ PortConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(PortConfigError):
+ PortConfig(name='foo')
+
+ def test_config_name_only(self):
+ with self.assertRaises(PortConfigError):
+ PortConfig(**{'name': 'foo'})
+
+ def test_name_netname_only(self):
+ settings = PortConfig(name='foo', network_name='bar')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.network_name)
+ self.assertTrue(settings.admin_state_up)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.mac_address)
+ self.assertIsNone(settings.ip_addrs)
+ self.assertIsNone(settings.security_groups)
+ self.assertIsNone(settings.allowed_address_pairs)
+ self.assertIsNone(settings.opt_value)
+ self.assertIsNone(settings.opt_name)
+ self.assertIsNone(settings.device_owner)
+ self.assertIsNone(settings.device_id)
+
+ def test_config_with_name_netname_only(self):
+ settings = PortConfig(**{'name': 'foo', 'network_name': 'bar'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.network_name)
+ self.assertTrue(settings.admin_state_up)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.mac_address)
+ self.assertIsNone(settings.ip_addrs)
+ self.assertIsNone(settings.security_groups)
+ self.assertIsNone(settings.port_security_enabled)
+ self.assertIsNone(settings.allowed_address_pairs)
+ self.assertIsNone(settings.opt_value)
+ self.assertIsNone(settings.opt_name)
+ self.assertIsNone(settings.device_owner)
+ self.assertIsNone(settings.device_id)
+
+ def test_all(self):
+ ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}]
+ allowed_address_pairs = {'10.0.0.101', '1234.5678'}
+
+ settings = PortConfig(
+ name='foo', network_name='bar', admin_state_up=False,
+ project_name='foo-project', mac_address='1234', ip_addrs=ip_addrs,
+ security_groups=['foo_grp_id'], port_security_enabled=False,
+ allowed_address_pairs=allowed_address_pairs, opt_value='opt value',
+ opt_name='opt name', device_owner='owner',
+ device_id='device number')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.network_name)
+ self.assertFalse(settings.admin_state_up)
+ self.assertEqual('foo-project', settings.project_name)
+ self.assertEqual('1234', settings.mac_address)
+ self.assertEqual(ip_addrs, settings.ip_addrs)
+ self.assertEqual(1, len(settings.security_groups))
+ self.assertFalse(settings.port_security_enabled)
+ self.assertEqual('foo_grp_id', settings.security_groups[0])
+ self.assertFalse(settings.port_security_enabled)
+ self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs)
+ self.assertEqual('opt value', settings.opt_value)
+ self.assertEqual('opt name', settings.opt_name)
+ self.assertEqual('owner', settings.device_owner)
+ self.assertEqual('device number', settings.device_id)
+
+ def test_config_all(self):
+ ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}]
+ allowed_address_pairs = {'10.0.0.101', '1234.5678'}
+
+ settings = PortConfig(
+ **{'name': 'foo', 'network_name': 'bar', 'admin_state_up': False,
+ 'project_name': 'foo-project', 'mac_address': '1234',
+ 'ip_addrs': ip_addrs, 'security_groups': ['foo_grp_id'],
+ 'port_security_enabled': 'false',
+ 'allowed_address_pairs': allowed_address_pairs,
+ 'opt_value': 'opt value', 'opt_name': 'opt name',
+ 'device_owner': 'owner', 'device_id': 'device number'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.network_name)
+ self.assertFalse(settings.admin_state_up)
+ self.assertEqual('foo-project', settings.project_name)
+ self.assertEqual('1234', settings.mac_address)
+ self.assertEqual(ip_addrs, settings.ip_addrs)
+ self.assertEqual(1, len(settings.security_groups))
+ self.assertFalse(settings.port_security_enabled)
+ self.assertEqual('foo_grp_id', settings.security_groups[0])
+ self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs)
+ self.assertEqual('opt value', settings.opt_value)
+ self.assertEqual('opt name', settings.opt_name)
+ self.assertEqual('owner', settings.device_owner)
+ self.assertEqual('device number', settings.device_id)
diff --git a/snaps/config/tests/project_tests.py b/snaps/config/tests/project_tests.py
new file mode 100644
index 0000000..99de4a9
--- /dev/null
+++ b/snaps/config/tests/project_tests.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.project import ProjectConfig, ProjectConfigError
+
+
+class ProjectConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the ProjectConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(ProjectConfigError):
+ ProjectConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(ProjectConfigError):
+ ProjectConfig(**dict())
+
+ def test_name_only(self):
+ settings = ProjectConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('Default', settings.domain_name)
+ self.assertIsNone(settings.description)
+ self.assertTrue(settings.enabled)
+ self.assertEqual(list(), settings.users)
+ self.assertIsNone(settings.quotas)
+
+ def test_config_with_name_only(self):
+ settings = ProjectConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('Default', settings.domain_name)
+ self.assertIsNone(settings.description)
+ self.assertTrue(settings.enabled)
+ self.assertEqual(list(), settings.users)
+ self.assertIsNone(settings.quotas)
+
+ def test_all(self):
+ users = ['test1', 'test2']
+ quotas = {
+ 'cores': 4, 'instances': 5, 'injected_files': 6,
+ 'injected_file_content_bytes': 60000, 'ram': 70000, 'fixed_ips': 7,
+ 'key_pairs': 8}
+ settings = ProjectConfig(
+ name='foo', domain='bar', description='foobar', enabled=False,
+ users=users, quotas=quotas)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.domain_name)
+ self.assertEqual('foobar', settings.description)
+ self.assertFalse(settings.enabled)
+ self.assertEqual(users, settings.users)
+ self.assertIsNotNone(settings.quotas)
+ self.assertEquals(4, settings.quotas.get('cores'))
+ self.assertEquals(5, settings.quotas.get('instances'))
+ self.assertEquals(6, settings.quotas.get('injected_files'))
+ self.assertEquals(
+ 60000, settings.quotas.get('injected_file_content_bytes'))
+ self.assertEquals(70000, settings.quotas.get('ram'))
+ self.assertEquals(7, settings.quotas.get('fixed_ips'))
+ self.assertEquals(8, settings.quotas.get('key_pairs'))
+
+ def test_config_all(self):
+ users = ['test1', 'test2']
+ settings = ProjectConfig(
+ **{'name': 'foo', 'domain': 'bar', 'description': 'foobar',
+ 'enabled': False, 'users': users,
+ 'quotas': {
+ 'cores': 4, 'instances': 5, 'injected_files': 6,
+ 'injected_file_content_bytes': 60000, 'ram': 70000,
+ 'fixed_ips': 7, 'key_pairs': 8}})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.domain_name)
+ self.assertEqual('foobar', settings.description)
+ self.assertFalse(settings.enabled)
+ self.assertEqual(users, settings.users)
+ self.assertIsNotNone(settings.quotas)
+ self.assertEquals(4, settings.quotas.get('cores'))
+ self.assertEquals(5, settings.quotas.get('instances'))
+ self.assertEquals(6, settings.quotas.get('injected_files'))
+ self.assertEquals(
+ 60000, settings.quotas.get('injected_file_content_bytes'))
+ self.assertEquals(70000, settings.quotas.get('ram'))
+ self.assertEquals(7, settings.quotas.get('fixed_ips'))
+ self.assertEquals(8, settings.quotas.get('key_pairs'))
diff --git a/snaps/config/tests/qos_tests.py b/snaps/config/tests/qos_tests.py
new file mode 100644
index 0000000..7314c5b
--- /dev/null
+++ b/snaps/config/tests/qos_tests.py
@@ -0,0 +1,91 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.qos import QoSConfig, QoSConfigError, Consumer
+
+
+class QoSConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the QoSConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(QoSConfigError):
+ QoSConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(QoSConfigError):
+ QoSConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(QoSConfigError):
+ QoSConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(QoSConfigError):
+ QoSConfig(**{'name': 'foo'})
+
+ def test_invalid_consumer(self):
+ with self.assertRaises(QoSConfigError):
+ QoSConfig(name='foo', consumer='bar')
+
+ def test_config_with_invalid_consumer(self):
+ with self.assertRaises(QoSConfigError):
+ QoSConfig(**{'name': 'foo', 'consumer': 'bar'})
+
+ def test_name_consumer(self):
+ settings = QoSConfig(name='foo', consumer=Consumer.front_end)
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(Consumer.front_end, settings.consumer)
+ self.assertEqual(dict(), settings.specs)
+
+ def test_name_consumer_front_end_strings(self):
+ settings = QoSConfig(name='foo', consumer='front-end')
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(Consumer.front_end, settings.consumer)
+ self.assertEqual(dict(), settings.specs)
+
+ def test_name_consumer_back_end_strings(self):
+ settings = QoSConfig(name='foo', consumer='back-end')
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(Consumer.back_end, settings.consumer)
+ self.assertEqual(dict(), settings.specs)
+
+ def test_name_consumer_both_strings(self):
+ settings = QoSConfig(name='foo', consumer='both')
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(Consumer.both, settings.consumer)
+ self.assertEqual(dict(), settings.specs)
+
+ def test_all(self):
+ specs = {'spec1': 'val1', 'spec2': 'val2'}
+ settings = QoSConfig(name='foo', consumer=Consumer.both, specs=specs)
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(Consumer.both, settings.consumer)
+ self.assertEqual(specs, settings.specs)
+
+ def test_config_all(self):
+ settings = QoSConfig(
+ **{'name': 'foo', 'consumer': 'both', 'specs': {'spec1': 'val1'}})
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual(Consumer.both, settings.consumer)
+ self.assertEqual({'spec1': 'val1'}, settings.specs)
diff --git a/snaps/config/tests/router_tests.py b/snaps/config/tests/router_tests.py
new file mode 100644
index 0000000..1397f23
--- /dev/null
+++ b/snaps/config/tests/router_tests.py
@@ -0,0 +1,142 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.network import PortConfig
+from snaps.config.router import RouterConfig, RouterConfigError
+
+
+class RouterConfigUnitTests(unittest.TestCase):
+ """
+ Class for testing the RouterConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(RouterConfigError):
+ RouterConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(RouterConfigError):
+ RouterConfig(**dict())
+
+ def test_bad_internal_subnets_bad_key(self):
+ with self.assertRaises(RouterConfigError):
+ RouterConfig(name='foo', internal_subnets={'foo': 'bar'})
+
+ def test_bad_internal_subnets_no_project(self):
+ with self.assertRaises(RouterConfigError):
+ RouterConfig(name='foo', internal_subnets={
+ 'subnet': {'subnet_name': 'bar', 'network_name': 'foo'}})
+
+ def test_bad_internal_subnets_no_network(self):
+ with self.assertRaises(RouterConfigError):
+ RouterConfig(name='foo', internal_subnets={
+ 'subnet': {'subnet_name': 'bar', 'project_name': 'foo'}})
+
+ def test_bad_internal_subnets_no_subnet(self):
+ with self.assertRaises(RouterConfigError):
+ RouterConfig(name='foo', internal_subnets={
+ 'subnet': {'project_name': 'bar', 'network_name': 'foo'}})
+
+ def test_name_only(self):
+ settings = RouterConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.external_gateway)
+ self.assertTrue(settings.admin_state_up)
+ self.assertIsNone(settings.enable_snat)
+ self.assertIsNotNone(settings.internal_subnets)
+ self.assertTrue(isinstance(settings.internal_subnets, list))
+ self.assertEqual(0, len(settings.internal_subnets))
+ self.assertIsNotNone(settings.port_settings)
+ self.assertTrue(isinstance(settings.port_settings, list))
+ self.assertEqual(0, len(settings.port_settings))
+
+ def test_config_with_name_only(self):
+ settings = RouterConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.external_gateway)
+ self.assertTrue(settings.admin_state_up)
+ self.assertIsNone(settings.enable_snat)
+ self.assertIsNotNone(settings.internal_subnets)
+ self.assertTrue(isinstance(settings.internal_subnets, list))
+ self.assertEqual(0, len(settings.internal_subnets))
+ self.assertIsNotNone(settings.port_settings)
+ self.assertTrue(isinstance(settings.port_settings, list))
+ self.assertEqual(0, len(settings.port_settings))
+
+ def test_all_internal_subnets_str(self):
+ port_settings = PortConfig(name='foo', network_name='bar')
+ settings = RouterConfig(
+ name='foo', project_name='bar', external_gateway='foo_gateway',
+ admin_state_up=True, enable_snat=False,
+ internal_subnets=['10.0.0.1/24'], interfaces=[port_settings])
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.project_name)
+ self.assertEqual('foo_gateway', settings.external_gateway)
+ self.assertTrue(settings.admin_state_up)
+ self.assertFalse(settings.enable_snat)
+ self.assertIsNotNone(settings.internal_subnets)
+ self.assertTrue(isinstance(settings.internal_subnets, list))
+ self.assertEqual(1, len(settings.internal_subnets))
+ self.assertEqual(['10.0.0.1/24'], settings.internal_subnets)
+ self.assertEqual([port_settings], settings.port_settings)
+
+ def test_all_internal_subnets_dict(self):
+ port_settings = PortConfig(name='foo', network_name='bar')
+ int_subs = {'subnet': {
+ 'project_name': 'proj_a', 'network_name': 'net_name',
+ 'subnet_name': 'sub_name'}}
+ settings = RouterConfig(
+ name='foo', project_name='bar', external_gateway='foo_gateway',
+ admin_state_up=True, enable_snat=False,
+ internal_subnets=int_subs,
+ interfaces=[port_settings])
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.project_name)
+ self.assertEqual('foo_gateway', settings.external_gateway)
+ self.assertTrue(settings.admin_state_up)
+ self.assertFalse(settings.enable_snat)
+ self.assertIsNotNone(settings.internal_subnets)
+ self.assertTrue(isinstance(settings.internal_subnets, dict))
+ self.assertEqual(1, len(settings.internal_subnets))
+ self.assertEqual(int_subs, settings.internal_subnets)
+ self.assertEqual([port_settings], settings.port_settings)
+
+ def test_config_all_internal_subnets_str(self):
+ int_subs = {'subnet': {
+ 'project_name': 'proj_a', 'network_name': 'net_name',
+ 'subnet_name': 'sub_name'}}
+ settings = RouterConfig(
+ **{'name': 'foo', 'project_name': 'bar',
+ 'external_gateway': 'foo_gateway', 'admin_state_up': True,
+ 'enable_snat': False,
+ 'internal_subnets': int_subs,
+ 'interfaces':
+ [{'port': {'name': 'foo-port',
+ 'network_name': 'bar-net'}}]})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.project_name)
+ self.assertEqual('foo_gateway', settings.external_gateway)
+ self.assertTrue(settings.admin_state_up)
+ self.assertFalse(settings.enable_snat)
+ self.assertIsNotNone(settings.internal_subnets)
+ self.assertTrue(isinstance(settings.internal_subnets, dict))
+ self.assertEqual(1, len(settings.internal_subnets))
+ self.assertEqual(int_subs, settings.internal_subnets)
+ self.assertEqual([PortConfig(**{'name': 'foo-port',
+ 'network_name': 'bar-net'})],
+ settings.port_settings)
diff --git a/snaps/config/tests/security_group_tests.py b/snaps/config/tests/security_group_tests.py
new file mode 100644
index 0000000..8834836
--- /dev/null
+++ b/snaps/config/tests/security_group_tests.py
@@ -0,0 +1,187 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.security_group import (
+ Direction, SecurityGroupConfig, SecurityGroupRuleConfig,
+ SecurityGroupConfigError, Protocol, Ethertype,
+ SecurityGroupRuleConfigError)
+
+
+class SecurityGroupRuleConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the SecurityGroupRuleConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(SecurityGroupRuleConfigError):
+ SecurityGroupRuleConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(SecurityGroupRuleConfigError):
+ SecurityGroupRuleConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(SecurityGroupRuleConfigError):
+ SecurityGroupRuleConfig(sec_grp_name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(SecurityGroupRuleConfigError):
+ SecurityGroupRuleConfig(**{'sec_grp_name': 'foo'})
+
+ def test_name_and_direction(self):
+ settings = SecurityGroupRuleConfig(sec_grp_name='foo',
+ direction=Direction.ingress)
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress, settings.direction)
+
+ def test_config_name_and_direction(self):
+ settings = SecurityGroupRuleConfig(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress, settings.direction)
+
+ def test_proto_ah_str(self):
+ settings = SecurityGroupRuleConfig(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 'ah'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress, settings.direction)
+ self.assertEqual(Protocol.ah, settings.protocol)
+
+ def test_proto_ah_value(self):
+ settings = SecurityGroupRuleConfig(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 51})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress, settings.direction)
+ self.assertEqual(Protocol.ah, settings.protocol)
+
+ def test_proto_any(self):
+ settings = SecurityGroupRuleConfig(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 'any'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress, settings.direction)
+ self.assertEqual(Protocol.null, settings.protocol)
+
+ def test_proto_null(self):
+ settings = SecurityGroupRuleConfig(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 'null'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress, settings.direction)
+ self.assertEqual(Protocol.null, settings.protocol)
+
+ def test_all(self):
+ settings = SecurityGroupRuleConfig(
+ sec_grp_name='foo', description='fubar',
+ direction=Direction.egress, remote_group_id='rgi',
+ protocol=Protocol.icmp, ethertype=Ethertype.IPv6, port_range_min=1,
+ port_range_max=2,
+ remote_ip_prefix='prfx')
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual('fubar', settings.description)
+ self.assertEqual(Direction.egress, settings.direction)
+ self.assertEqual('rgi', settings.remote_group_id)
+ self.assertEqual(Protocol.icmp, settings.protocol)
+ self.assertEqual(Ethertype.IPv6, settings.ethertype)
+ self.assertEqual(1, settings.port_range_min)
+ self.assertEqual(2, settings.port_range_max)
+ self.assertEqual('prfx', settings.remote_ip_prefix)
+
+ def test_config_all(self):
+ settings = SecurityGroupRuleConfig(
+ **{'sec_grp_name': 'foo',
+ 'description': 'fubar',
+ 'direction': 'egress',
+ 'remote_group_id': 'rgi',
+ 'protocol': 'tcp',
+ 'ethertype': 'IPv6',
+ 'port_range_min': 1,
+ 'port_range_max': 2,
+ 'remote_ip_prefix': 'prfx'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual('fubar', settings.description)
+ self.assertEqual(Direction.egress, settings.direction)
+ self.assertEqual('rgi', settings.remote_group_id)
+ self.assertEqual(Protocol.tcp, settings.protocol)
+ self.assertEqual(Ethertype.IPv6, settings.ethertype)
+ self.assertEqual(1, settings.port_range_min)
+ self.assertEqual(2, settings.port_range_max)
+ self.assertEqual('prfx', settings.remote_ip_prefix)
+
+
+class SecurityGroupConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the SecurityGroupConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(SecurityGroupConfigError):
+ SecurityGroupConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(SecurityGroupConfigError):
+ SecurityGroupConfig(**dict())
+
+ def test_name_only(self):
+ settings = SecurityGroupConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+
+ def test_config_with_name_only(self):
+ settings = SecurityGroupConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+
+ def test_invalid_rule(self):
+ rule_setting = SecurityGroupRuleConfig(
+ sec_grp_name='bar', direction=Direction.ingress,
+ description='test_rule_1')
+ with self.assertRaises(SecurityGroupConfigError):
+ SecurityGroupConfig(name='foo', rule_settings=[rule_setting])
+
+ def test_all(self):
+ rule_settings = list()
+ rule_settings.append(SecurityGroupRuleConfig(
+ sec_grp_name='bar', direction=Direction.egress,
+ description='test_rule_1'))
+ rule_settings.append(SecurityGroupRuleConfig(
+ sec_grp_name='bar', direction=Direction.ingress,
+ description='test_rule_2'))
+ settings = SecurityGroupConfig(
+ name='bar', description='fubar', project_name='foo',
+ rule_settings=rule_settings)
+
+ self.assertEqual('bar', settings.name)
+ self.assertEqual('fubar', settings.description)
+ self.assertEqual('foo', settings.project_name)
+ self.assertEqual(rule_settings[0], settings.rule_settings[0])
+ self.assertEqual(rule_settings[1], settings.rule_settings[1])
+
+ def test_config_all(self):
+ settings = SecurityGroupConfig(
+ **{'name': 'bar',
+ 'description': 'fubar',
+ 'project_name': 'foo',
+ 'rules': [
+ {'sec_grp_name': 'bar', 'direction': 'ingress'}]})
+
+ self.assertEqual('bar', settings.name)
+ self.assertEqual('fubar', settings.description)
+ self.assertEqual('foo', settings.project_name)
+ self.assertEqual(1, len(settings.rule_settings))
+ self.assertEqual('bar', settings.rule_settings[0].sec_grp_name)
+ self.assertEqual(Direction.ingress,
+ settings.rule_settings[0].direction)
diff --git a/snaps/config/tests/stack_tests.py b/snaps/config/tests/stack_tests.py
new file mode 100644
index 0000000..773e9c2
--- /dev/null
+++ b/snaps/config/tests/stack_tests.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+import snaps
+from snaps.config.stack import StackConfigError, StackConfig
+
+
+class StackConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the StackConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(StackConfigError):
+ StackConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(StackConfigError):
+ StackConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(StackConfigError):
+ StackConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(StackConfigError):
+ StackConfig(**{'name': 'foo'})
+
+ def test_resource_not_list(self):
+ with self.assertRaises(StackConfigError):
+ StackConfig(**{'name': 'foo', 'resource_files': 'bar'})
+
+ def test_config_minimum_template(self):
+ settings = StackConfig(**{'name': 'stack', 'template': 'foo'})
+ self.assertEqual('stack', settings.name)
+ self.assertEqual('foo', settings.template)
+ self.assertIsNone(settings.template_path)
+ self.assertIsNone(settings.resource_files)
+ self.assertIsNone(settings.env_values)
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
+ settings.stack_create_timeout)
+
+ def test_config_minimum_template_path(self):
+ settings = StackConfig(**{'name': 'stack', 'template_path': 'foo'})
+ self.assertEqual('stack', settings.name)
+ self.assertIsNone(settings.template)
+ self.assertEqual('foo', settings.template_path)
+ self.assertIsNone(settings.resource_files)
+ self.assertIsNone(settings.env_values)
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
+ settings.stack_create_timeout)
+
+ def test_minimum_template(self):
+ settings = StackConfig(name='stack', template='foo')
+ self.assertEqual('stack', settings.name)
+ self.assertEqual('foo', settings.template)
+ self.assertIsNone(settings.template_path)
+ self.assertIsNone(settings.resource_files)
+ self.assertIsNone(settings.env_values)
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
+ settings.stack_create_timeout)
+
+ def test_minimum_template_path(self):
+ settings = StackConfig(name='stack', template_path='foo')
+ self.assertEqual('stack', settings.name)
+ self.assertEqual('foo', settings.template_path)
+ self.assertIsNone(settings.template)
+ self.assertIsNone(settings.resource_files)
+ self.assertIsNone(settings.env_values)
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
+ settings.stack_create_timeout)
+
+ def test_resource(self):
+ settings = StackConfig(
+ name='stack', template_path='foo', resource_files=['foo', 'bar'])
+ self.assertEqual('stack', settings.name)
+ self.assertEqual('foo', settings.template_path)
+ self.assertIsNone(settings.template)
+ self.assertEqual(['foo', 'bar'], settings.resource_files)
+ self.assertIsNone(settings.env_values)
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
+ settings.stack_create_timeout)
+
+ def test_all(self):
+ env_values = {'foo': 'bar'}
+ settings = StackConfig(
+ name='stack', template='bar', template_path='foo',
+ env_values=env_values, stack_create_timeout=999)
+ self.assertEqual('stack', settings.name)
+ self.assertEqual('bar', settings.template)
+ self.assertEqual('foo', settings.template_path)
+ self.assertEqual(env_values, settings.env_values)
+ self.assertEqual(999, settings.stack_create_timeout)
+
+ def test_config_all(self):
+ env_values = {'foo': 'bar'}
+ settings = StackConfig(
+ **{'name': 'stack', 'template': 'bar', 'template_path': 'foo',
+ 'env_values': env_values, 'stack_create_timeout': 999})
+ self.assertEqual('stack', settings.name)
+ self.assertEqual('bar', settings.template)
+ self.assertEqual('foo', settings.template_path)
+ self.assertEqual(env_values, settings.env_values)
+ self.assertEqual(999, settings.stack_create_timeout)
diff --git a/snaps/config/tests/user_tests.py b/snaps/config/tests/user_tests.py
new file mode 100644
index 0000000..d3d8feb
--- /dev/null
+++ b/snaps/config/tests/user_tests.py
@@ -0,0 +1,84 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.user import UserConfig
+
+
+class UserConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the UserConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(Exception):
+ UserConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(Exception):
+ UserConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(Exception):
+ UserConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(Exception):
+ UserConfig(**{'name': 'foo'})
+
+ def test_name_pass_enabled_str(self):
+ with self.assertRaises(Exception):
+ UserConfig(name='foo', password='bar', enabled='true')
+
+ def test_config_with_name_pass_enabled_str(self):
+ with self.assertRaises(Exception):
+ UserConfig(
+ **{'name': 'foo', 'password': 'bar', 'enabled': 'true'})
+
+ def test_name_pass_only(self):
+ settings = UserConfig(name='foo', password='bar')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.password)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.email)
+ self.assertTrue(settings.enabled)
+
+ def test_config_with_name_pass_only(self):
+ settings = UserConfig(**{'name': 'foo', 'password': 'bar'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.password)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.email)
+ self.assertTrue(settings.enabled)
+
+ def test_all(self):
+ settings = UserConfig(
+ name='foo', password='bar', project_name='proj-foo',
+ email='foo@bar.com', enabled=False)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.password)
+ self.assertEqual('proj-foo', settings.project_name)
+ self.assertEqual('foo@bar.com', settings.email)
+ self.assertFalse(settings.enabled)
+
+ def test_config_all(self):
+ settings = UserConfig(
+ **{'name': 'foo', 'password': 'bar', 'project_name': 'proj-foo',
+ 'email': 'foo@bar.com', 'enabled': False})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.password)
+ self.assertEqual('proj-foo', settings.project_name)
+ self.assertEqual('foo@bar.com', settings.email)
+ self.assertFalse(settings.enabled)
diff --git a/snaps/config/tests/vm_inst_tests.py b/snaps/config/tests/vm_inst_tests.py
new file mode 100644
index 0000000..d7fb287
--- /dev/null
+++ b/snaps/config/tests/vm_inst_tests.py
@@ -0,0 +1,246 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.network import PortConfig
+from snaps.config.vm_inst import (
+ FloatingIpConfig, VmInstanceConfig, FloatingIpConfigError,
+ VmInstanceConfigError)
+
+
+class VmInstanceConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the VmInstanceConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(VmInstanceConfigError):
+ VmInstanceConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(VmInstanceConfigError):
+ VmInstanceConfig(config=dict())
+
+ def test_name_only(self):
+ with self.assertRaises(VmInstanceConfigError):
+ VmInstanceConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(VmInstanceConfigError):
+ VmInstanceConfig(config={'name': 'foo'})
+
+ def test_name_flavor_only(self):
+ with self.assertRaises(VmInstanceConfigError):
+ VmInstanceConfig(name='foo', flavor='bar')
+
+ def test_config_with_name_flavor_only(self):
+ with self.assertRaises(VmInstanceConfigError):
+ VmInstanceConfig(config={'name': 'foo', 'flavor': 'bar'})
+
+ def test_name_flavor_port_only(self):
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
+ settings = VmInstanceConfig(name='foo', flavor='bar',
+ port_settings=[port_settings])
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.flavor)
+ self.assertEqual(1, len(settings.port_settings))
+ self.assertEqual('foo-port', settings.port_settings[0].name)
+ self.assertEqual('bar-net', settings.port_settings[0].network_name)
+ self.assertEqual(0, len(settings.security_group_names))
+ self.assertEqual(0, len(settings.floating_ip_settings))
+ self.assertIsNone(settings.sudo_user)
+ self.assertEqual(900, settings.vm_boot_timeout)
+ self.assertEqual(300, settings.vm_delete_timeout)
+ self.assertEqual(180, settings.ssh_connect_timeout)
+ self.assertEqual(300, settings.cloud_init_timeout)
+ self.assertIsNone(settings.availability_zone)
+ self.assertIsNone(settings.volume_names)
+
+ def test_config_with_name_flavor_port_only(self):
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
+ settings = VmInstanceConfig(
+ **{'name': 'foo', 'flavor': 'bar', 'ports': [port_settings]})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.flavor)
+ self.assertEqual(1, len(settings.port_settings))
+ self.assertEqual('foo-port', settings.port_settings[0].name)
+ self.assertEqual('bar-net', settings.port_settings[0].network_name)
+ self.assertEqual(0, len(settings.security_group_names))
+ self.assertEqual(0, len(settings.floating_ip_settings))
+ self.assertIsNone(settings.sudo_user)
+ self.assertEqual(900, settings.vm_boot_timeout)
+ self.assertEqual(300, settings.vm_delete_timeout)
+ self.assertEqual(180, settings.ssh_connect_timeout)
+ self.assertEqual(300, settings.cloud_init_timeout)
+ self.assertIsNone(settings.availability_zone)
+ self.assertIsNone(settings.volume_names)
+
+ def test_all(self):
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
+ fip_settings = FloatingIpConfig(name='foo-fip', port_name='bar-port',
+ router_name='foo-bar-router')
+
+ settings = VmInstanceConfig(
+ name='foo', flavor='bar', port_settings=[port_settings],
+ security_group_names=['sec_grp_1'],
+ floating_ip_settings=[fip_settings], sudo_user='joe',
+ vm_boot_timeout=999, vm_delete_timeout=333,
+ ssh_connect_timeout=111, cloud_init_timeout=998,
+ availability_zone='server name', volume_names=['vol1'])
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.flavor)
+ self.assertEqual(1, len(settings.port_settings))
+ self.assertEqual('foo-port', settings.port_settings[0].name)
+ self.assertEqual('bar-net', settings.port_settings[0].network_name)
+ self.assertEqual(1, len(settings.security_group_names))
+ self.assertEqual('sec_grp_1', settings.security_group_names[0])
+ self.assertEqual(1, len(settings.floating_ip_settings))
+ self.assertEqual('foo-fip', settings.floating_ip_settings[0].name)
+ self.assertEqual('bar-port',
+ settings.floating_ip_settings[0].port_name)
+ self.assertEqual('foo-bar-router',
+ settings.floating_ip_settings[0].router_name)
+ self.assertEqual('joe', settings.sudo_user)
+ self.assertEqual(999, settings.vm_boot_timeout)
+ self.assertEqual(333, settings.vm_delete_timeout)
+ self.assertEqual(111, settings.ssh_connect_timeout)
+ self.assertEqual(998, settings.cloud_init_timeout)
+ self.assertEqual('server name', settings.availability_zone)
+ self.assertEqual('vol1', settings.volume_names[0])
+
+ def test_config_all(self):
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
+ fip_settings = FloatingIpConfig(name='foo-fip', port_name='bar-port',
+ router_name='foo-bar-router')
+
+ settings = VmInstanceConfig(
+ **{'name': 'foo', 'flavor': 'bar', 'ports': [port_settings],
+ 'security_group_names': ['sec_grp_1'],
+ 'floating_ips': [fip_settings], 'sudo_user': 'joe',
+ 'vm_boot_timeout': 999, 'vm_delete_timeout': 333,
+ 'ssh_connect_timeout': 111, 'cloud_init_timeout': 998,
+ 'availability_zone': 'server name', 'volume_names': ['vol2']})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('bar', settings.flavor)
+ self.assertEqual(1, len(settings.port_settings))
+ self.assertEqual('foo-port', settings.port_settings[0].name)
+ self.assertEqual('bar-net', settings.port_settings[0].network_name)
+ self.assertEqual(1, len(settings.security_group_names))
+ self.assertEqual(1, len(settings.floating_ip_settings))
+ self.assertEqual('foo-fip', settings.floating_ip_settings[0].name)
+ self.assertEqual('bar-port',
+ settings.floating_ip_settings[0].port_name)
+ self.assertEqual('foo-bar-router',
+ settings.floating_ip_settings[0].router_name)
+ self.assertEqual('joe', settings.sudo_user)
+ self.assertEqual(999, settings.vm_boot_timeout)
+ self.assertEqual(333, settings.vm_delete_timeout)
+ self.assertEqual(111, settings.ssh_connect_timeout)
+ self.assertEqual(998, settings.cloud_init_timeout)
+ self.assertEqual('server name', settings.availability_zone)
+ self.assertEqual('vol2', settings.volume_names[0])
+
+
+class FloatingIpConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the FloatingIpConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(**dict())
+
+ def test_name_only(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(name='foo')
+
+ def test_config_with_name_only(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(**{'name': 'foo'})
+
+ def test_name_port_only(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(name='foo', port_name='bar')
+
+ def test_config_with_name_port_only(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(**{'name': 'foo', 'port_name': 'bar'})
+
+ def test_name_router_only(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(name='foo', router_name='bar')
+
+ def test_config_with_name_router_only(self):
+ with self.assertRaises(FloatingIpConfigError):
+ FloatingIpConfig(**{'name': 'foo', 'router_name': 'bar'})
+
+ def test_name_port_router_name_only(self):
+ settings = FloatingIpConfig(name='foo', port_name='foo-port',
+ router_name='bar-router')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('foo-port', settings.port_name)
+ self.assertIsNone(settings.port_id)
+ self.assertEqual('bar-router', settings.router_name)
+ self.assertIsNone(settings.subnet_name)
+ self.assertTrue(settings.provisioning)
+
+ def test_name_port_router_id_only(self):
+ settings = FloatingIpConfig(name='foo', port_id='foo-port',
+ router_name='bar-router')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('foo-port', settings.port_id)
+ self.assertIsNone(settings.port_name)
+ self.assertEqual('bar-router', settings.router_name)
+ self.assertIsNone(settings.subnet_name)
+ self.assertTrue(settings.provisioning)
+
+ def test_config_with_name_port_router_only(self):
+ settings = FloatingIpConfig(
+ **{'name': 'foo', 'port_name': 'foo-port',
+ 'router_name': 'bar-router'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('foo-port', settings.port_name)
+ self.assertIsNone(settings.port_id)
+ self.assertEqual('bar-router', settings.router_name)
+ self.assertIsNone(settings.subnet_name)
+ self.assertTrue(settings.provisioning)
+
+ def test_all(self):
+ settings = FloatingIpConfig(name='foo', port_name='foo-port',
+ router_name='bar-router',
+ subnet_name='bar-subnet',
+ provisioning=False)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('foo-port', settings.port_name)
+ self.assertIsNone(settings.port_id)
+ self.assertEqual('bar-router', settings.router_name)
+ self.assertEqual('bar-subnet', settings.subnet_name)
+ self.assertFalse(settings.provisioning)
+
+ def test_config_all(self):
+ settings = FloatingIpConfig(
+ **{'name': 'foo', 'port_name': 'foo-port',
+ 'router_name': 'bar-router', 'subnet_name': 'bar-subnet',
+ 'provisioning': False})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('foo-port', settings.port_name)
+ self.assertIsNone(settings.port_id)
+ self.assertEqual('bar-router', settings.router_name)
+ self.assertEqual('bar-subnet', settings.subnet_name)
+ self.assertFalse(settings.provisioning)
diff --git a/snaps/config/tests/volume_tests.py b/snaps/config/tests/volume_tests.py
new file mode 100644
index 0000000..21adffa
--- /dev/null
+++ b/snaps/config/tests/volume_tests.py
@@ -0,0 +1,99 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.volume import VolumeConfigError, VolumeConfig
+
+
+class VolumeConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the VolumeConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(VolumeConfigError):
+ VolumeConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(VolumeConfigError):
+ VolumeConfig(**dict())
+
+ def test_name_only(self):
+ settings = VolumeConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.description)
+ self.assertEquals(1, settings.size)
+ self.assertIsNone(settings.image_name)
+ self.assertIsNone(settings.type_name)
+ self.assertIsNone(settings.availability_zone)
+ self.assertFalse(settings.multi_attach)
+
+ def test_config_with_name_only(self):
+ settings = VolumeConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.project_name)
+ self.assertIsNone(settings.description)
+ self.assertEquals(1, settings.size)
+ self.assertIsNone(settings.image_name)
+ self.assertIsNone(settings.type_name)
+ self.assertIsNone(settings.availability_zone)
+ self.assertFalse(settings.multi_attach)
+
+ def test_all_strings(self):
+ settings = VolumeConfig(
+ name='foo', project_name='proj-foo', description='desc', size='2',
+ image_name='image', type_name='type', availability_zone='zone1',
+ multi_attach='true')
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('proj-foo', settings.project_name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual(2, settings.size)
+ self.assertEqual('image', settings.image_name)
+ self.assertEqual('type', settings.type_name)
+ self.assertEqual('zone1', settings.availability_zone)
+ self.assertTrue(settings.multi_attach)
+
+ def test_all_correct_type(self):
+ settings = VolumeConfig(
+ name='foo', project_name='proj-foo', description='desc', size=2,
+ image_name='image', type_name='bar', availability_zone='zone1',
+ multi_attach=True)
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('proj-foo', settings.project_name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual(2, settings.size)
+ self.assertEqual('image', settings.image_name)
+ self.assertEqual('bar', settings.type_name)
+ self.assertEqual('zone1', settings.availability_zone)
+ self.assertTrue(settings.multi_attach)
+
+ def test_config_all(self):
+ settings = VolumeConfig(
+ **{'name': 'foo', 'project_name': 'proj-foo',
+ 'description': 'desc', 'size': '2',
+ 'image_name': 'foo', 'type_name': 'bar',
+ 'availability_zone': 'zone1', 'multi_attach': 'true'})
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('proj-foo', settings.project_name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual(2, settings.size)
+ self.assertEqual('foo', settings.image_name)
+ self.assertEqual('bar', settings.type_name)
+ self.assertEqual('zone1', settings.availability_zone)
+ self.assertTrue(settings.multi_attach)
diff --git a/snaps/config/tests/volume_type_tests.py b/snaps/config/tests/volume_type_tests.py
new file mode 100644
index 0000000..7b4fb1b
--- /dev/null
+++ b/snaps/config/tests/volume_type_tests.py
@@ -0,0 +1,91 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.volume_type import (
+ VolumeTypeConfig, VolumeTypeEncryptionConfig, ControlLocation,
+ VolumeTypeConfigError)
+
+
+class VolumeTypeConfigUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the VolumeTypeConfig class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(VolumeTypeConfigError):
+ VolumeTypeConfig()
+
+ def test_empty_config(self):
+ with self.assertRaises(VolumeTypeConfigError):
+ VolumeTypeConfig(**dict())
+
+ def test_name_only(self):
+ settings = VolumeTypeConfig(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.description)
+ self.assertIsNone(settings.qos_spec_name)
+ self.assertIsNone(settings.encryption)
+ self.assertFalse(settings.public)
+
+ def test_config_with_name_only(self):
+ settings = VolumeTypeConfig(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.description)
+ self.assertIsNone(settings.qos_spec_name)
+ self.assertIsNone(settings.encryption)
+ self.assertFalse(settings.public)
+
+ def test_all(self):
+ encryption_settings = VolumeTypeEncryptionConfig(
+ name='foo', provider_class='bar',
+ control_location=ControlLocation.back_end)
+ settings = VolumeTypeConfig(
+ name='foo', description='desc', encryption=encryption_settings,
+ qos_spec_name='spec_name', public=True)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual('spec_name', settings.qos_spec_name)
+ self.assertEqual(encryption_settings, settings.encryption)
+ self.assertTrue(True, settings.public)
+
+ def test_all_string(self):
+ encryption_settings = {
+ 'name': 'foo', 'provider_class': 'bar',
+ 'control_location': 'back-end'}
+ settings = VolumeTypeConfig(
+ name='foo', description='desc', encryption=encryption_settings,
+ qos_spec_name='spec_name', public='true')
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual('spec_name', settings.qos_spec_name)
+ self.assertEqual(VolumeTypeEncryptionConfig(**encryption_settings),
+ settings.encryption)
+ self.assertTrue(settings.public)
+
+ def test_config_all(self):
+ encryption_settings = {
+ 'name': 'foo', 'provider_class': 'bar',
+ 'control_location': 'back-end'}
+ settings = VolumeTypeConfig(
+ **{'name': 'foo', 'description': 'desc',
+ 'encryption': encryption_settings,
+ 'qos_spec_name': 'spec_name', 'public': 'false'})
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual('spec_name', settings.qos_spec_name)
+ self.assertEqual(VolumeTypeEncryptionConfig(**encryption_settings),
+ settings.encryption)
+ self.assertFalse(settings.public)
diff --git a/snaps/config/user.py b/snaps/config/user.py
new file mode 100644
index 0000000..fcc8fac
--- /dev/null
+++ b/snaps/config/user.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+
+class UserConfig(object):
+ """
+ Class for holding user configurations
+ """
+ def __init__(self, **kwargs):
+
+ """
+ Constructor
+ :param name: the user's name (required)
+ :param password: the user's password (required)
+ :param project_name: the user's primary project name (optional)
+ :param domain_name: the user's domain name (default='Default'). For v3
+ APIs
+ :param email: the user's email address (optional)
+ :param enabled: denotes whether or not the user is enabled
+ (default True)
+ :param roles: dict where key is the role's name and value is the name
+ of the project to associate with the role (optional)
+ """
+
+ self.name = kwargs.get('name')
+ self.password = kwargs.get('password')
+ self.project_name = kwargs.get('project_name')
+ self.email = kwargs.get('email')
+ self.domain_name = kwargs.get('domain_name', 'Default')
+ self.enabled = kwargs.get('enabled', True)
+ self.roles = kwargs.get('roles', dict())
+
+ if not self.name or not self.password:
+ raise UserConfigException(
+ 'The attributes name and password are required for '
+ 'UserConfig')
+
+ if not isinstance(self.enabled, bool):
+ raise UserConfigException(
+ 'The attribute enabled must be of type boolean')
+
+
+class UserConfigException(Exception):
+ """
+ Raised when there is a problem with the values set in the UserConfig
+ class
+ """
diff --git a/snaps/config/vm_inst.py b/snaps/config/vm_inst.py
new file mode 100644
index 0000000..6a63e33
--- /dev/null
+++ b/snaps/config/vm_inst.py
@@ -0,0 +1,166 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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 snaps.config.network import PortConfig
+
+
+class VmInstanceConfig(object):
+ """
+ Class responsible for holding configuration setting for a VM Instance
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the name of the VM
+ :param flavor: the VM's flavor name
+ :param port_settings: the port configuration settings (required)
+ :param security_group_names: a set of names of the security groups to
+ add to the VM
+ :param floating_ip_settings: the floating IP configuration settings
+ :param sudo_user: the sudo user of the VM that will override the
+ instance_settings.image_user when trying to
+ connect to the VM
+ :param vm_boot_timeout: the amount of time a thread will wait
+ for an instance to boot
+ :param vm_delete_timeout: the amount of time a thread will wait
+ for an instance to be deleted
+ :param ssh_connect_timeout: the amount of time a thread will wait
+ to obtain an SSH connection to a VM
+ :param cloud_init_timeout: the amount of time a thread will wait for
+ cloud-init to complete
+ :param availability_zone: the name of the compute server on which to
+ deploy the VM (optional)
+ :param volume_names: a list of the names of the volume to attach
+ (optional)
+ :param userdata: the string contents of any optional cloud-init script
+ to execute after the VM has been activated.
+ This value may also contain a dict who's key value
+ must contain the key 'cloud-init_file' which denotes
+ the location of some file containing the cloud-init
+ script
+ """
+ self.name = kwargs.get('name')
+ self.flavor = kwargs.get('flavor')
+ self.sudo_user = kwargs.get('sudo_user')
+ self.userdata = kwargs.get('userdata')
+
+ self.port_settings = list()
+ port_settings = kwargs.get('ports')
+ if not port_settings:
+ port_settings = kwargs.get('port_settings')
+ if port_settings:
+ for port_setting in port_settings:
+ if isinstance(port_setting, dict):
+ self.port_settings.append(PortConfig(**port_setting))
+ elif isinstance(port_setting, PortConfig):
+ self.port_settings.append(port_setting)
+
+ if kwargs.get('security_group_names'):
+ if isinstance(kwargs['security_group_names'], list):
+ self.security_group_names = kwargs['security_group_names']
+ elif isinstance(kwargs['security_group_names'], set):
+ self.security_group_names = kwargs['security_group_names']
+ elif isinstance(kwargs['security_group_names'], str):
+ self.security_group_names = [kwargs['security_group_names']]
+ else:
+ raise VmInstanceConfigError(
+ 'Invalid data type for security_group_names attribute')
+ else:
+ self.security_group_names = set()
+
+ self.floating_ip_settings = list()
+ floating_ip_settings = kwargs.get('floating_ips')
+ if not floating_ip_settings:
+ floating_ip_settings = kwargs.get('floating_ip_settings')
+ if floating_ip_settings:
+ for floating_ip_config in floating_ip_settings:
+ if isinstance(floating_ip_config, FloatingIpConfig):
+ self.floating_ip_settings.append(floating_ip_config)
+ else:
+ self.floating_ip_settings.append(FloatingIpConfig(
+ **floating_ip_config['floating_ip']))
+
+ self.vm_boot_timeout = kwargs.get('vm_boot_timeout', 900)
+ self.vm_delete_timeout = kwargs.get('vm_delete_timeout', 300)
+ self.ssh_connect_timeout = kwargs.get('ssh_connect_timeout', 180)
+ self.cloud_init_timeout = kwargs.get('cloud_init_timeout', 300)
+ self.availability_zone = kwargs.get('availability_zone')
+ self.volume_names = kwargs.get('volume_names')
+
+ if self.volume_names and not isinstance(self.volume_names, list):
+ raise VmInstanceConfigError('volume_names must be a list')
+
+ if not self.name or not self.flavor:
+ raise VmInstanceConfigError(
+ 'Instance configuration requires the attributes: name, flavor')
+
+ if len(self.port_settings) == 0:
+ raise VmInstanceConfigError(
+ 'Instance configuration requires port settings (aka. NICS)')
+
+
+class FloatingIpConfig(object):
+ """
+ Class responsible for holding configuration settings for a floating IP
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the name of the floating IP
+ :param port_name: the name of the router to the external network
+ :param router_name: the name of the router to the external network
+ :param subnet_name: the name of the subnet on which to attach the
+ floating IP
+ :param provisioning: when true, this floating IP can be used for
+ provisioning
+
+ TODO - provisioning flag is a hack as I have only observed a single
+ Floating IPs that actually works on an instance. Multiple floating IPs
+ placed on different subnets from the same port are especially
+ troublesome as you cannot predict which one will actually connect.
+ For now, it is recommended not to setup multiple floating IPs on an
+ instance unless absolutely necessary.
+ """
+ self.name = kwargs.get('name')
+ self.port_name = kwargs.get('port_name')
+ self.port_id = kwargs.get('port_id')
+ self.router_name = kwargs.get('router_name')
+ self.subnet_name = kwargs.get('subnet_name')
+ if kwargs.get('provisioning') is not None:
+ self.provisioning = kwargs['provisioning']
+ else:
+ self.provisioning = True
+
+ # if not self.name or not self.port_name or not self.router_name:
+ if not self.name or not self.router_name:
+ raise FloatingIpConfigError(
+ 'The attributes name, port_name and router_name are required')
+
+ if not self.port_name and not self.port_id:
+ raise FloatingIpConfigError(
+ 'The attributes port_name or port_id are required')
+
+
+class VmInstanceConfigError(Exception):
+ """
+ Exception to be thrown when an VM instance settings are incorrect
+ """
+
+
+class FloatingIpConfigError(Exception):
+ """
+ Exception to be thrown when an VM instance settings are incorrect
+ """
diff --git a/snaps/config/volume.py b/snaps/config/volume.py
new file mode 100644
index 0000000..0b4b73e
--- /dev/null
+++ b/snaps/config/volume.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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 neutronclient.common.utils import str2bool
+
+
+class VolumeConfig(object):
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the volume's name (required)
+ :param project_name: the name of the project to associate (optional)
+ note: due to a bug in the Cinder API, this functionality will not
+ work. see https://bugs.launchpad.net/cinder/+bug/1641982
+ :param description: the volume's name (optional)
+ :param size: the volume's size in GB (default 1)
+ :param image_name: when a glance image is used for the image source
+ (optional)
+ :param type_name: the associated volume's type name (optional)
+ :param availability_zone: the name of the compute server on which to
+ deploy the volume (optional)
+ :param multi_attach: when true, volume can be attached to more than one
+ server (default False)
+ """
+
+ self.name = kwargs.get('name')
+ self.project_name = kwargs.get('project_name')
+ self.description = kwargs.get('description')
+ self.size = int(kwargs.get('size', 1))
+ self.image_name = kwargs.get('image_name')
+ self.type_name = kwargs.get('type_name')
+ self.availability_zone = kwargs.get('availability_zone')
+
+ if kwargs.get('multi_attach'):
+ self.multi_attach = str2bool(str(kwargs.get('multi_attach')))
+ else:
+ self.multi_attach = False
+
+ if not self.name:
+ raise VolumeConfigError("The attribute name is required")
+
+
+class VolumeConfigError(Exception):
+ """
+ Exception to be thrown when an volume settings are incorrect
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
diff --git a/snaps/config/volume_type.py b/snaps/config/volume_type.py
new file mode 100644
index 0000000..35ca1d4
--- /dev/null
+++ b/snaps/config/volume_type.py
@@ -0,0 +1,146 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import enum
+from neutronclient.common.utils import str2bool
+
+
+class VolumeTypeConfig(object):
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the volume's name (required)
+ :param description: the volume's name (optional)
+ :param encryption: VolumeTypeEncryptionConfig (optional)
+ :param qos_spec_name: name of the QoS Spec to associate (optional)
+ :param public: volume visibility where True denotes global
+ (default - False)
+
+ TODO - Implement project_access parameter that will associate this
+ VolumeType to a list of project names
+ """
+
+ self.name = kwargs.get('name')
+ self.description = kwargs.get('description')
+ self.qos_spec_name = kwargs.get('qos_spec_name')
+
+ if 'encryption' in kwargs:
+ if isinstance(kwargs['encryption'], dict):
+ self.encryption = VolumeTypeEncryptionConfig(
+ **kwargs['encryption'])
+ elif isinstance(kwargs['encryption'],
+ VolumeTypeEncryptionConfig):
+ self.encryption = kwargs['encryption']
+ else:
+ self.encryption = None
+
+ if 'public' in kwargs:
+ if isinstance(kwargs['public'], str):
+ self.public = str2bool(kwargs['public'])
+ else:
+ self.public = kwargs['public']
+ else:
+ self.public = False
+
+ if not self.name:
+ raise VolumeTypeConfigError("The attribute name is required")
+
+ def __eq__(self, other):
+ return (self.name == other.name
+ and self.description == other.description
+ and self.qos_spec_name == other.qos_spec_name
+ and self.encryption == other.encryption
+ and self.public == other.public)
+
+
+class ControlLocation(enum.Enum):
+ """
+ QoS Specification consumer types
+ """
+ front_end = 'front-end'
+ back_end = 'back-end'
+
+
+class VolumeTypeEncryptionConfig(object):
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the volume's name (required)
+ :param provider_class: the volume's provider class (e.g. LuksEncryptor)
+ :param control_location: the notional service where encryption is
+ performed (e.g., front-end=Nova). The default
+ value is 'front-end.'
+ :param cipher: the encryption algorithm/mode to use
+ (e.g., aes-xts-plain64). If the field is left empty,
+ the provider default will be used
+ :param key_size: the size of the encryption key, in bits
+ (e.g., 128, 256). If the field is left empty, the
+ provider default will be used
+ """
+
+ self.name = kwargs.get('name')
+ self.provider_class = kwargs.get('provider_class')
+ self.control_location = kwargs.get('control_location')
+ if kwargs.get('control_location'):
+ self.control_location = map_control_location(
+ kwargs['control_location'])
+ else:
+ self.control_location = None
+
+ self.cipher = kwargs.get('cipher')
+ self.key_size = kwargs.get('key_size')
+
+ if (not self.name or not self.provider_class
+ or not self.control_location):
+ raise VolumeTypeConfigError(
+ 'The attributes name, provider_class, and control_location '
+ 'are required')
+
+ def __eq__(self, other):
+ return (self.name == other.name
+ and self.provider_class == other.provider_class
+ and self.control_location == other.control_location
+ and self.cipher == other.cipher
+ and self.key_size == other.key_size)
+
+
+def map_control_location(control_location):
+ """
+ Takes a the protocol value maps it to the Consumer enum. When None return
+ None
+ :param control_location: the value to map to the Enum
+ :return: a ControlLocation enum object
+ :raise: Exception if control_location parameter is invalid
+ """
+ if not control_location:
+ return None
+ elif isinstance(control_location, ControlLocation):
+ return control_location
+ else:
+ proto_str = str(control_location)
+ if proto_str == 'front-end':
+ return ControlLocation.front_end
+ elif proto_str == 'back-end':
+ return ControlLocation.back_end
+ else:
+ raise VolumeTypeConfigError('Invalid Consumer - ' + proto_str)
+
+
+class VolumeTypeConfigError(Exception):
+ """
+ Exception to be thrown when an volume settings are incorrect
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
diff --git a/snaps/domain/cluster_template.py b/snaps/domain/cluster_template.py
new file mode 100644
index 0000000..83892b2
--- /dev/null
+++ b/snaps/domain/cluster_template.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+
+class ClusterTemplate(object):
+ """
+ Class for OpenStack cluster template domain object
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param id: the cluster template's UUID
+ :param name: the cluster template's name
+ :param image: name or ID of the base image in Glance used to boot the
+ cluster's servers. The image must have the attribute
+ 'os-distro' defined as appropriate for the cluster
+ driver
+ :param keypair: name or ID of the keypair to gain cluster machine
+ access
+ :param network_driver: The name of a network driver for providing the
+ networks for the containers. Note that this is
+ different and separate from the Neutron network
+ for the bay/cluster. The operation and
+ networking model are specific to the particular
+ driver
+ :param external_net: name or IDof the external Neutron network to
+ provide connectivity to the cluster
+ :param floating_ip_enabled: Whether enable or not using the floating IP
+ of cloud provider. Some cloud providers
+ used floating IP, some used public IP,
+ thus Magnum provide this option for
+ specifying the choice of using floating IP
+ :param docker_volume_size: The size in GB for the local storage on each
+ server for the Docker daemon to cache the
+ images and host the containers. Cinder
+ volumes provide the storage. The default is
+ 25 GB. For the devicemapper storage driver,
+ the minimum value is 3GB. For the overlay
+ storage driver, the minimum value is 1GB.
+ :param server_type: server type string
+ :param flavor: name or ID of the nova flavor for booting the node
+ servers
+ :param master_flavor: name or ID of the nova flavor of the master node
+ for this cluster
+ :param coe: ContainerOrchestrationEngine enum instance
+ :param fixed_net: name of a Neutron network to provide connectivity
+ to the internal network for the cluster
+ :param fixed_subnet: Fixed subnet that are using to allocate network
+ address for nodes in bay/cluster
+ :param registry_enabled: Docker images by default are pulled from the
+ public Docker registry, but in some cases,
+ users may want to use a private registry.
+ This option provides an alternative registry
+ based on the Registry V2: Magnum will create a
+ local registry in the bay/cluster backed by
+ swift to host the images
+ :param insecure_registry: The URL pointing to the user's own private
+ insecure docker registry to deploy and run
+ docker containers
+ :param docker_storage_driver: DockerStorageDriver enum instance to
+ manage storage for the images and
+ container's writable layer
+ :param dns_nameserver: The DNS nameserver for the servers and
+ containers in the bay/cluster to use.
+ This is configured in the private Neutron
+ network for the bay/cluster.
+ :param public: denotes whether or not the cluster type is public
+ :param tls_disabled: denotes whether or not TLS should be enabled
+ :param http_proxy: host:port for a proxy to use when direct HTTP
+ access from the servers to sites on the external
+ internet is blocked
+ :param https_proxy: host:port for a proxy to use when direct HTTPS
+ access from the servers to sites on the external
+ internet is blocked
+ :param no_proxy: comma separated list of IPs that should not be
+ redirected through the proxy
+ :param volume_driver: The name of a volume driver for managing the
+ persistent storage for the containers. The
+ functionality supported are specific to the
+ driver
+ :param master_lb_enabled: Since multiple masters may exist in a
+ bay/cluster, a Neutron load balancer is
+ created to provide the API endpoint for the
+ bay/cluster and to direct requests to the
+ masters. In some cases, such as when the
+ LBaaS service is not available, this option
+ can be set to false to create a bay/cluster
+ without the load balancer. In this case, one
+ of the masters will serve as the API endpoint
+ :param labels: Arbitrary labels in the form of a dict. The accepted
+ keys and valid values are defined in the bay/cluster
+ drivers. They are used as a way to pass additional
+ parameters that are specific to a bay/cluster driver.
+ """
+ self.id = kwargs.get('id')
+ self.name = kwargs.get('name')
+ self.image = kwargs.get('image')
+ self.keypair = kwargs.get('keypair')
+ self.network_driver = kwargs.get('network_driver')
+ self.external_net = kwargs.get('external_net')
+ self.floating_ip_enabled = kwargs.get('floating_ip_enabled')
+ self.docker_volume_size = int(kwargs.get('docker_volume_size', 3))
+ self.server_type = kwargs.get('server_type')
+ self.flavor = kwargs.get('flavor')
+ self.master_flavor = kwargs.get('master_flavor')
+ self.coe = kwargs.get('coe')
+ self.fixed_net = kwargs.get('fixed_net')
+ self.fixed_subnet = kwargs.get('fixed_subnet')
+ self.registry_enabled = kwargs.get('registry_enabled')
+ self.insecure_registry = kwargs.get('insecure_registry')
+ self.docker_storage_driver = kwargs.get('docker_storage_driver')
+ self.dns_nameserver = kwargs.get('dns_nameserver')
+ self.public = kwargs.get('public', False)
+ self.tls_disabled = kwargs.get('tls_disabled')
+ self.http_proxy = kwargs.get('http_proxy')
+ self.https_proxy = kwargs.get('https_proxy')
+ self.no_proxy = kwargs.get('no_proxy')
+ self.volume_driver = kwargs.get('volume_driver')
+ self.master_lb_enabled = kwargs.get('master_lb_enabled', True)
+ self.labels = kwargs.get('labels')
+
+ def __eq__(self, other):
+ labels_eq = False
+ if (self.labels and isinstance(self.labels, dict)
+ and len(self.labels) == 0):
+ if (other.labels and isinstance(other.labels, dict)
+ and len(other.labels) == 0):
+ labels_eq = True
+ elif not self.labels:
+ if (not other.labels or
+ (isinstance(other.labels, dict)
+ and len(other.labels) == 0)):
+ labels_eq = True
+ else:
+ labels_eq = self.labels == other.labels
+
+ return (self.name == other.name
+ and self.id == other.id
+ and self.image == other.image
+ and self.keypair == other.keypair
+ and self.network_driver == other.network_driver
+ and self.external_net == other.external_net
+ and self.floating_ip_enabled == other.floating_ip_enabled
+ and self.docker_volume_size == other.docker_volume_size
+ and self.server_type == other.server_type
+ and self.flavor == other.flavor
+ and self.master_flavor == other.master_flavor
+ and self.coe == other.coe
+ and self.fixed_net == other.fixed_net
+ and self.fixed_subnet == other.fixed_subnet
+ and self.registry_enabled == other.registry_enabled
+ and self.insecure_registry == other.insecure_registry
+ and self.docker_storage_driver == other.docker_storage_driver
+ and self.dns_nameserver == other.dns_nameserver
+ and self.public == other.public
+ and self.tls_disabled == other.tls_disabled
+ and self.http_proxy == other.http_proxy
+ and self.https_proxy == other.https_proxy
+ and self.no_proxy == other.no_proxy
+ and self.volume_driver == other.volume_driver
+ and self.master_lb_enabled == other.master_lb_enabled
+ and labels_eq)
diff --git a/snaps/domain/flavor.py b/snaps/domain/flavor.py
index 035ca64..bf84cf4 100644
--- a/snaps/domain/flavor.py
+++ b/snaps/domain/flavor.py
@@ -23,7 +23,7 @@ class Flavor:
"""
Constructor
:param name: the flavor's name
- :param flavor_id: the flavor's id
+ :param flavor_id or id: the flavor's id
:param ram: the flavor's RAM in MB
:param disk: the flavor's disk size in GB
:param vcpus: the flavor's number of virtual CPUs
@@ -33,11 +33,16 @@ class Flavor:
:param is_public: denotes if flavor can be used by other projects
"""
self.name = kwargs.get('name')
- self.id = kwargs.get('id')
+ self.id = kwargs.get('flavor_id', kwargs.get('id'))
self.ram = kwargs.get('ram')
self.disk = kwargs.get('disk')
self.vcpus = kwargs.get('vcpus')
self.ephemeral = kwargs.get('ephemeral')
- self.swap = kwargs.get('swap')
+
+ if kwargs.get('swap'):
+ self.swap = int(kwargs.get('swap'))
+ else:
+ self.swap = None
+
self.rxtx_factor = kwargs.get('rxtx_factor')
self.is_public = kwargs.get('is_public')
diff --git a/snaps/domain/network.py b/snaps/domain/network.py
index 9cc1dd1..3d5e4af 100644
--- a/snaps/domain/network.py
+++ b/snaps/domain/network.py
@@ -22,19 +22,33 @@ class Network:
def __init__(self, **kwargs):
"""
Constructor
+ :param name: the network's name
+ :param id: the network's ID
+ :param project_id: the associated project ID
+ :param admin_state_up: T/F - network is up when True
+ :param shared: T/F - network can be shared amongst other project's
+ :param external: T/F - network is deemed to be external
+ :param type: vlan, vxlan, flat, etc.
+ :param subnets: list of Subnet objects
"""
self.name = kwargs.get('name')
self.id = kwargs.get('id')
+ self.project_id = kwargs.get('project_id')
self.admin_state_up = kwargs.get('admin_state_up')
self.shared = kwargs.get('shared')
self.external = kwargs.get('router:external', kwargs.get('external'))
self.type = kwargs.get('provider:network_type', kwargs.get('type'))
+ self.subnets = kwargs.get('subnets', list())
+ self.mtu = kwargs.get('mtu')
def __eq__(self, other):
return (self.name == other.name and self.id == other.id and
+ self.project_id == other.project_id and
self.admin_state_up == other.admin_state_up and
self.shared == other.shared and
- self.external == other.external and self.type == other.type)
+ self.external == other.external and
+ self.subnets == other.subnets and
+ self.mtu == other.mtu)
class Subnet:
@@ -45,9 +59,25 @@ class Subnet:
def __init__(self, **kwargs):
"""
Constructor
+ :param name: the network's name
+ :param id: the subnet's ID
+ :param project_id: the associated project ID
+ :param network_id: the network's ID
+ :param cidr: the CIDR
+ :param ip_version: the IP version
+ :param gateway_ip: the IP of the gateway
+ :param enable_dhcp: T/F if DHCP is enabled
+ :param dns_nameservers: list of DNS server IPs
+ :param host_routes: routes as returned in a dict by Neutron
+ :param ipv6_ra_mode: IPv6 RA Mode
+ :param ipv6_address_mode: IPv6 Address Mode
+ :param start: start IP address pool
+ :param end: end IP address pool
"""
self.name = kwargs.get('name')
self.id = kwargs.get('id')
+ self.project_id = kwargs.get('project_id')
+ self.network_id = kwargs.get('network_id')
self.cidr = kwargs.get('cidr')
self.ip_version = kwargs.get('ip_version')
self.gateway_ip = kwargs.get('gateway_ip')
@@ -71,6 +101,8 @@ class Subnet:
def __eq__(self, other):
return (self.name == other.name and
self.id == other.id and
+ self.project_id == other.project_id and
+ self.network_id == other.network_id and
self.cidr == other.cidr and
self.ip_version == other.ip_version and
self.gateway_ip == other.gateway_ip and
@@ -134,20 +166,45 @@ class Router:
Constructor
:param name: the router's name
:param id: the router's id
+ :param status: the router's status
+ :param tenant_id: the router's project/tenant ID
+ :param admin_state_up: Router is up when True
+ :param external_gateway_info: dict() for populating external_network_id
+ and external_fixed_ips
+ external_network_id: ID of the external network to route
+ in dict under key 'external_fixed_ips'
+ external_fixed_ips: List IP addresses associated with the
+ external_network_id found in dict under
+ key 'network_id'
+ :param port_subnets: list of tuples where #1 is the Port domain object
+ and #2 is a list of associated Subnet domain
+ objects
"""
self.name = kwargs.get('name')
self.id = kwargs.get('id')
self.status = kwargs.get('status')
self.tenant_id = kwargs.get('tenant_id')
self.admin_state_up = kwargs.get('admin_state_up')
- self.external_gateway_info = kwargs.get('external_gateway_info')
+ self.port_subnets = kwargs.get('port_subnets')
+
+ if (kwargs.get('external_gateway_info') and
+ isinstance(kwargs.get('external_gateway_info'), dict)):
+ gateway_info = kwargs.get('external_gateway_info')
+
+ self.external_network_id = gateway_info.get('network_id')
+ self.external_fixed_ips = gateway_info.get('external_fixed_ips')
+ else:
+ self.external_fixed_ips = kwargs.get('external_fixed_ips', None)
+ self.external_network_id = kwargs.get('external_network_id', None)
def __eq__(self, other):
return (self.name == other.name and self.id == other.id and
self.status == other.status and
self.tenant_id == other.tenant_id and
self.admin_state_up == other.admin_state_up and
- self.external_gateway_info == other.external_gateway_info)
+ self.external_network_id == other.external_network_id and
+ self.external_fixed_ips == other.external_fixed_ips and
+ self.port_subnets == other.port_subnets)
class InterfaceRouter:
@@ -178,15 +235,29 @@ class SecurityGroup:
Constructor
:param name: the security group's name
:param id: the security group's id
+ :param description: the security group's description
+ :param project_id: the security group's project_id
+ :param rules: list of SecurityGroupRule objects associated to this
"""
self.name = kwargs.get('name')
self.id = kwargs.get('id')
self.description = kwargs.get('description')
self.project_id = kwargs.get('project_id', kwargs.get('tenant_id'))
+ self.rules = list()
+ if kwargs.get('rules') and isinstance(kwargs.get('rules'), list):
+ for rule in kwargs.get('rules'):
+ if isinstance(rule, SecurityGroupRule):
+ self.rules.append(rule)
+ else:
+ self.rules.append(SecurityGroupRule(**rule))
+
def __eq__(self, other):
- return (self.name == other.name and self.id == other.id and
- self.project_id == other.project_id)
+ return (self.name == other.name and
+ self.id == other.id and
+ self.description == other.description and
+ self.project_id == other.project_id and
+ self.rules == other.rules)
class SecurityGroupRule:
@@ -198,7 +269,7 @@ class SecurityGroupRule:
"""
Constructor
:param id: the security group rule's id
- :param sec_grp_id: the ID of the associated security group
+ :param security_group_id: the ID of the associated security group
:param description: the security group rule's description
:param direction: the security group rule's direction
:param ethertype: the security group rule's ethertype
diff --git a/snaps/domain/stack.py b/snaps/domain/stack.py
index 543c78b..2c593d3 100644
--- a/snaps/domain/stack.py
+++ b/snaps/domain/stack.py
@@ -19,14 +19,22 @@ class Stack:
SNAPS domain object for Heat Stacks. Should contain attributes that
are shared amongst cloud providers
"""
- def __init__(self, name, stack_id):
+ def __init__(self, name, stack_id, stack_project_id,
+ status, status_reason):
"""
Constructor
:param name: the stack's name
:param stack_id: the stack's stack_id
+ :param stack_project_id: the project ID that was spawned from this
+ deployment
+ :param status: the stack's last known status code
+ :param status_reason: the stack's last known explanation of the status
"""
self.name = name
self.id = stack_id
+ self.stack_project_id = stack_project_id
+ self.status = status
+ self.status_reason = status_reason
def __eq__(self, other):
return (self.name == other.name and
@@ -37,14 +45,21 @@ class Resource:
"""
SNAPS domain object for a resource created by a heat template
"""
- def __init__(self, resource_type, resource_id):
+ def __init__(self, name, resource_type, resource_id, status,
+ status_reason):
"""
Constructor
- :param resource_type: the type
+ :param name: the resource's name
+ :param resource_type: the resource's type
:param resource_id: the ID attached to the resource of the given type
+ :param status: the resource's status code
+ :param status_reason: the resource's status code reason
"""
+ self.name = name
self.type = resource_type
self.id = resource_id
+ self.status = status
+ self.status_reason = status_reason
class Output:
diff --git a/snaps/domain/test/cluster_template_tests.py b/snaps/domain/test/cluster_template_tests.py
new file mode 100644
index 0000000..76e5663
--- /dev/null
+++ b/snaps/domain/test/cluster_template_tests.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import unittest
+
+from snaps.config.cluster_template import (
+ ContainerOrchestrationEngine, ServerType, DockerStorageDriver)
+from snaps.domain.cluster_template import ClusterTemplate
+
+
+class ClusterTemplateUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the ClusterTypeConfig class
+ """
+ def test_all_named(self):
+ labels = {'foo': 'bar'}
+ config = ClusterTemplate(
+ id='tmplt-id', name='foo', image='bar', keypair='keys',
+ network_driver='driver', external_net='external',
+ docker_volume_size=99, server_type=ServerType.baremetal.value,
+ flavor='testFlavor', master_flavor='masterFlavor',
+ coe=ContainerOrchestrationEngine.kubernetes.value,
+ fixed_net='fixedNet', fixed_subnet='fixedSubnet',
+ registry_enabled=False,
+ docker_storage_driver=DockerStorageDriver.overlay.value,
+ dns_nameserver='8.8.4.4', public=True, tls=False,
+ http_proxy='http://foo:8080', https_proxy='https://foo:443',
+ no_proxy='foo,bar', volume_driver='volDriver',
+ master_lb_enabled=False, labels=labels)
+ self.assertIsNotNone(config)
+ self.assertEqual('tmplt-id', config.id)
+ self.assertEqual('foo', config.name)
+ self.assertEqual('bar', config.image)
+ self.assertEqual('keys', config.keypair)
+ self.assertEqual('driver', config.network_driver)
+ self.assertEqual('external', config.external_net)
+ self.assertEqual(99, config.docker_volume_size)
+ self.assertEqual(ServerType.baremetal.value, config.server_type)
+ self.assertEqual('testFlavor', config.flavor)
+ self.assertEqual('masterFlavor', config.master_flavor)
+ self.assertEqual(ContainerOrchestrationEngine.kubernetes.value,
+ config.coe)
+ self.assertEqual('fixedNet', config.fixed_net)
+ self.assertEqual('fixedSubnet', config.fixed_subnet)
+ self.assertFalse(config.registry_enabled)
+ self.assertEqual(DockerStorageDriver.overlay.value,
+ config.docker_storage_driver)
+ self.assertEqual('8.8.4.4', config.dns_nameserver)
+ self.assertTrue(config.public)
+ self.assertFalse(config.tls_disabled)
+ self.assertEqual('http://foo:8080', config.http_proxy)
+ self.assertEqual('https://foo:443', config.https_proxy)
+ self.assertEqual('foo,bar', config.no_proxy)
+ self.assertEqual('volDriver', config.volume_driver)
+ self.assertFalse(config.master_lb_enabled)
+ self.assertEqual(labels, config.labels)
+
+ def test_all_config(self):
+ labels = {'foo': 'bar'}
+ config = ClusterTemplate(**{
+ 'id': 'tmplt-id', 'name': 'foo', 'image': 'bar', 'keypair': 'keys',
+ 'network_driver': 'driver', 'external_net': 'external',
+ 'docker_volume_size': '99', 'server_type': 'baremetal',
+ 'flavor': 'testFlavor', 'master_flavor': 'masterFlavor',
+ 'coe': 'kubernetes', 'fixed_net': 'fixedNet',
+ 'fixed_subnet': 'fixedSubnet', 'registry_enabled': False,
+ 'docker_storage_driver': 'overlay', 'dns_nameserver': '8.8.4.4',
+ 'public': 'true', 'tls': 'false', 'http_proxy': 'http://foo:8080',
+ 'https_proxy': 'https://foo:443', 'no_proxy': 'foo,bar',
+ 'volume_driver': 'volDriver', 'master_lb_enabled': False,
+ 'labels': labels})
+ self.assertIsNotNone(config)
+ self.assertEqual('tmplt-id', config.id)
+ self.assertEqual('foo', config.name)
+ self.assertEqual('bar', config.image)
+ self.assertEqual('keys', config.keypair)
+ self.assertEqual('driver', config.network_driver)
+ self.assertEqual('external', config.external_net)
+ self.assertEqual(99, config.docker_volume_size)
+ self.assertEqual(ServerType.baremetal.value, config.server_type)
+ self.assertEqual('testFlavor', config.flavor)
+ self.assertEqual('masterFlavor', config.master_flavor)
+ self.assertEqual(ContainerOrchestrationEngine.kubernetes.value,
+ config.coe)
+ self.assertEqual('fixedNet', config.fixed_net)
+ self.assertEqual('fixedSubnet', config.fixed_subnet)
+ self.assertFalse(config.registry_enabled)
+ self.assertEqual(DockerStorageDriver.overlay.value,
+ config.docker_storage_driver)
+ self.assertEqual('8.8.4.4', config.dns_nameserver)
+ self.assertTrue(config.public)
+ self.assertFalse(config.tls_disabled)
+ self.assertEqual('http://foo:8080', config.http_proxy)
+ self.assertEqual('https://foo:443', config.https_proxy)
+ self.assertEqual('foo,bar', config.no_proxy)
+ self.assertEqual('volDriver', config.volume_driver)
+ self.assertFalse(config.master_lb_enabled)
+ self.assertEqual(labels, config.labels)
diff --git a/snaps/domain/test/network_tests.py b/snaps/domain/test/network_tests.py
index 24a60c9..2c4c841 100644
--- a/snaps/domain/test/network_tests.py
+++ b/snaps/domain/test/network_tests.py
@@ -25,39 +25,57 @@ class NetworkObjectTests(unittest.TestCase):
"""
def test_construction_kwargs_1(self):
+ subnet = Subnet(
+ **{'name': 'foo', 'id': 'bar', 'project_id': 'proj1',
+ 'network_id': 'foo-bar'})
network = Network(
- **{'name': 'foo', 'id': 'bar', 'provider:network_type': 'flat',
- 'admin_state_up': False, 'shared': True,
- 'router:external': False})
+ **{'name': 'foo', 'id': 'bar', 'project_id': 'proj1',
+ 'provider:network_type': 'flat', 'admin_state_up': False,
+ 'shared': True, 'router:external': False, 'subnets': [subnet],
+ 'mtu': 999})
self.assertEqual('foo', network.name)
self.assertEqual('bar', network.id)
+ self.assertEqual('proj1', network.project_id)
self.assertEqual('flat', network.type)
self.assertFalse(network.admin_state_up)
self.assertFalse(network.external)
self.assertTrue(network.shared)
+ self.assertEqual([subnet], network.subnets)
+ self.assertEqual(999, network.mtu)
def test_construction_kwargs_2(self):
+ subnet = Subnet(
+ **{'name': 'foo', 'id': 'bar', 'project_id': 'proj1',
+ 'network_id': 'foo-bar'})
network = Network(
- **{'name': 'foo', 'id': 'bar', 'type': 'flat',
- 'admin_state_up': False, 'shared': True,
- 'external': False})
+ **{'name': 'foo', 'id': 'bar', 'project_id': 'proj1',
+ 'type': 'flat', 'admin_state_up': False, 'shared': True,
+ 'external': False, 'subnets': [subnet]})
self.assertEqual('foo', network.name)
self.assertEqual('bar', network.id)
+ self.assertEqual('proj1', network.project_id)
self.assertEqual('flat', network.type)
self.assertFalse(network.admin_state_up)
self.assertFalse(network.external)
self.assertTrue(network.shared)
+ self.assertEqual([subnet], network.subnets)
def test_construction_named(self):
+ subnet = Subnet(
+ **{'name': 'foo', 'id': 'bar', 'project_id': 'proj1',
+ 'network_id': 'foo-bar'})
network = Network(
- name='foo', id='bar', type='flat', admin_state_up=False,
- shared=True, external=False)
+ name='foo', id='bar', project_id='proj1', type='flat',
+ admin_state_up=False, shared=True, external=False,
+ subnets=[subnet])
self.assertEqual('foo', network.name)
self.assertEqual('bar', network.id)
+ self.assertEqual('proj1', network.project_id)
self.assertEqual('flat', network.type)
self.assertFalse(network.admin_state_up)
self.assertFalse(network.external)
self.assertTrue(network.shared)
+ self.assertEqual([subnet], network.subnets)
class SubnetObjectTests(unittest.TestCase):
@@ -67,12 +85,14 @@ class SubnetObjectTests(unittest.TestCase):
def test_construction_kwargs(self):
subnet = Subnet(
- **{'name': 'foo', 'id': 'bar', 'cidr': '10.0.0.0/24',
- 'ip_version': 4, 'gateway_ip': '10.0.0.1', 'enable_dhcp': True,
+ **{'name': 'foo', 'id': 'bar', 'project_id': 'proj1',
+ 'cidr': '10.0.0.0/24', 'ip_version': 4,
+ 'gateway_ip': '10.0.0.1', 'enable_dhcp': True,
'dns_nameservers': ['8.8.8.8'], 'host_routes': list(),
'ipv6_ra_mode': 'hello', 'ipv6_address_mode': 'world'})
self.assertEqual('foo', subnet.name)
self.assertEqual('bar', subnet.id)
+ self.assertEqual('proj1', subnet.project_id)
self.assertEqual('10.0.0.0/24', subnet.cidr)
self.assertEqual(4, subnet.ip_version)
self.assertEqual('10.0.0.1', subnet.gateway_ip)
@@ -85,12 +105,13 @@ class SubnetObjectTests(unittest.TestCase):
def test_construction_named(self):
subnet = Subnet(
- name='foo', id='bar', cidr='10.0.0.0/24',
+ name='foo', id='bar', project_id='proj1', cidr='10.0.0.0/24',
ip_version=4, gateway_ip='10.0.0.1', enable_dhcp=True,
dns_nameservers=['8.8.8.8'], host_routes=list(),
ipv6_ra_mode='hello', ipv6_address_mode='world')
self.assertEqual('foo', subnet.name)
self.assertEqual('bar', subnet.id)
+ self.assertEqual('proj1', subnet.project_id)
self.assertEqual('10.0.0.0/24', subnet.cidr)
self.assertEqual(4, subnet.ip_version)
self.assertEqual('10.0.0.1', subnet.gateway_ip)
@@ -213,7 +234,8 @@ class RouterDomainObjectTests(unittest.TestCase):
self.assertEqual('hello', router.status)
self.assertEqual('1234', router.tenant_id)
self.assertEqual('yes', router.admin_state_up)
- self.assertEqual('no', router.external_gateway_info)
+ self.assertIsNone(router.external_fixed_ips)
+ self.assertIsNone(router.external_network_id)
def test_construction_named(self):
router = Router(
@@ -224,7 +246,35 @@ class RouterDomainObjectTests(unittest.TestCase):
self.assertEqual('hello', router.status)
self.assertEqual('1234', router.tenant_id)
self.assertEqual('yes', router.admin_state_up)
- self.assertEqual('no', router.external_gateway_info)
+ self.assertIsNone(router.external_fixed_ips)
+ self.assertIsNone(router.external_network_id)
+
+ def test_ext_gateway_named(self):
+ router = Router(
+ external_fixed_ips=['456', '789'], external_network_id='123',
+ admin_state_up='yes', tenant_id='1234', status='hello', id='id',
+ name='name')
+ self.assertEqual('name', router.name)
+ self.assertEqual('id', router.id)
+ self.assertEqual('hello', router.status)
+ self.assertEqual('1234', router.tenant_id)
+ self.assertEqual('yes', router.admin_state_up)
+ self.assertEqual(['456', '789'], router.external_fixed_ips)
+ self.assertEqual('123', router.external_network_id)
+
+ def test_ext_net_ips_named(self):
+ ext_gateway = {'network_id': '123',
+ 'external_fixed_ips': ['456', '789']}
+ router = Router(
+ external_gateway_info=ext_gateway, admin_state_up='yes',
+ tenant_id='1234', status='hello', id='id', name='name')
+ self.assertEqual('name', router.name)
+ self.assertEqual('id', router.id)
+ self.assertEqual('hello', router.status)
+ self.assertEqual('1234', router.tenant_id)
+ self.assertEqual('yes', router.admin_state_up)
+ self.assertEqual(['456', '789'], router.external_fixed_ips)
+ self.assertEqual('123', router.external_network_id)
class InterfaceRouterDomainObjectTests(unittest.TestCase):
@@ -254,12 +304,33 @@ class SecurityGroupDomainObjectTests(unittest.TestCase):
def test_construction_proj_id_kwargs(self):
sec_grp = SecurityGroup(
**{'name': 'name', 'id': 'id', 'project_id': 'foo',
- 'description': 'test desc'})
+ 'description': 'test desc',
+ 'rules': [
+ {'id': 'id', 'security_group_id': 'grp_id',
+ 'description': 'desc', 'direction': 'dir',
+ 'ethertype': 'eType', 'port_range_min': '10.0.0.100',
+ 'port_range_max': '10.0.0.200', 'protocol': 'proto',
+ 'remote_group_id': 'group_id',
+ 'remote_ip_prefix': 'ip_prefix'}
+ ]})
self.assertEqual('name', sec_grp.name)
self.assertEqual('id', sec_grp.id)
self.assertEqual('test desc', sec_grp.description)
self.assertEqual('foo', sec_grp.project_id)
+ self.assertEqual(1, len(sec_grp.rules))
+ rule = sec_grp.rules[0]
+ self.assertEqual('id', rule.id)
+ self.assertEqual('grp_id', rule.security_group_id)
+ self.assertEqual('desc', rule.description)
+ self.assertEqual('dir', rule.direction)
+ self.assertEqual('eType', rule.ethertype)
+ self.assertEqual('10.0.0.100', rule.port_range_min)
+ self.assertEqual('10.0.0.200', rule.port_range_max)
+ self.assertEqual('proto', rule.protocol)
+ self.assertEqual('group_id', rule.remote_group_id)
+ self.assertEqual('ip_prefix', rule.remote_ip_prefix)
+
def test_construction_tenant_id_kwargs(self):
sec_grp = SecurityGroup(
**{'name': 'name', 'id': 'id',
@@ -268,15 +339,37 @@ class SecurityGroupDomainObjectTests(unittest.TestCase):
self.assertEqual('id', sec_grp.id)
self.assertEqual('foo', sec_grp.project_id)
self.assertIsNone(sec_grp.description)
+ self.assertEqual(0, len(sec_grp.rules))
def test_construction_named(self):
+ rules = [SecurityGroupRule(
+ **{'id': 'id', 'security_group_id': 'grp_id',
+ 'description': 'desc', 'direction': 'dir',
+ 'ethertype': 'eType', 'port_range_min': '10.0.0.100',
+ 'port_range_max': '10.0.0.200', 'protocol': 'proto',
+ 'remote_group_id': 'group_id',
+ 'remote_ip_prefix': 'ip_prefix'}
+ )]
sec_grp = SecurityGroup(description='test desc', tenant_id='foo',
- id='id', name='name')
+ id='id', name='name', rules=rules)
self.assertEqual('name', sec_grp.name)
self.assertEqual('id', sec_grp.id)
self.assertEqual('test desc', sec_grp.description)
self.assertEqual('foo', sec_grp.project_id)
+ self.assertEqual(1, len(sec_grp.rules))
+ rule = sec_grp.rules[0]
+ self.assertEqual('id', rule.id)
+ self.assertEqual('grp_id', rule.security_group_id)
+ self.assertEqual('desc', rule.description)
+ self.assertEqual('dir', rule.direction)
+ self.assertEqual('eType', rule.ethertype)
+ self.assertEqual('10.0.0.100', rule.port_range_min)
+ self.assertEqual('10.0.0.200', rule.port_range_max)
+ self.assertEqual('proto', rule.protocol)
+ self.assertEqual('group_id', rule.remote_group_id)
+ self.assertEqual('ip_prefix', rule.remote_ip_prefix)
+
class SecurityGroupRuleDomainObjectTests(unittest.TestCase):
"""
diff --git a/snaps/domain/test/stack_tests.py b/snaps/domain/test/stack_tests.py
index f816ef8..2ad690a 100644
--- a/snaps/domain/test/stack_tests.py
+++ b/snaps/domain/test/stack_tests.py
@@ -23,14 +23,23 @@ class StackDomainObjectTests(unittest.TestCase):
"""
def test_construction_positional(self):
- stack = Stack('name', 'id')
+ stack = Stack(
+ 'name', 'id', 'stack_proj_id', 'fine', 'good')
self.assertEqual('name', stack.name)
self.assertEqual('id', stack.id)
+ self.assertEqual('stack_proj_id', stack.stack_project_id)
+ self.assertEqual('fine', stack.status)
+ self.assertEqual('good', stack.status_reason)
def test_construction_named(self):
- stack = Stack(stack_id='id', name='name')
+ stack = Stack(
+ stack_id='id', name='name', stack_project_id='stack_proj_id',
+ status='fine', status_reason='good')
self.assertEqual('name', stack.name)
self.assertEqual('id', stack.id)
+ self.assertEqual('stack_proj_id', stack.stack_project_id)
+ self.assertEqual('fine', stack.status)
+ self.assertEqual('good', stack.status_reason)
class ResourceDomainObjectTests(unittest.TestCase):
@@ -39,14 +48,22 @@ class ResourceDomainObjectTests(unittest.TestCase):
"""
def test_construction_positional(self):
- resource = Resource('foo', 'bar')
+ resource = Resource('res_name', 'foo', 'bar', 'status', 'reason')
+ self.assertEqual('res_name', resource.name)
self.assertEqual('foo', resource.type)
self.assertEqual('bar', resource.id)
+ self.assertEqual('status', resource.status)
+ self.assertEqual('reason', resource.status_reason)
def test_construction_named(self):
- resource = Resource(resource_id='bar', resource_type='foo')
+ resource = Resource(
+ status_reason=None, status=None, resource_id='bar',
+ resource_type='foo', name='res_name')
+ self.assertEqual('res_name', resource.name)
self.assertEqual('foo', resource.type)
self.assertEqual('bar', resource.id)
+ self.assertIsNone(resource.status)
+ self.assertIsNone(resource.status_reason)
class OutputDomainObjectTests(unittest.TestCase):
diff --git a/snaps/domain/test/vm_inst_tests.py b/snaps/domain/test/vm_inst_tests.py
index d293373..c90837d 100644
--- a/snaps/domain/test/vm_inst_tests.py
+++ b/snaps/domain/test/vm_inst_tests.py
@@ -23,26 +23,35 @@ class VmInstDomainObjectTests(unittest.TestCase):
"""
def test_construction_positional(self):
- vm_inst = VmInst('name', 'id', '456', '123', dict(), 'kp-name', list())
+ vm_inst = VmInst('name', 'id', '456', '123', list(), 'kp-name',
+ ['foo', 'bar'], ['123', '456'], 'host1', 'zone1')
self.assertEqual('name', vm_inst.name)
self.assertEqual('id', vm_inst.id)
self.assertEqual('456', vm_inst.image_id)
self.assertEqual('123', vm_inst.flavor_id)
- self.assertEqual(dict(), vm_inst.networks)
+ self.assertEqual(list(), vm_inst.ports)
self.assertEqual('kp-name', vm_inst.keypair_name)
- self.assertEqual(list(), vm_inst.sec_grp_names)
+ self.assertEqual(['foo', 'bar'], vm_inst.sec_grp_names)
+ self.assertEqual(['123', '456'], vm_inst.volume_ids)
+ self.assertEqual('host1', vm_inst.compute_host)
+ self.assertEqual('zone1', vm_inst.availability_zone)
def test_construction_named(self):
- vm_inst = VmInst(sec_grp_names=list(), networks=dict(), inst_id='id',
- name='name', flavor_id='123', image_id='456',
- keypair_name='kp-name')
+ vm_inst = VmInst(
+ availability_zone='zone1', compute_host='host1',
+ volume_ids=['123', '456'], sec_grp_names=['foo', 'bar'],
+ ports=list(), inst_id='id', name='name', flavor_id='123',
+ image_id='456', keypair_name='kp-name')
self.assertEqual('name', vm_inst.name)
self.assertEqual('id', vm_inst.id)
self.assertEqual('456', vm_inst.image_id)
self.assertEqual('123', vm_inst.flavor_id)
- self.assertEqual(dict(), vm_inst.networks)
+ self.assertEqual(list(), vm_inst.ports)
self.assertEqual('kp-name', vm_inst.keypair_name)
- self.assertEqual(list(), vm_inst.sec_grp_names)
+ self.assertEqual(['foo', 'bar'], vm_inst.sec_grp_names)
+ self.assertEqual(['123', '456'], vm_inst.volume_ids)
+ self.assertEqual('host1', vm_inst.compute_host)
+ self.assertEqual('zone1', vm_inst.availability_zone)
class FloatingIpDomainObjectTests(unittest.TestCase):
diff --git a/snaps/domain/test/volume_tests.py b/snaps/domain/test/volume_tests.py
index ec5f7b7..09401d3 100644
--- a/snaps/domain/test/volume_tests.py
+++ b/snaps/domain/test/volume_tests.py
@@ -14,7 +14,47 @@
# limitations under the License.
import unittest
-from snaps.domain.volume import QoSSpec, VolumeType, VolumeTypeEncryption
+from snaps.domain.volume import (
+ QoSSpec, VolumeType, VolumeTypeEncryption, Volume)
+
+
+class VolumeDomainObjectTests(unittest.TestCase):
+ """
+ Tests the construction of the snaps.domain.volume.Volume class
+ """
+
+ def test_construction_positional(self):
+ volume = Volume('name1', 'id1', 'proj_id1', 'desc_val1', 2,
+ 'type_val1', 'avail_zone1', False,
+ [{'attached_at': 'foo'}])
+ self.assertEqual('name1', volume.name)
+ self.assertEqual('id1', volume.id)
+ self.assertEqual('proj_id1', volume.project_id)
+ self.assertEqual('desc_val1', volume.description)
+ self.assertEqual(2, volume.size)
+ self.assertEqual('type_val1', volume.type)
+ self.assertEqual('avail_zone1', volume.availability_zone)
+ self.assertFalse(volume.multi_attach)
+ self.assertIsNotNone(volume.attachments)
+ self.assertTrue(isinstance(volume.attachments[0], dict))
+ self.assertEqual(1, len(volume.attachments))
+
+ def test_construction_named(self):
+ volume = Volume(attachments=[{'attached_at': 'foo'}],
+ multi_attach=True, availability_zone='avail_zone2',
+ vol_type='type_val2', size=3, description='desc_val2',
+ volume_id='id2', name='name2', project_id='proj_id1')
+ self.assertEqual('name2', volume.name)
+ self.assertEqual('id2', volume.id)
+ self.assertEqual('proj_id1', volume.project_id)
+ self.assertEqual('desc_val2', volume.description)
+ self.assertEqual(3, volume.size)
+ self.assertEqual('type_val2', volume.type)
+ self.assertEqual('avail_zone2', volume.availability_zone)
+ self.assertTrue(volume.multi_attach)
+ self.assertIsNotNone(volume.attachments)
+ self.assertTrue(isinstance(volume.attachments[0], dict))
+ self.assertEqual(1, len(volume.attachments))
class VolumeTypeDomainObjectTests(unittest.TestCase):
diff --git a/snaps/domain/vm_inst.py b/snaps/domain/vm_inst.py
index ca38143..f3b5381 100644
--- a/snaps/domain/vm_inst.py
+++ b/snaps/domain/vm_inst.py
@@ -19,35 +19,45 @@ class VmInst:
SNAPS domain object for Images. Should contain attributes that
are shared amongst cloud providers
"""
- def __init__(self, name, inst_id, image_id, flavor_id, networks,
- keypair_name, sec_grp_names):
+ def __init__(self, name, inst_id, image_id, flavor_id, ports,
+ keypair_name, sec_grp_names, volume_ids, compute_host,
+ availability_zone):
"""
Constructor
:param name: the image's name
:param inst_id: the instance's id
:param image_id: the instance's image id
:param flavor_id: the ID used to spawn this instance
- :param networks: dict of networks where the key is the network name and
- value is a list of associated IPs
+ :param ports: list of SNAPS-OO Port domain objects associated with this
+ server instance
:param keypair_name: the name of the associated keypair
:param sec_grp_names: list of security group names
+ :param volume_ids: list of attached volume IDs
+ :param compute_host: the name of the host on which this VM is running
+ When the user requesting this query is not part of
+ the 'admin' role, this value will be None
+ :param availability_zone: the name of the availability zone to which
+ this VM has been assigned
"""
self.name = name
self.id = inst_id
self.image_id = image_id
self.flavor_id = flavor_id
- self.networks = networks
+ self.ports = ports
self.keypair_name = keypair_name
self.sec_grp_names = sec_grp_names
+ self.volume_ids = volume_ids
+ self.compute_host = compute_host
+ self.availability_zone = availability_zone
def __eq__(self, other):
return (self.name == other.name and
self.id == other.id and
self.image_id == other.image_id and
self.flavor_id == other.flavor_id and
- self.networks == other.networks and
+ self.ports == other.ports and
self.keypair_name == other.keypair_name and
- self.sec_grp_names == other.sec_grp_names)
+ self.volume_ids == other.volume_ids)
class FloatingIp:
diff --git a/snaps/domain/volume.py b/snaps/domain/volume.py
index e82a60a..0ab2a7d 100644
--- a/snaps/domain/volume.py
+++ b/snaps/domain/volume.py
@@ -14,6 +14,48 @@
# limitations under the License.
+class Volume:
+ """
+ SNAPS domain object for Volumes. Should contain attributes that
+ are shared amongst cloud providers
+ """
+ def __init__(self, name, volume_id, project_id, description, size,
+ vol_type, availability_zone, multi_attach,
+ attachments=list()):
+ """
+ Constructor
+ :param name: the volume's name
+ :param volume_id: the volume's id
+ :param project_id: the volume's associated project id
+ :param description: the volume's description
+ :param size: the volume's size in GB
+ :param vol_type: the volume's type
+ :param availability_zone: the volume's availability zone
+ :param multi_attach: When true, volume can be attached to multiple VMs
+ :param attachments: List of dict objects containing the info on where
+ this volume is attached
+ """
+ self.name = name
+ self.id = volume_id
+ self.project_id = project_id
+ self.description = description
+ self.size = size
+ self.type = vol_type
+ self.availability_zone = availability_zone
+ self.multi_attach = multi_attach
+ self.attachments = attachments
+
+ def __eq__(self, other):
+ return (self.name == other.name
+ and self.id == other.id
+ and self.project_id == other.project_id
+ and self.description == other.description
+ and self.size == other.size
+ and self.type == other.type
+ and self.availability_zone == other.availability_zone
+ and self.multi_attach == other.multi_attach)
+
+
class VolumeType:
"""
SNAPS domain object for Volume Types. Should contain attributes that
diff --git a/snaps/file_utils.py b/snaps/file_utils.py
index a421dd3..284ae15 100644
--- a/snaps/file_utils.py
+++ b/snaps/file_utils.py
@@ -12,6 +12,8 @@
# 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.
+import ssl
+
import os
import logging
@@ -168,7 +170,8 @@ def __get_url_response(url):
proxy_handler = urllib.ProxyHandler({})
opener = urllib.build_opener(proxy_handler)
urllib.install_opener(opener)
- return urllib.urlopen(url)
+ context = ssl._create_unverified_context()
+ return urllib.urlopen(url, context=context)
def read_yaml(config_file_path):
@@ -180,7 +183,7 @@ def read_yaml(config_file_path):
logger.debug('Attempting to load configuration file - ' + config_file_path)
config_file = None
try:
- with open(config_file_path) as config_file:
+ with open(config_file_path, 'r') as config_file:
config = yaml.safe_load(config_file)
logger.info('Loaded configuration')
return config
@@ -190,6 +193,20 @@ def read_yaml(config_file_path):
config_file.close()
+def persist_dict_to_yaml(the_dict, file_name):
+ """
+ Creates a YAML file from a dict
+ :param the_dict: the dictionary to store
+ :param conf_dir: the directory used to store the config file
+ :return: the file object
+ """
+ logger.info('Persisting %s to [%s]', the_dict, file_name)
+ file_path = os.path.expanduser(file_name)
+ yaml_from_dict = yaml.dump(
+ the_dict, default_flow_style=False, default_style='')
+ return save_string_to_file(yaml_from_dict, file_path)
+
+
def read_os_env_file(os_env_filename):
"""
Reads the OS environment source file and returns a map of each key/value
diff --git a/snaps/openstack/cluster_template.py b/snaps/openstack/cluster_template.py
new file mode 100644
index 0000000..f6c7aa2
--- /dev/null
+++ b/snaps/openstack/cluster_template.py
@@ -0,0 +1,96 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+import logging
+
+from magnumclient.common.apiclient.exceptions import NotFound
+
+from snaps.openstack.openstack_creator import OpenStackMagnumObject
+from snaps.openstack.utils import magnum_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('cluster_template')
+
+
+class OpenStackClusterTemplate(OpenStackMagnumObject):
+ """
+ Class responsible for managing an volume in OpenStack
+ """
+
+ def __init__(self, os_creds, cluster_template_config):
+ """
+ Constructor
+ :param os_creds: The OpenStack connection credentials
+ :param cluster_template_config: The volume type settings
+ :return:
+ """
+ super(self.__class__, self).__init__(os_creds)
+
+ self.cluster_template_config = cluster_template_config
+ self.__cluster_template = None
+
+ def initialize(self):
+ """
+ Loads the existing Volume
+ :return: The Volume domain object or None
+ """
+ super(self.__class__, self).initialize()
+
+ self.__cluster_template = magnum_utils.get_cluster_template(
+ self._magnum, template_config=self.cluster_template_config)
+
+ return self.__cluster_template
+
+ def create(self):
+ """
+ Creates the volume in OpenStack if it does not already exist and
+ returns the domain Volume object
+ :return: The Volume domain object or None
+ """
+ self.initialize()
+
+ if not self.__cluster_template:
+ self.__cluster_template = magnum_utils.create_cluster_template(
+ self._magnum, self.cluster_template_config)
+ logger.info(
+ 'Created volume type with name - %s',
+ self.cluster_template_config.name)
+
+ return self.__cluster_template
+
+ def clean(self):
+ """
+ Cleanse environment of all artifacts
+ :return: void
+ """
+ if self.__cluster_template:
+ try:
+ magnum_utils.delete_cluster_template(
+ self._magnum, self.__cluster_template.id)
+ except NotFound:
+ pass
+
+ self.__cluster_template = None
+
+ super(self.__class__, self).clean()
+
+ def get_cluster_template(self):
+ """
+ Returns the domain Volume object as it was populated when create() was
+ called
+ :return: the object
+ """
+ return self.__cluster_template
diff --git a/snaps/openstack/create_flavor.py b/snaps/openstack/create_flavor.py
index 1479bb0..48b3e7c 100644
--- a/snaps/openstack/create_flavor.py
+++ b/snaps/openstack/create_flavor.py
@@ -16,6 +16,7 @@ import logging
from novaclient.exceptions import NotFound
+from snaps.config.flavor import FlavorConfig
from snaps.openstack.openstack_creator import OpenStackComputeObject
from snaps.openstack.utils import nova_utils
@@ -36,7 +37,7 @@ class OpenStackFlavor(OpenStackComputeObject):
"""
Constructor
:param os_creds: The OpenStack connection credentials
- :param flavor_settings: The flavor settings
+ :param flavor_settings: a FlavorConfig instance
:return:
"""
super(self.__class__, self).__init__(os_creds)
@@ -70,8 +71,6 @@ class OpenStackFlavor(OpenStackComputeObject):
if self.flavor_settings.metadata:
nova_utils.set_flavor_keys(self._nova, self.__flavor,
self.flavor_settings.metadata)
- else:
- logger.info('Did not create flavor due to cleanup mode')
return self.__flavor
@@ -88,6 +87,8 @@ class OpenStackFlavor(OpenStackComputeObject):
self.__flavor = None
+ super(self.__class__, self).clean()
+
def get_flavor(self):
"""
Returns the OpenStack flavor object
@@ -96,97 +97,13 @@ class OpenStackFlavor(OpenStackComputeObject):
return self.__flavor
-class FlavorSettings:
+class FlavorSettings(FlavorConfig):
"""
Configuration settings for OpenStack flavor creation
"""
def __init__(self, **kwargs):
- """
- Constructor
- :param config: dict() object containing the configuration settings
- using the attribute names below as each member's the
- key and overrides any of the other parameters.
- :param name: the flavor's name (required)
- :param flavor_id: the string ID (default 'auto')
- :param ram: the required RAM in MB (required)
- :param disk: the size of the root disk in GB (required)
- :param vcpus: the number of virtual CPUs (required)
- :param ephemeral: the size of the ephemeral disk in GB (default 0)
- :param swap: the size of the dedicated swap disk in GB (default 0)
- :param rxtx_factor: the receive/transmit factor to be set on ports if
- backend supports QoS extension (default 1.0)
- :param is_public: denotes whether or not the flavor is public
- (default True)
- :param metadata: freeform dict() for special metadata
- """
- self.name = kwargs.get('name')
-
- if kwargs.get('flavor_id'):
- self.flavor_id = kwargs['flavor_id']
- else:
- self.flavor_id = 'auto'
-
- self.ram = kwargs.get('ram')
- self.disk = kwargs.get('disk')
- self.vcpus = kwargs.get('vcpus')
-
- if kwargs.get('ephemeral'):
- self.ephemeral = kwargs['ephemeral']
- else:
- self.ephemeral = 0
-
- if kwargs.get('swap'):
- self.swap = kwargs['swap']
- else:
- self.swap = 0
-
- if kwargs.get('rxtx_factor'):
- self.rxtx_factor = kwargs['rxtx_factor']
- else:
- self.rxtx_factor = 1.0
-
- if kwargs.get('is_public') is not None:
- self.is_public = kwargs['is_public']
- else:
- self.is_public = True
-
- if kwargs.get('metadata'):
- self.metadata = kwargs['metadata']
- else:
- self.metadata = None
-
- if not self.name or not self.ram or not self.disk or not self.vcpus:
- raise FlavorSettingsError(
- 'The attributes name, ram, disk, and vcpus are required for'
- 'FlavorSettings')
-
- if not isinstance(self.ram, int):
- raise FlavorSettingsError('The ram attribute must be a integer')
-
- if not isinstance(self.disk, int):
- raise FlavorSettingsError('The ram attribute must be a integer')
-
- if not isinstance(self.vcpus, int):
- raise FlavorSettingsError('The vcpus attribute must be a integer')
-
- if self.ephemeral and not isinstance(self.ephemeral, int):
- raise FlavorSettingsError(
- 'The ephemeral attribute must be an integer')
-
- if self.swap and not isinstance(self.swap, int):
- raise FlavorSettingsError('The swap attribute must be an integer')
-
- if self.rxtx_factor and not isinstance(self.rxtx_factor, (int, float)):
- raise FlavorSettingsError(
- 'The is_public attribute must be an integer or float')
-
- if self.is_public and not isinstance(self.is_public, bool):
- raise FlavorSettingsError(
- 'The is_public attribute must be a boolean')
-
-
-class FlavorSettingsError(Exception):
- """
- Exception to be thrown when an flavor settings are incorrect
- """
+ from warnings import warn
+ warn('Use snaps.config.flavor.FlavorConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_image.py b/snaps/openstack/create_image.py
index 3024717..1a8aa12 100644
--- a/snaps/openstack/create_image.py
+++ b/snaps/openstack/create_image.py
@@ -19,6 +19,7 @@ import time
from snaps.openstack.openstack_creator import OpenStackCloudObject
from snaps.openstack.utils import glance_utils
+from snaps.config import image
__author__ = 'spisarski'
@@ -38,8 +39,7 @@ class OpenStackImage(OpenStackCloudObject):
"""
Constructor
:param os_creds: The OpenStack connection credentials
- :param image_settings: The image settings
- :return:
+ :param image_settings: An snaps.config.image.ImageConfig object
"""
super(self.__class__, self).__init__(os_creds)
@@ -54,9 +54,13 @@ class OpenStackImage(OpenStackCloudObject):
Loads the existing Image
:return: The Image domain object or None
"""
- self.__glance = glance_utils.glance_client(self._os_creds)
+ super(self.__class__, self).initialize()
+
+ self.__glance = glance_utils.glance_client(
+ self._os_creds, self._os_session)
self.__image = glance_utils.get_image(
self.__glance, image_settings=self.image_settings)
+
if self.__image:
logger.info('Found image with name - ' + self.image_settings.name)
return self.__image
@@ -70,6 +74,7 @@ class OpenStackImage(OpenStackCloudObject):
self.__kernel_image = glance_utils.get_image(
self.__glance,
image_settings=self.image_settings.kernel_image_settings)
+
if self.image_settings.ramdisk_image_settings:
self.__ramdisk_image = glance_utils.get_image(
self.__glance,
@@ -97,6 +102,7 @@ class OpenStackImage(OpenStackCloudObject):
self.__glance,
self.image_settings.kernel_image_settings)
extra_properties['kernel_id'] = self.__kernel_image.id
+
if self.image_settings.ramdisk_image_settings:
if not self.__ramdisk_image:
logger.info(
@@ -113,6 +119,7 @@ class OpenStackImage(OpenStackCloudObject):
logger.info(
'Created image with name - %s', self.image_settings.name)
+
if self.__image and self.image_active(block=True):
logger.info(
'Image is now active with name - %s',
@@ -122,8 +129,6 @@ class OpenStackImage(OpenStackCloudObject):
raise ImageCreationError(
'Image was not created or activated in the alloted amount'
'of time')
- else:
- logger.info('Did not create image due to cleanup mode')
return self.__image
@@ -132,10 +137,10 @@ class OpenStackImage(OpenStackCloudObject):
Cleanse environment of all artifacts
:return: void
"""
- for image in [self.__image, self.__kernel_image, self.__ramdisk_image]:
- if image:
+ for img in [self.__image, self.__kernel_image, self.__ramdisk_image]:
+ if img:
try:
- glance_utils.delete_image(self.__glance, image)
+ glance_utils.delete_image(self.__glance, img)
except HTTPNotFound:
pass
@@ -143,6 +148,11 @@ class OpenStackImage(OpenStackCloudObject):
self.__kernel_image = None
self.__ramdisk_image = None
+ if self.__glance:
+ self.__glance.http_client.session.session.close()
+
+ super(self.__class__, self).clean()
+
def get_image(self):
"""
Returns the domain Image object as it was populated when create() was
@@ -236,100 +246,14 @@ class OpenStackImage(OpenStackCloudObject):
return status == expected_status_code
-class ImageSettings:
- def __init__(self, **kwargs):
- """
- Constructor
- :param name: the image's name (required)
- :param image_user: the image's default sudo user (required)
- :param format or img_format: the image type (required)
- :param url or download_url: the image download location (requires url
- or img_file)
- :param image_file: the image file location (requires url or img_file)
- :param extra_properties: dict() object containing extra parameters to
- pass when loading the image;
- can be ids of kernel and initramfs images for
- a 3-part image
- :param nic_config_pb_loc: the file location to the Ansible Playbook
- that can configure multiple NICs
- :param kernel_image_settings: the settings for a kernel image
- :param ramdisk_image_settings: the settings for a kernel image
- :param exists: When True, an image with the given name must exist
- :param public: When True, an image will be created with public
- visibility
- """
-
- self.name = kwargs.get('name')
- self.image_user = kwargs.get('image_user')
- self.format = kwargs.get('format')
- if not self.format:
- self.format = kwargs.get('img_format')
-
- self.url = kwargs.get('url')
- if not self.url:
- self.url = kwargs.get('download_url')
- if self.url == 'None':
- self.url = None
-
- self.image_file = kwargs.get('image_file')
- if self.image_file == 'None':
- self.image_file = None
-
- self.extra_properties = kwargs.get('extra_properties')
- self.nic_config_pb_loc = kwargs.get('nic_config_pb_loc')
-
- kernel_image_settings = kwargs.get('kernel_image_settings')
- if kernel_image_settings:
- if isinstance(kernel_image_settings, dict):
- self.kernel_image_settings = ImageSettings(
- **kernel_image_settings)
- else:
- self.kernel_image_settings = kernel_image_settings
- else:
- self.kernel_image_settings = None
-
- ramdisk_image_settings = kwargs.get('ramdisk_image_settings')
- if ramdisk_image_settings:
- if isinstance(ramdisk_image_settings, dict):
- self.ramdisk_image_settings = ImageSettings(
- **ramdisk_image_settings)
- else:
- self.ramdisk_image_settings = ramdisk_image_settings
- else:
- self.ramdisk_image_settings = None
-
- if 'exists' in kwargs and kwargs['exists'] is True:
- self.exists = True
- else:
- self.exists = False
-
- if 'public' in kwargs and kwargs['public'] is True:
- self.public = True
- else:
- self.public = False
-
- if not self.name:
- raise ImageSettingsError("The attribute name is required")
-
- if not (self.url or self.image_file) and not self.exists:
- raise ImageSettingsError(
- 'URL or image file must be set or image must already exist')
-
- if not self.image_user:
- raise ImageSettingsError('Image user is required')
-
- if not self.format and not self.exists:
- raise ImageSettingsError(
- 'Format is required when the image should not already exist')
-
-
-class ImageSettingsError(Exception):
+class ImageSettings(image.ImageConfig):
"""
- Exception to be thrown when an image settings are incorrect
+ Deprecated, use snaps.config.image.ImageSettings instead
"""
-
- def __init__(self, message):
- Exception.__init__(self, message)
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.image.ImageConfig instead', DeprecationWarning)
+ super(ImageSettings, self).__init__(**kwargs)
class ImageCreationError(Exception):
diff --git a/snaps/openstack/create_instance.py b/snaps/openstack/create_instance.py
index 3d55f42..b158a25 100644
--- a/snaps/openstack/create_instance.py
+++ b/snaps/openstack/create_instance.py
@@ -15,14 +15,15 @@
import logging
import time
-from neutronclient.common.exceptions import PortNotFoundClient
from novaclient.exceptions import NotFound
-from snaps.openstack.create_network import PortSettings
+from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
from snaps.openstack.openstack_creator import OpenStackComputeObject
-from snaps.openstack.utils import glance_utils
+from snaps.openstack.utils import (
+ glance_utils, cinder_utils, settings_utils, keystone_utils)
from snaps.openstack.utils import neutron_utils
from snaps.openstack.utils import nova_utils
+from snaps.openstack.utils.nova_utils import RebootType
from snaps.provisioning import ansible_utils
__author__ = 'spisarski'
@@ -72,7 +73,14 @@ class OpenStackVmInstance(OpenStackComputeObject):
"""
super(self.__class__, self).initialize()
- self.__neutron = neutron_utils.neutron_client(self._os_creds)
+ self.__neutron = neutron_utils.neutron_client(
+ self._os_creds, self._os_session)
+ self.__keystone = keystone_utils.keystone_client(
+ self._os_creds, self._os_session)
+ self.__cinder = cinder_utils.cinder_client(
+ self._os_creds, self._os_session)
+ self.__glance = glance_utils.glance_client(
+ self._os_creds, self._os_session)
self.__ports = self.__query_ports(self.instance_settings.port_settings)
self.__lookup_existing_vm_by_name()
@@ -88,8 +96,9 @@ class OpenStackVmInstance(OpenStackComputeObject):
"""
self.initialize()
- if len(self.__ports) == 0:
- self.__ports = self.__create_ports(self.instance_settings.port_settings)
+ if len(self.__ports) != len(self.instance_settings.port_settings):
+ self.__ports = self.__create_ports(
+ self.instance_settings.port_settings)
if not self.__vm:
self.__create_vm(block)
@@ -102,7 +111,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
within the project
"""
server = nova_utils.get_server(
- self._nova, vm_inst_settings=self.instance_settings)
+ self._nova, self.__neutron, self.__keystone,
+ vm_inst_settings=self.instance_settings)
if server:
if server.name == self.instance_settings.name:
self.__vm = server
@@ -110,8 +120,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
'Found existing machine with name - %s',
self.instance_settings.name)
- fips = neutron_utils.get_floating_ips(self.__neutron,
- self.__ports)
+ fips = neutron_utils.get_port_floating_ips(
+ self.__neutron, self.__ports)
for port_id, fip in fips:
settings = self.instance_settings.floating_ip_settings
for fip_setting in settings:
@@ -130,10 +140,10 @@ class OpenStackVmInstance(OpenStackComputeObject):
active, error, or timeout waiting. Floating IPs will be
assigned after active when block=True
"""
- glance = glance_utils.glance_client(self._os_creds)
self.__vm = nova_utils.create_server(
- self._nova, self.__neutron, glance, self.instance_settings,
- self.image_settings, self.keypair_settings)
+ self._nova, self.__keystone, self.__neutron, self.__glance,
+ self.instance_settings, self.image_settings,
+ self._os_creds.project_name, self.keypair_settings)
logger.info('Created instance with name - %s',
self.instance_settings.name)
@@ -155,6 +165,26 @@ class OpenStackVmInstance(OpenStackComputeObject):
' to VM that did not activate with name - ' +
self.instance_settings.name)
+ if self.instance_settings.volume_names:
+ for volume_name in self.instance_settings.volume_names:
+ volume = cinder_utils.get_volume(
+ self.__cinder, self.__keystone, volume_name=volume_name,
+ project_name=self._os_creds.project_name)
+
+ if volume and self.vm_active(block=True):
+ vm = nova_utils.attach_volume(
+ self._nova, self.__neutron, self.__keystone, self.__vm,
+ volume, self._os_creds.project_name)
+
+ if vm:
+ self.__vm = vm
+ else:
+ logger.warn(
+ 'Volume [%s] attachment timeout ', volume.name)
+ else:
+ logger.warn('Unable to attach volume named [%s]',
+ volume_name)
+
self.__apply_floating_ips()
def __apply_floating_ips(self):
@@ -167,33 +197,44 @@ class OpenStackVmInstance(OpenStackComputeObject):
# Apply floating IPs
for floating_ip_setting in self.instance_settings.floating_ip_settings:
- port = port_dict.get(floating_ip_setting.port_name)
+ self.add_floating_ip(floating_ip_setting)
- if not port:
- raise VmInstanceCreationError(
- 'Cannot find port object with name - ' +
- floating_ip_setting.port_name)
+ def add_floating_ip(self, floating_ip_setting):
+ """
+ Adds a floating IP to a running instance
+ :param floating_ip_setting - the floating IP configuration
+ :return: the floating ip object
+ """
+ port_dict = dict()
+ for key, port in self.__ports:
+ port_dict[key] = port
+
+ # Apply floating IP
+ port = port_dict.get(floating_ip_setting.port_name)
- # Setup Floating IP only if there is a router with an external
- # gateway
- ext_gateway = self.__ext_gateway_by_router(
+ if not port:
+ raise VmInstanceCreationError(
+ 'Cannot find port object with name - ' +
+ floating_ip_setting.port_name)
+
+ # Setup Floating IP only if there is a router with an external
+ # gateway
+ ext_gateway = self.__ext_gateway_by_router(
+ floating_ip_setting.router_name)
+ if ext_gateway and self.vm_active(block=True):
+ floating_ip = neutron_utils.create_floating_ip(
+ self.__neutron, self.__keystone, ext_gateway, port.id)
+ self.__floating_ip_dict[floating_ip_setting.name] = floating_ip
+
+ logger.info(
+ 'Created floating IP %s via router - %s', floating_ip.ip,
floating_ip_setting.router_name)
- if ext_gateway:
- subnet = neutron_utils.get_subnet(
- self.__neutron,
- subnet_name=floating_ip_setting.subnet_name)
- floating_ip = neutron_utils.create_floating_ip(
- self.__neutron, ext_gateway)
- self.__floating_ip_dict[floating_ip_setting.name] = floating_ip
- logger.info(
- 'Created floating IP %s via router - %s', floating_ip.ip,
- floating_ip_setting.router_name)
- self.__add_floating_ip(floating_ip, port, subnet)
- else:
- raise VmInstanceCreationError(
- 'Unable to add floating IP to port, cannot locate router '
- 'with an external gateway ')
+ return floating_ip
+ else:
+ raise VmInstanceCreationError(
+ 'Unable to add floating IP to port, cannot locate router '
+ 'with an external gateway ')
def __ext_gateway_by_router(self, router_name):
"""
@@ -203,11 +244,11 @@ class OpenStackVmInstance(OpenStackComputeObject):
:return: the external network name or None
"""
router = neutron_utils.get_router(
- self.__neutron, router_name=router_name)
- if router and router.external_gateway_info:
+ self.__neutron, self.__keystone, router_name=router_name,
+ project_name=self._os_creds.project_name)
+ if router and router.external_network_id:
network = neutron_utils.get_network_by_id(
- self.__neutron,
- router.external_gateway_info['network_id'])
+ self.__neutron, router.external_network_id)
if network:
return network.name
return None
@@ -219,50 +260,60 @@ class OpenStackVmInstance(OpenStackComputeObject):
# Cleanup floating IPs
for name, floating_ip in self.__floating_ip_dict.items():
- try:
- logger.info('Deleting Floating IP - ' + floating_ip.ip)
- neutron_utils.delete_floating_ip(self.__neutron, floating_ip)
- except Exception as e:
- logger.error('Error deleting Floating IP - ' + str(e))
+ logger.info('Deleting Floating IP - ' + floating_ip.ip)
+ neutron_utils.delete_floating_ip(self.__neutron, floating_ip)
+
self.__floating_ip_dict = dict()
# Cleanup ports
for name, port in self.__ports:
- logger.info('Deleting Port with ID - %S ' + port.id)
- try:
- neutron_utils.delete_port(self.__neutron, port)
- except PortNotFoundClient as e:
- logger.warning('Unexpected error deleting port - %s', e)
- pass
+ logger.info('Deleting Port with ID - %s ', port.id)
+ neutron_utils.delete_port(self.__neutron, port)
+
self.__ports = list()
- # Cleanup VM
if self.__vm:
+ # Detach Volume
+ for volume_rec in self.__vm.volume_ids:
+ volume = cinder_utils.get_volume_by_id(
+ self.__cinder, volume_rec['id'])
+ if volume:
+ vm = nova_utils.detach_volume(
+ self._nova, self.__neutron, self.__keystone, self.__vm,
+ volume, self._os_creds.project_name)
+ if vm:
+ self.__vm = vm
+ else:
+ logger.warn(
+ 'Timeout waiting to detach volume %s', volume.name)
+ else:
+ logger.warn('Unable to detach volume with ID - [%s]',
+ volume_rec['id'])
+
+ # Cleanup VM
+ logger.info(
+ 'Deleting VM instance - ' + self.instance_settings.name)
+
try:
- logger.info(
- 'Deleting VM instance - ' + self.instance_settings.name)
nova_utils.delete_vm_instance(self._nova, self.__vm)
- except Exception as e:
- logger.error('Error deleting VM - %s', e)
+ except NotFound as e:
+ logger.warn('Instance already deleted - %s', e)
# Block until instance cannot be found or returns the status of
# DELETED
logger.info('Checking deletion status')
- try:
- if self.vm_deleted(block=True):
- logger.info(
- 'VM has been properly deleted VM with name - %s',
- self.instance_settings.name)
- self.__vm = None
- else:
- logger.error(
- 'VM not deleted within the timeout period of %s '
- 'seconds', self.instance_settings.vm_delete_timeout)
- except Exception as e:
+ if self.vm_deleted(block=True):
+ logger.info(
+ 'VM has been properly deleted VM with name - %s',
+ self.instance_settings.name)
+ self.__vm = None
+ else:
logger.error(
- 'Unexpected error while checking VM instance status - %s',
- e)
+ 'VM not deleted within the timeout period of %s '
+ 'seconds', self.instance_settings.vm_delete_timeout)
+
+ super(self.__class__, self).clean()
def __query_ports(self, port_settings):
"""
@@ -276,7 +327,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
for port_setting in port_settings:
port = neutron_utils.get_port(
- self.__neutron, port_settings=port_setting)
+ self.__neutron, self.__keystone, port_settings=port_setting,
+ project_name=self._os_creds.project_name)
if port:
ports.append((port_setting.name, port))
@@ -294,59 +346,16 @@ class OpenStackVmInstance(OpenStackComputeObject):
for port_setting in port_settings:
port = neutron_utils.get_port(
- self.__neutron, port_settings=port_setting)
+ self.__neutron, self.__keystone, port_settings=port_setting,
+ project_name=self._os_creds.project_name)
if not port:
- port = neutron_utils.create_port(self.__neutron, self._os_creds, port_setting)
- if port:
- ports.append((port_setting.name, port))
+ port = neutron_utils.create_port(
+ self.__neutron, self._os_creds, port_setting)
+ if port:
+ ports.append((port_setting.name, port))
return ports
- def __add_floating_ip(self, floating_ip, port, subnet, timeout=30,
- poll_interval=POLL_INTERVAL):
- """
- Returns True when active else False
- TODO - Make timeout and poll_interval configurable...
- """
- ip = None
-
- if subnet:
- # Take IP of subnet if there is one configured on which to place
- # the floating IP
- for fixed_ip in port.ips:
- if fixed_ip['subnet_id'] == subnet.id:
- ip = fixed_ip['ip_address']
- break
- else:
- # Simply take the first
- ip = port.ips[0]['ip_address']
-
- if ip:
- count = timeout / poll_interval
- while count > 0:
- logger.debug('Attempting to add floating IP to instance')
- try:
- nova_utils.add_floating_ip_to_server(
- self._nova, self.__vm, floating_ip, ip)
- logger.info(
- 'Added floating IP %s to port IP %s on instance %s',
- floating_ip.ip, ip, self.instance_settings.name)
- return
- except Exception as e:
- logger.debug(
- 'Retry adding floating IP to instance. Last attempt '
- 'failed with - %s', e)
- time.sleep(poll_interval)
- count -= 1
- pass
- else:
- raise VmInstanceCreationError(
- 'Unable find IP address on which to place the floating IP')
-
- logger.error('Timeout attempting to add the floating IP to instance.')
- raise VmInstanceCreationError(
- 'Timeout while attempting add floating IP to instance')
-
def get_os_creds(self):
"""
Returns the OpenStack credentials used to create these objects
@@ -359,7 +368,9 @@ class OpenStackVmInstance(OpenStackComputeObject):
Returns the latest version of this server object from OpenStack
:return: Server object
"""
- return nova_utils.get_server_object_by_id(self._nova, self.__vm.id)
+ return nova_utils.get_server_object_by_id(
+ self._nova, self.__neutron, self.__keystone, self.__vm.id,
+ self._os_creds.project_name)
def get_console_output(self):
"""
@@ -380,8 +391,10 @@ class OpenStackVmInstance(OpenStackComputeObject):
port = self.get_port_by_name(port_name)
if port:
if subnet_name:
+ network = neutron_utils.get_network_by_id(
+ self.__neutron, port.network_id)
subnet = neutron_utils.get_subnet(
- self.__neutron, subnet_name=subnet_name)
+ self.__neutron, network, subnet_name=subnet_name)
if not subnet:
logger.warning('Cannot retrieve port IP as subnet could '
'not be located with name - %s',
@@ -426,26 +439,11 @@ class OpenStackVmInstance(OpenStackComputeObject):
Returns a dictionary of a VMs info as returned by OpenStack
:return: a dict()
"""
- return nova_utils.get_server_info(self._nova, self.__vm)
+ from warnings import warn
+ warn('Do not use the returned dict() structure',
+ DeprecationWarning)
- def config_nics(self):
- """
- Responsible for configuring NICs on RPM systems where the instance has
- more than one configured port
- :return: the value returned by ansible_utils.apply_ansible_playbook()
- """
- if len(self.__ports) > 1 and len(self.__floating_ip_dict) > 0:
- if self.vm_active(block=True) and self.vm_ssh_active(block=True):
- for key, port in self.__ports:
- port_index = self.__ports.index((key, port))
- if port_index > 0:
- nic_name = 'eth' + repr(port_index)
- retval = self.__config_nic(
- nic_name, port,
- self.__get_first_provisioning_floating_ip().ip)
- logger.info('Configured NIC - %s on VM - %s',
- nic_name, self.instance_settings.name)
- return retval
+ return nova_utils.get_server_info(self._nova, self.__vm)
def __get_first_provisioning_floating_ip(self):
"""
@@ -462,30 +460,10 @@ class OpenStackVmInstance(OpenStackComputeObject):
for key, fip in self.__floating_ip_dict.items():
return fip
- def __config_nic(self, nic_name, port, ip):
- """
- Although ports/NICs can contain multiple IPs, this code currently only
- supports the first.
-
- :param nic_name: Name of the interface
- :param port: The port information containing the expected IP values.
- :param ip: The IP on which to apply the playbook.
- :return: the return value from ansible
- """
- port_ip = port.ips[0]['ip_address']
- variables = {
- 'floating_ip': ip,
- 'nic_name': nic_name,
- 'nic_ip': port_ip
- }
-
- if self.image_settings.nic_config_pb_loc and self.keypair_settings:
- return self.apply_ansible_playbook(
- self.image_settings.nic_config_pb_loc, variables)
- else:
- logger.warning(
- 'VM %s cannot self configure NICs eth1++. No playbook or '
- 'keypairs found.', self.instance_settings.name)
+ # When cannot be found above
+ if len(self.__floating_ip_dict) > 0:
+ for key, fip in self.__floating_ip_dict.items():
+ return fip
def apply_ansible_playbook(self, pb_file_loc, variables=None,
fip_name=None):
@@ -496,12 +474,16 @@ class OpenStackVmInstance(OpenStackComputeObject):
playbook
:param fip_name: the name of the floating IP to use for applying the
playbook (default - will take the first)
- :return: the return value from ansible
"""
- return ansible_utils.apply_playbook(
+ from warnings import warn
+ warn('This method will be removed in a subsequent release',
+ DeprecationWarning)
+
+ ansible_utils.apply_playbook(
pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
- self.get_image_user(), self.keypair_settings.private_filepath,
- variables, self._os_creds.proxy_settings)
+ self.get_image_user(),
+ ssh_priv_key_file_path=self.keypair_settings.private_filepath,
+ variables=variables, proxy_setting=self._os_creds.proxy_settings)
def get_image_user(self):
"""
@@ -541,9 +523,14 @@ class OpenStackVmInstance(OpenStackComputeObject):
:param poll_interval: The polling interval in seconds
:return: T/F
"""
- return self.__vm_status_check(STATUS_ACTIVE, block,
- self.instance_settings.vm_boot_timeout,
- poll_interval)
+ if self.__vm_status_check(
+ STATUS_ACTIVE, block, self.instance_settings.vm_boot_timeout,
+ poll_interval):
+ self.__vm = nova_utils.get_server_object_by_id(
+ self._nova, self.__neutron, self.__keystone, self.__vm.id,
+ self._os_creds.project_name)
+ return True
+ return False
def __vm_status_check(self, expected_status_code, block, timeout,
poll_interval):
@@ -606,18 +593,27 @@ class OpenStackVmInstance(OpenStackComputeObject):
status)
return status == expected_status_code
- def vm_ssh_active(self, block=False, poll_interval=POLL_INTERVAL):
+ def vm_ssh_active(self, user_override=None, password=None, block=False,
+ timeout=None, poll_interval=POLL_INTERVAL):
"""
Returns true when the VM can be accessed via SSH
+ :param user_override: overrides the user with which to create the
+ connection
+ :param password: overrides the use of a password instead of a private
+ key with which to create the connection
:param block: When true, thread will block until active or timeout
value in seconds has been exceeded (False)
+ :param timeout: the number of seconds to retry obtaining the connection
+ and overrides the ssh_connect_timeout member of the
+ self.instance_settings object
:param poll_interval: The polling interval
:return: T/F
"""
# sleep and wait for VM status change
logger.info('Checking if VM is active')
- timeout = self.instance_settings.ssh_connect_timeout
+ if not timeout:
+ timeout = self.instance_settings.ssh_connect_timeout
if self.vm_active(block=True):
if block:
@@ -626,7 +622,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
start = time.time() - timeout
while timeout > time.time() - start:
- status = self.__ssh_active()
+ status = self.__ssh_active(
+ user_override=user_override, password=password)
if status:
logger.info('SSH is active for VM instance')
return True
@@ -640,18 +637,69 @@ class OpenStackVmInstance(OpenStackComputeObject):
logger.error('Timeout attempting to connect with VM via SSH')
return False
- def __ssh_active(self):
+ def __ssh_active(self, user_override=None, password=None):
"""
Returns True when can create a SSH session else False
:return: T/F
"""
if len(self.__floating_ip_dict) > 0:
- ssh = self.ssh_client()
+ ssh = self.ssh_client(
+ user_override=user_override, password=password)
if ssh:
ssh.close()
return True
return False
+ def cloud_init_complete(self, block=False, poll_interval=POLL_INTERVAL):
+ """
+ Returns true when the VM's cloud-init routine has completed.
+ Note: this is currently done via SSH, therefore, if this instance does
+ not have a Floating IP or a running SSH server, this routine
+ will always return False or raise an Exception
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param poll_interval: The polling interval
+ :return: T/F
+ """
+ # sleep and wait for VM status change
+ logger.info('Checking if cloud-init has completed')
+
+ timeout = self.instance_settings.cloud_init_timeout
+
+ if self.vm_active(block=True) and self.vm_ssh_active(block=True):
+ if block:
+ start = time.time()
+ else:
+ start = time.time() - timeout
+
+ while timeout > time.time() - start:
+ status = self.__cloud_init_complete()
+ if status:
+ logger.info('cloud-init complete for VM instance')
+ return True
+
+ logger.debug('Retry cloud-init query in ' + str(
+ poll_interval) + ' seconds')
+ time.sleep(poll_interval)
+ logger.debug('cloud-init complete timeout in ' + str(
+ timeout - (time.time() - start)))
+
+ logger.error('Timeout waiting for cloud-init to complete')
+ return False
+
+ def __cloud_init_complete(self):
+ """
+ Returns True when can create a SSH session else False
+ :return: T/F
+ """
+ if len(self.__floating_ip_dict) > 0:
+ ssh = self.ssh_client()
+ if ssh:
+ stdin1, stdout1, sterr1 = ssh.exec_command(
+ 'ls -l /var/lib/cloud/instance/boot-finished')
+ return stdout1.channel.recv_exit_status() == 0
+ return False
+
def get_floating_ip(self, fip_name=None):
"""
Returns the floating IP object byt name if found, else the first known,
@@ -659,28 +707,40 @@ class OpenStackVmInstance(OpenStackComputeObject):
:param fip_name: the name of the floating IP to return
:return: the SSH client or None
"""
- fip = None
if fip_name and self.__floating_ip_dict.get(fip_name):
return self.__floating_ip_dict.get(fip_name)
- if not fip:
+ else:
return self.__get_first_provisioning_floating_ip()
- def ssh_client(self, fip_name=None):
+ def ssh_client(self, fip_name=None, user_override=None, password=None):
"""
Returns an SSH client using the name or the first known floating IP if
exists, else None
:param fip_name: the name of the floating IP to return
+ :param user_override: the username to use instead of the default
+ :param password: the password to use instead of the private key
:return: the SSH client or None
"""
fip = self.get_floating_ip(fip_name)
+
+ ansible_user = self.get_image_user()
+ if user_override:
+ ansible_user = user_override
+
+ if password:
+ private_key = None
+ else:
+ private_key = self.keypair_settings.private_filepath
+
if fip:
return ansible_utils.ssh_client(
self.__get_first_provisioning_floating_ip().ip,
- self.get_image_user(),
- self.keypair_settings.private_filepath,
+ ansible_user,
+ private_key_filepath=private_key,
+ password=password,
proxy_settings=self._os_creds.proxy_settings)
else:
- logger.warning(
+ FloatingIPAllocationError(
'Cannot return an SSH client. No Floating IP configured')
def add_security_group(self, security_group):
@@ -723,165 +783,75 @@ class OpenStackVmInstance(OpenStackComputeObject):
logger.warning('Security group not removed - ' + str(e))
return False
-
-class VmInstanceSettings:
- """
- Class responsible for holding configuration setting for a VM Instance
- """
-
- def __init__(self, **kwargs):
+ def reboot(self, reboot_type=RebootType.soft):
"""
- Constructor
- :param name: the name of the VM
- :param flavor: the VM's flavor name
- :param port_settings: the port configuration settings (required)
- :param security_group_names: a set of names of the security groups to
- add to the VM
- :param floating_ip_settings: the floating IP configuration settings
- :param sudo_user: the sudo user of the VM that will override the
- instance_settings.image_user when trying to
- connect to the VM
- :param vm_boot_timeout: the amount of time a thread will sleep waiting
- for an instance to boot
- :param vm_delete_timeout: the amount of time a thread will sleep
- waiting for an instance to be deleted
- :param ssh_connect_timeout: the amount of time a thread will sleep
- waiting obtaining an SSH connection to a VM
- :param availability_zone: the name of the compute server on which to
- deploy the VM (optional)
- :param userdata: the string contents of any optional cloud-init script
- to execute after the VM has been activated.
- This value may also contain a dict who's key value
- must contain the key 'cloud-init_file' which denotes
- the location of some file containing the cloud-init
- script
- """
- self.name = kwargs.get('name')
- self.flavor = kwargs.get('flavor')
- self.sudo_user = kwargs.get('sudo_user')
- self.userdata = kwargs.get('userdata')
-
- self.port_settings = list()
- port_settings = kwargs.get('ports')
- if not port_settings:
- port_settings = kwargs.get('port_settings')
- if port_settings:
- for port_setting in port_settings:
- if isinstance(port_setting, dict):
- self.port_settings.append(PortSettings(**port_setting))
- elif isinstance(port_setting, PortSettings):
- self.port_settings.append(port_setting)
-
- if kwargs.get('security_group_names'):
- if isinstance(kwargs['security_group_names'], list):
- self.security_group_names = kwargs['security_group_names']
- elif isinstance(kwargs['security_group_names'], set):
- self.security_group_names = kwargs['security_group_names']
- elif isinstance(kwargs['security_group_names'], str):
- self.security_group_names = [kwargs['security_group_names']]
- else:
- raise VmInstanceSettingsError(
- 'Invalid data type for security_group_names attribute')
- else:
- self.security_group_names = set()
-
- self.floating_ip_settings = list()
- floating_ip_settings = kwargs.get('floating_ips')
- if not floating_ip_settings:
- floating_ip_settings = kwargs.get('floating_ip_settings')
- if floating_ip_settings:
- for floating_ip_config in floating_ip_settings:
- if isinstance(floating_ip_config, FloatingIpSettings):
- self.floating_ip_settings.append(floating_ip_config)
- else:
- self.floating_ip_settings.append(FloatingIpSettings(
- **floating_ip_config['floating_ip']))
-
- if kwargs.get('vm_boot_timeout'):
- self.vm_boot_timeout = kwargs['vm_boot_timeout']
- else:
- self.vm_boot_timeout = 900
-
- if kwargs.get('vm_delete_timeout'):
- self.vm_delete_timeout = kwargs['vm_delete_timeout']
- else:
- self.vm_delete_timeout = 300
+ Issues a reboot call
+ :param reboot_type: instance of
+ snaps.openstack.utils.nova_utils.RebootType
+ enumeration
+ :return:
+ """
+ nova_utils.reboot_server(
+ self._nova, self.__vm, reboot_type=reboot_type)
- if kwargs.get('ssh_connect_timeout'):
- self.ssh_connect_timeout = kwargs['ssh_connect_timeout']
- else:
- self.ssh_connect_timeout = 180
- if kwargs.get('availability_zone'):
- self.availability_zone = kwargs['availability_zone']
- else:
- self.availability_zone = None
+def generate_creator(os_creds, vm_inst, image_config, project_name,
+ keypair_config=None):
+ """
+ Initializes an OpenStackVmInstance object
+ :param os_creds: the OpenStack credentials
+ :param vm_inst: the SNAPS-OO VmInst domain object
+ :param image_config: the associated ImageConfig object
+ :param project_name: the associated project ID
+ :param keypair_config: the associated KeypairConfig object (optional)
+ :return: an initialized OpenStackVmInstance object
+ """
+ session = keystone_utils.keystone_session(os_creds)
+ nova = nova_utils.nova_client(os_creds, session)
+ keystone = keystone_utils.keystone_client(os_creds, session)
+ neutron = neutron_utils.neutron_client(os_creds, session)
- if not self.name or not self.flavor:
- raise VmInstanceSettingsError(
- 'Instance configuration requires the attributes: name, flavor')
+ try:
+ derived_inst_config = settings_utils.create_vm_inst_config(
+ nova, keystone, neutron, vm_inst, project_name)
- if len(self.port_settings) == 0:
- raise VmInstanceSettingsError(
- 'Instance configuration requires port settings (aka. NICS)')
+ derived_inst_creator = OpenStackVmInstance(
+ os_creds, derived_inst_config, image_config, keypair_config)
+ derived_inst_creator.initialize()
+ return derived_inst_creator
+ finally:
+ keystone_utils.close_session(session)
-class FloatingIpSettings:
+class VmInstanceSettings(VmInstanceConfig):
"""
- Class responsible for holding configuration settings for a floating IP
+ Deprecated, use snaps.config.vm_inst.VmInstanceConfig instead
"""
-
def __init__(self, **kwargs):
- """
- Constructor
- :param name: the name of the floating IP
- :param port_name: the name of the router to the external network
- :param router_name: the name of the router to the external network
- :param subnet_name: the name of the subnet on which to attach the
- floating IP
- :param provisioning: when true, this floating IP can be used for
- provisioning
-
- TODO - provisioning flag is a hack as I have only observed a single
- Floating IPs that actually works on an instance. Multiple floating IPs
- placed on different subnets from the same port are especially
- troublesome as you cannot predict which one will actually connect.
- For now, it is recommended not to setup multiple floating IPs on an
- instance unless absolutely necessary.
- """
- self.name = kwargs.get('name')
- self.port_name = kwargs.get('port_name')
- self.port_id = kwargs.get('port_id')
- self.router_name = kwargs.get('router_name')
- self.subnet_name = kwargs.get('subnet_name')
- if kwargs.get('provisioning') is not None:
- self.provisioning = kwargs['provisioning']
- else:
- self.provisioning = True
+ from warnings import warn
+ warn('Use snaps.config.vm_inst.VmInstanceConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
- # if not self.name or not self.port_name or not self.router_name:
- if not self.name or not self.router_name:
- raise FloatingIpSettingsError(
- 'The attributes name, port_name and router_name are required')
- if not self.port_name and not self.port_id:
- raise FloatingIpSettingsError(
- 'The attributes port_name or port_id are required')
-
-
-class VmInstanceSettingsError(Exception):
+class FloatingIpSettings(FloatingIpConfig):
"""
- Exception to be thrown when an VM instance settings are incorrect
+ Deprecated, use snaps.config.vm_inst.FloatingIpConfig instead
"""
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.vm_inst.FloatingIpConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
-class FloatingIpSettingsError(Exception):
+class VmInstanceCreationError(Exception):
"""
- Exception to be thrown when an VM instance settings are incorrect
+ Exception to be thrown when an VM instance cannot be created
"""
-class VmInstanceCreationError(Exception):
+class FloatingIPAllocationError(Exception):
"""
- Exception to be thrown when an VM instance cannot be created
+ Exception to be thrown when an VM instance cannot allocate a floating IP
"""
diff --git a/snaps/openstack/create_keypairs.py b/snaps/openstack/create_keypairs.py
index 3869afc..b037c3f 100644
--- a/snaps/openstack/create_keypairs.py
+++ b/snaps/openstack/create_keypairs.py
@@ -15,10 +15,10 @@
import logging
import os
-from neutronclient.common.utils import str2bool
from novaclient.exceptions import NotFound
from snaps import file_utils
+from snaps.config.keypair import KeypairConfig
from snaps.openstack.openstack_creator import OpenStackComputeObject
from snaps.openstack.utils import nova_utils
@@ -36,7 +36,7 @@ class OpenStackKeypair(OpenStackComputeObject):
"""
Constructor - all parameters are required
:param os_creds: The credentials to connect with OpenStack
- :param keypair_settings: The settings used to create a keypair
+ :param keypair_settings: a KeypairConfig object
"""
super(self.__class__, self).__init__(os_creds)
@@ -123,6 +123,7 @@ class OpenStackKeypair(OpenStackComputeObject):
self.keypair_settings.public_filepath)
os.chmod(expanded_path, 0o755)
os.remove(expanded_path)
+ logger.info('Deleted public key file [%s]', expanded_path)
if (self.keypair_settings.private_filepath and
file_utils.file_exists(
self.keypair_settings.private_filepath)):
@@ -130,6 +131,9 @@ class OpenStackKeypair(OpenStackComputeObject):
self.keypair_settings.private_filepath)
os.chmod(expanded_path, 0o755)
os.remove(expanded_path)
+ logger.info('Deleted private key file [%s]', expanded_path)
+
+ super(self.__class__, self).clean()
def get_keypair(self):
"""
@@ -139,47 +143,13 @@ class OpenStackKeypair(OpenStackComputeObject):
return self.__keypair
-class KeypairSettings:
+class KeypairSettings(KeypairConfig):
"""
Class representing a keypair configuration
"""
def __init__(self, **kwargs):
- """
- Constructor - all parameters are optional
- :param name: The keypair name.
- :param public_filepath: The path to/from the filesystem where the
- public key file is or will be stored
- :param private_filepath: The path where the generated private key file
- will be stored
- :param key_size: The number of bytes for the key size when it needs to
- be generated (Must be >=512 default 1024)
- :param delete_on_clean: when True, the key files will be deleted when
- OpenStackKeypair#clean() is called
- :return:
- """
-
- self.name = kwargs.get('name')
- self.public_filepath = kwargs.get('public_filepath')
- self.private_filepath = kwargs.get('private_filepath')
- self.key_size = int(kwargs.get('key_size', 1024))
-
- if kwargs.get('delete_on_clean') is not None:
- if isinstance(kwargs.get('delete_on_clean'), bool):
- self.delete_on_clean = kwargs.get('delete_on_clean')
- else:
- self.delete_on_clean = str2bool(kwargs.get('delete_on_clean'))
- else:
- self.delete_on_clean = None
-
- if not self.name:
- raise KeypairSettingsError('Name is a required attribute')
-
- if self.key_size < 512:
- raise KeypairSettingsError('key_size must be >=512')
-
-
-class KeypairSettingsError(Exception):
- """
- Exception to be thrown when keypair settings are incorrect
- """
+ from warnings import warn
+ warn('Use snaps.config.keypair.KeypairConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_network.py b/snaps/openstack/create_network.py
index 437f294..13c9667 100644
--- a/snaps/openstack/create_network.py
+++ b/snaps/openstack/create_network.py
@@ -14,10 +14,12 @@
# limitations under the License.
import logging
-from neutronclient.common.exceptions import NotFound
+import enum
+from neutronclient.common.exceptions import NetworkNotFoundClient, Unauthorized
+from snaps.config.network import NetworkConfig, SubnetConfig, PortConfig
from snaps.openstack.openstack_creator import OpenStackNetworkObject
-from snaps.openstack.utils import keystone_utils, neutron_utils
+from snaps.openstack.utils import neutron_utils
__author__ = 'spisarski'
@@ -41,7 +43,6 @@ class OpenStackNetwork(OpenStackNetworkObject):
# Attributes instantiated on create()
self.__network = None
- self.__subnets = list()
def initialize(self):
"""
@@ -50,18 +51,14 @@ class OpenStackNetwork(OpenStackNetworkObject):
"""
super(self.__class__, self).initialize()
- self.__network = neutron_utils.get_network(
- self._neutron, network_settings=self.network_settings,
- project_id=self.network_settings.get_project_id(self._os_creds))
-
- if self.__network:
- for subnet_setting in self.network_settings.subnet_settings:
- sub_inst = neutron_utils.get_subnet(
- self._neutron, subnet_settings=subnet_setting)
- if sub_inst:
- self.__subnets.append(sub_inst)
- logger.debug(
- "Subnet '%s' created successfully" % sub_inst.id)
+ try:
+ self.__network = neutron_utils.get_network(
+ self._neutron, self._keystone,
+ network_settings=self.network_settings,
+ project_name=self._os_creds.project_name)
+ except Unauthorized as e:
+ logger.warn('Unable to lookup network with name %s - %s',
+ self.network_settings.name, e)
return self.__network
@@ -77,19 +74,7 @@ class OpenStackNetwork(OpenStackNetworkObject):
self.__network = neutron_utils.create_network(
self._neutron, self._os_creds, self.network_settings)
logger.debug(
- "Network '%s' created successfully" % self.__network.id)
-
- for subnet_setting in self.network_settings.subnet_settings:
- sub_inst = neutron_utils.get_subnet(
- self._neutron, subnet_settings=subnet_setting)
- if not sub_inst:
- sub_inst = neutron_utils.create_subnet(
- self._neutron, subnet_setting, self._os_creds,
- self.__network)
- if sub_inst:
- self.__subnets.append(sub_inst)
- logger.debug(
- "Subnet '%s' created successfully" % sub_inst.id)
+ 'Network [%s] created successfully' % self.__network.id)
return self.__network
@@ -97,24 +82,13 @@ class OpenStackNetwork(OpenStackNetworkObject):
"""
Removes and deletes all items created in reverse order.
"""
- for subnet in self.__subnets:
- try:
- logger.info(
- 'Deleting subnet with name ' + subnet.name)
- neutron_utils.delete_subnet(self._neutron, subnet)
- except NotFound as e:
- logger.warning(
- 'Error deleting subnet with message - ' + str(e))
- pass
- self.__subnets = list()
-
- if self.__network:
- try:
- neutron_utils.delete_network(self._neutron, self.__network)
- except NotFound:
- pass
+ try:
+ neutron_utils.delete_network(self._neutron, self.__network)
+ except NetworkNotFoundClient:
+ pass
+ self.__network = None
- self.__network = None
+ super(self.__class__, self).clean()
def get_network(self):
"""
@@ -123,445 +97,54 @@ class OpenStackNetwork(OpenStackNetworkObject):
"""
return self.__network
- def get_subnets(self):
- """
- Returns the OpenStack subnet objects
- :return:
- """
- return self.__subnets
-
-class NetworkSettings:
+class NetworkSettings(NetworkConfig):
"""
- Class representing a network configuration
+ Class to hold the configuration settings required for creating OpenStack
+ Network objects
+ deprecated
"""
def __init__(self, **kwargs):
- """
- Constructor - all parameters are optional
- :param name: The network name.
- :param admin_state_up: The administrative status of the network.
- True = up / False = down (default True)
- :param shared: Boolean value indicating whether this network is shared
- across all projects/tenants. By default, only
- administrative users can change this value.
- :param project_name: Admin-only. The name of the project that will own
- the network. This project can be different from
- the project that makes the create network request.
- However, only administrative users can specify a
- project ID other than their own. You cannot change
- this value through authorization policies.
- :param external: when true, will setup an external network
- (default False).
- :param network_type: the type of network (i.e. vlan|flat).
- :param physical_network: the name of the physical network
- (this is required when network_type is 'flat')
- :param segmentation_id: the id of the segmentation
- (this is required when network_type is 'vlan')
- :param subnets or subnet_settings: List of SubnetSettings objects.
- :return:
- """
+ from warnings import warn
+ warn('Use snaps.config.network.NetworkConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
- self.project_id = None
- self.name = kwargs.get('name')
- if kwargs.get('admin_state_up') is not None:
- self.admin_state_up = bool(kwargs['admin_state_up'])
- else:
- self.admin_state_up = True
-
- if kwargs.get('shared') is not None:
- self.shared = bool(kwargs['shared'])
- else:
- self.shared = None
-
- self.project_name = kwargs.get('project_name')
-
- if kwargs.get('external') is not None:
- self.external = bool(kwargs.get('external'))
- else:
- self.external = False
-
- self.network_type = kwargs.get('network_type')
- self.physical_network = kwargs.get('physical_network')
- self.segmentation_id = kwargs.get('segmentation_id')
-
- self.subnet_settings = list()
- subnet_settings = kwargs.get('subnets')
- if not subnet_settings:
- subnet_settings = kwargs.get('subnet_settings')
- if subnet_settings:
- for subnet_config in subnet_settings:
- if isinstance(subnet_config, SubnetSettings):
- self.subnet_settings.append(subnet_config)
- else:
- self.subnet_settings.append(
- SubnetSettings(**subnet_config['subnet']))
-
- if not self.name or len(self.name) < 1:
- raise NetworkSettingsError('Name required for networks')
-
- def get_project_id(self, os_creds):
- """
- Returns the project ID for a given project_name or None
- :param os_creds: the credentials required for keystone client retrieval
- :return: the ID or None
- """
- if self.project_id:
- return self.project_id
- else:
- if self.project_name:
- keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(
- keystone=keystone, project_name=self.project_name)
- if project:
- return project.id
-
- return None
-
- def dict_for_neutron(self, os_creds):
- """
- Returns a dictionary object representing this object.
- This is meant to be converted into JSON designed for use by the Neutron
- API
- TODO - expand automated testing to exercise all parameters
-
- :param os_creds: the OpenStack credentials
- :return: the dictionary object
- """
- out = dict()
-
- if self.name:
- out['name'] = self.name
- if self.admin_state_up is not None:
- out['admin_state_up'] = self.admin_state_up
- if self.shared:
- out['shared'] = self.shared
- if self.project_name:
- project_id = self.get_project_id(os_creds)
- if project_id:
- out['tenant_id'] = project_id
- else:
- raise NetworkSettingsError(
- 'Could not find project ID for project named - ' +
- self.project_name)
- if self.network_type:
- out['provider:network_type'] = self.network_type
- if self.physical_network:
- out['provider:physical_network'] = self.physical_network
- if self.segmentation_id:
- out['provider:segmentation_id'] = self.segmentation_id
- if self.external:
- out['router:external'] = self.external
- return {'network': out}
-
-
-class NetworkSettingsError(Exception):
+class IPv6Mode(enum.Enum):
"""
- Exception to be thrown when networks settings attributes are incorrect
+ A rule's direction
+ deprecated - use snaps.config.network.IPv6Mode
"""
+ slaac = 'slaac'
+ stateful = 'dhcpv6-stateful'
+ stateless = 'dhcpv6-stateless'
-class SubnetSettings:
+class SubnetSettings(SubnetConfig):
"""
- Class representing a subnet configuration
+ Class to hold the configuration settings required for creating OpenStack
+ Subnet objects
+ deprecated
"""
def __init__(self, **kwargs):
- """
- Constructor - all parameters are optional except cidr (subnet mask)
- :param cidr: The CIDR. REQUIRED if config parameter is None
- :param ip_version: The IP version, which is 4 or 6.
- :param name: The subnet name.
- :param project_name: The name of the project who owns the network.
- Only administrative users can specify a project ID
- other than their own. You cannot change this value
- through authorization policies.
- :param start: The start address for the allocation pools.
- :param end: The end address for the allocation pools.
- :param gateway_ip: The gateway IP address.
- :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
- disabled.
- :param dns_nameservers: A list of DNS name servers for the subnet.
- Specify each name server as an IP address
- and separate multiple entries with a space.
- For example [8.8.8.7 8.8.8.8].
- :param host_routes: A list of host route dictionaries for the subnet.
- For example:
- "host_routes":[
- {
- "destination":"0.0.0.0/0",
- "nexthop":"123.456.78.9"
- },
- {
- "destination":"192.168.0.0/24",
- "nexthop":"192.168.0.1"
- }
- ]
- :param destination: The destination for static route
- :param nexthop: The next hop for the destination.
- :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
- dhcpv6-stateless, or slaac.
- :param ipv6_address_mode: A valid value is dhcpv6-stateful,
- dhcpv6-stateless, or slaac.
- :raise: SubnetSettingsError when config does not have or cidr values
- are None
- """
- self.cidr = kwargs.get('cidr')
- if kwargs.get('ip_version'):
- self.ip_version = kwargs['ip_version']
- else:
- self.ip_version = 4
-
- # Optional attributes that can be set after instantiation
- self.name = kwargs.get('name')
- self.project_name = kwargs.get('project_name')
- self.start = kwargs.get('start')
- self.end = kwargs.get('end')
- self.gateway_ip = kwargs.get('gateway_ip')
- self.enable_dhcp = kwargs.get('enable_dhcp')
+ from warnings import warn
+ warn('Use snaps.config.network.SubnetConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
- if kwargs.get('dns_nameservers'):
- self.dns_nameservers = kwargs.get('dns_nameservers')
- else:
- self.dns_nameservers = ['8.8.8.8']
- self.host_routes = kwargs.get('host_routes')
- self.destination = kwargs.get('destination')
- self.nexthop = kwargs.get('nexthop')
- self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
- self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
-
- if not self.name or not self.cidr:
- raise SubnetSettingsError('Name and cidr required for subnets')
-
- def dict_for_neutron(self, os_creds, network=None):
- """
- Returns a dictionary object representing this object.
- This is meant to be converted into JSON designed for use by the Neutron
- API
- :param os_creds: the OpenStack credentials
- :param network: The network object on which the subnet will be created
- (optional)
- :return: the dictionary object
- """
- out = {
- 'cidr': self.cidr,
- 'ip_version': self.ip_version,
- }
-
- if network:
- out['network_id'] = network.id
- if self.name:
- out['name'] = self.name
- if self.project_name:
- keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(
- keystone=keystone, project_name=self.project_name)
- project_id = None
- if project:
- project_id = project.id
- if project_id:
- out['tenant_id'] = project_id
- else:
- raise SubnetSettingsError(
- 'Could not find project ID for project named - ' +
- self.project_name)
- if self.start and self.end:
- out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
- if self.gateway_ip:
- out['gateway_ip'] = self.gateway_ip
- if self.enable_dhcp is not None:
- out['enable_dhcp'] = self.enable_dhcp
- if self.dns_nameservers and len(self.dns_nameservers) > 0:
- out['dns_nameservers'] = self.dns_nameservers
- if self.host_routes and len(self.host_routes) > 0:
- out['host_routes'] = self.host_routes
- if self.destination:
- out['destination'] = self.destination
- if self.nexthop:
- out['nexthop'] = self.nexthop
- if self.ipv6_ra_mode:
- out['ipv6_ra_mode'] = self.ipv6_ra_mode
- if self.ipv6_address_mode:
- out['ipv6_address_mode'] = self.ipv6_address_mode
- return out
-
-
-class SubnetSettingsError(Exception):
- """
- Exception to be thrown when subnet settings attributes are incorrect
+class PortSettings(PortConfig):
"""
-
-
-class PortSettings:
- """
- Class representing a port configuration
+ Class to hold the configuration settings required for creating OpenStack
+ Subnet objects
+ deprecated
"""
def __init__(self, **kwargs):
- """
- Constructor
- :param name: A symbolic name for the port (optional).
- :param network_name: The name of the network on which to create the
- port (required).
- :param admin_state_up: A boolean value denoting the administrative
- status of the port. True = up / False = down
- :param project_name: The name of the project who owns the network.
- Only administrative users can specify a project ID
- other than their own. You cannot change this value
- through authorization policies.
- :param mac_address: The MAC address. If you specify an address that is
- not valid, a Bad Request (400) status code is
- returned. If you do not specify a MAC address,
- OpenStack Networking tries to allocate one. If a
- failure occurs, a Service Unavailable (503) status
- code is returned.
- :param ip_addrs: A list of dict objects where each contains two keys
- 'subnet_name' and 'ip' values which will get mapped to
- self.fixed_ips. These values will be directly
- translated into the fixed_ips dict
- :param fixed_ips: A dict where the key is the subnet IDs and value is
- the IP address to assign to the port
- :param security_groups: One or more security group IDs.
- :param allowed_address_pairs: A dictionary containing a set of zero or
- more allowed address pairs. An address
- pair contains an IP address and MAC
- address.
- :param opt_value: The extra DHCP option value.
- :param opt_name: The extra DHCP option name.
- :param device_owner: The ID of the entity that uses this port.
- For example, a DHCP agent.
- :param device_id: The ID of the device that uses this port.
- For example, a virtual server.
- :return:
- """
- if 'port' in kwargs:
- kwargs = kwargs['port']
-
- self.network = None
-
- self.name = kwargs.get('name')
- self.network_name = kwargs.get('network_name')
-
- if kwargs.get('admin_state_up') is not None:
- self.admin_state_up = bool(kwargs['admin_state_up'])
- else:
- self.admin_state_up = True
-
- self.project_name = kwargs.get('project_name')
- self.mac_address = kwargs.get('mac_address')
- self.ip_addrs = kwargs.get('ip_addrs')
- self.fixed_ips = kwargs.get('fixed_ips')
- self.security_groups = kwargs.get('security_groups')
- self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
- self.opt_value = kwargs.get('opt_value')
- self.opt_name = kwargs.get('opt_name')
- self.device_owner = kwargs.get('device_owner')
- self.device_id = kwargs.get('device_id')
-
- if not self.network_name:
- raise PortSettingsError(
- 'The attribute network_name is required')
-
- def __set_fixed_ips(self, neutron):
- """
- Sets the self.fixed_ips value
- :param neutron: the Neutron client
- :return: None
- """
- if not self.fixed_ips and self.ip_addrs:
- self.fixed_ips = list()
-
- for ip_addr_dict in self.ip_addrs:
- subnet = neutron_utils.get_subnet(
- neutron, subnet_name=ip_addr_dict['subnet_name'])
- if subnet and 'ip' in ip_addr_dict:
- self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
- 'subnet_id': subnet.id})
- else:
- raise PortSettingsError(
- 'Invalid port configuration, subnet does not exist '
- 'with name - ' + ip_addr_dict['subnet_name'])
-
- def dict_for_neutron(self, neutron, os_creds):
- """
- Returns a dictionary object representing this object.
- This is meant to be converted into JSON designed for use by the Neutron
- API
-
- TODO - expand automated testing to exercise all parameters
- :param neutron: the Neutron client
- :param os_creds: the OpenStack credentials
- :return: the dictionary object
- """
- self.__set_fixed_ips(neutron)
-
- out = dict()
-
- project_id = None
- if self.project_name:
- keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(
- keystone=keystone, project_name=self.project_name)
- if project:
- project_id = project.id
-
- if not self.network:
- self.network = neutron_utils.get_network(
- neutron, network_name=self.network_name, project_id=project_id)
- if not self.network:
- raise PortSettingsError(
- 'Cannot locate network with name - ' + self.network_name)
-
- out['network_id'] = self.network.id
-
- if self.admin_state_up is not None:
- out['admin_state_up'] = self.admin_state_up
- if self.name:
- out['name'] = self.name
- if self.project_name:
- if project_id:
- out['tenant_id'] = project_id
- else:
- raise PortSettingsError(
- 'Could not find project ID for project named - ' +
- self.project_name)
- if self.mac_address:
- out['mac_address'] = self.mac_address
- if self.fixed_ips and len(self.fixed_ips) > 0:
- out['fixed_ips'] = self.fixed_ips
- if self.security_groups:
- out['security_groups'] = self.security_groups
- if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
- out['allowed_address_pairs'] = self.allowed_address_pairs
- if self.opt_value:
- out['opt_value'] = self.opt_value
- if self.opt_name:
- out['opt_name'] = self.opt_name
- if self.device_owner:
- out['device_owner'] = self.device_owner
- if self.device_id:
- out['device_id'] = self.device_id
- return {'port': out}
-
- def __eq__(self, other):
- return (self.name == other.name and
- self.network_name == other.network_name and
- self.admin_state_up == other.admin_state_up and
- self.project_name == other.project_name and
- self.mac_address == other.mac_address and
- self.ip_addrs == other.ip_addrs and
- self.fixed_ips == other.fixed_ips and
- self.security_groups == other.security_groups and
- self.allowed_address_pairs == other.allowed_address_pairs and
- self.opt_value == other.opt_value and
- self.opt_name == other.opt_name and
- self.device_owner == other.device_owner and
- self.device_id == other.device_id)
-
-
-class PortSettingsError(Exception):
- """
- Exception to be thrown when port settings attributes are incorrect
- """
+ from warnings import warn
+ warn('Use snaps.config.network.PortConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_project.py b/snaps/openstack/create_project.py
index 0cf6d4a..ed7e9cd 100644
--- a/snaps/openstack/create_project.py
+++ b/snaps/openstack/create_project.py
@@ -16,6 +16,7 @@ import logging
from keystoneclient.exceptions import NotFound, Conflict
+from snaps.config.project import ProjectConfig
from snaps.openstack.openstack_creator import OpenStackIdentityObject
from snaps.openstack.utils import keystone_utils, neutron_utils, nova_utils
@@ -73,6 +74,29 @@ class OpenStackProject(OpenStackIdentityObject):
logger.warn('Unable to associate user %s due to %s',
user.name, e)
+ if self.project_settings.quotas:
+ quota_dict = self.project_settings.quotas
+ nova = nova_utils.nova_client(self._os_creds, self._os_session)
+ quotas = nova_utils.get_compute_quotas(nova, self.__project.id)
+ if quotas:
+ if 'cores' in quota_dict:
+ quotas.cores = quota_dict['cores']
+ if 'instances' in quota_dict:
+ quotas.instances = quota_dict['instances']
+ if 'injected_files' in quota_dict:
+ quotas.injected_files = quota_dict['injected_files']
+ if 'injected_file_content_bytes' in quota_dict:
+ quotas.injected_file_content_bytes = \
+ quota_dict['injected_file_content_bytes']
+ if 'ram' in quota_dict:
+ quotas.ram = quota_dict['ram']
+ if 'fixed_ips' in quota_dict:
+ quotas.fixed_ips = quota_dict['fixed_ips']
+ if 'key_pairs' in quota_dict:
+ quotas.key_pairs = quota_dict['key_pairs']
+
+ nova_utils.update_quotas(nova, self.__project.id, quotas)
+
return self.__project
def clean(self):
@@ -82,16 +106,20 @@ class OpenStackProject(OpenStackIdentityObject):
"""
if self.__project:
# Delete security group 'default' if exists
- neutron = neutron_utils.neutron_client(self._os_creds)
- default_sec_grp = neutron_utils.get_security_group(
- neutron, sec_grp_name='default',
- project_id=self.__project.id)
- if default_sec_grp:
- try:
- neutron_utils.delete_security_group(
- neutron, default_sec_grp)
- except:
- pass
+ neutron = neutron_utils.neutron_client(
+ self._os_creds, self._os_session)
+ try:
+ default_sec_grp = neutron_utils.get_security_group(
+ neutron, self._keystone, sec_grp_name='default',
+ project_name=self.__project.name)
+ if default_sec_grp:
+ try:
+ neutron_utils.delete_security_group(
+ neutron, default_sec_grp)
+ except:
+ pass
+ finally:
+ neutron.httpclient.session.session.close()
# Delete Project
try:
@@ -113,6 +141,8 @@ class OpenStackProject(OpenStackIdentityObject):
if role:
keystone_utils.delete_role(self._keystone, role)
+ super(self.__class__, self).clean()
+
def get_project(self):
"""
Returns the OpenStack project object populated on create()
@@ -141,72 +171,59 @@ class OpenStackProject(OpenStackIdentityObject):
Returns the compute quotas as an instance of the ComputeQuotas class
:return:
"""
- nova = nova_utils.nova_client(self._os_creds)
- return nova_utils.get_compute_quotas(nova, self.__project.id)
+ nova = nova_utils.nova_client(self._os_creds, self._os_session)
+
+ try:
+ return nova_utils.get_compute_quotas(nova, self.__project.id)
+ finally:
+ nova.client.session.session.close()
def get_network_quotas(self):
"""
Returns the network quotas as an instance of the NetworkQuotas class
:return:
"""
- neutron = neutron_utils.neutron_client(self._os_creds)
- return neutron_utils.get_network_quotas(neutron, self.__project.id)
+ neutron = neutron_utils.neutron_client(
+ self._os_creds, self._os_session)
+ try:
+ return neutron_utils.get_network_quotas(neutron, self.__project.id)
+ finally:
+ neutron.httpclient.session.session.close()
def update_compute_quotas(self, compute_quotas):
"""
Updates the compute quotas for this project
:param compute_quotas: a ComputeQuotas object.
"""
- nova = nova_utils.nova_client(self._os_creds)
- nova_utils.update_quotas(nova, self.__project.id, compute_quotas)
+ nova = nova_utils.nova_client(self._os_creds, self._os_session)
+ try:
+ nova_utils.update_quotas(nova, self.__project.id, compute_quotas)
+ finally:
+ nova.client.session.session.close()
def update_network_quotas(self, network_quotas):
"""
Updates the network quotas for this project
:param network_quotas: a NetworkQuotas object.
"""
- neutron = neutron_utils.neutron_client(self._os_creds)
- neutron_utils.update_quotas(neutron, self.__project.id, network_quotas)
+ neutron = neutron_utils.neutron_client(
+ self._os_creds, self._os_session)
+ try:
+ neutron_utils.update_quotas(
+ neutron, self.__project.id, network_quotas)
+ finally:
+ neutron.httpclient.session.session.close()
-class ProjectSettings:
+class ProjectSettings(ProjectConfig):
"""
Class to hold the configuration settings required for creating OpenStack
project objects
+ deprecated
"""
def __init__(self, **kwargs):
-
- """
- Constructor
- :param name: the project's name (required)
- :param domain or domain_name: the project's domain name
- (default = 'Default').
- Field is used for v3 clients
- :param description: the description (optional)
- :param users: list of users to associat project to (optional)
- :param enabled: denotes whether or not the user is enabled
- (default True)
- """
-
- self.name = kwargs.get('name')
- self.domain_name = kwargs.get(
- 'domain', kwargs.get('domain', 'Default'))
-
- self.description = kwargs.get('description')
- if kwargs.get('enabled') is not None:
- self.enabled = kwargs['enabled']
- else:
- self.enabled = True
-
- self.users = kwargs.get('users', list())
-
- if not self.name:
- raise ProjectSettingsError(
- "The attribute name is required for ProjectSettings")
-
-
-class ProjectSettingsError(Exception):
- """
- Exception to be thrown when project settings attributes are incorrect
- """
+ from warnings import warn
+ warn('Use snaps.config.project.ProjectConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_qos.py b/snaps/openstack/create_qos.py
index ea96609..7764a57 100644
--- a/snaps/openstack/create_qos.py
+++ b/snaps/openstack/create_qos.py
@@ -18,6 +18,7 @@ import logging
import enum
from cinderclient.exceptions import NotFound
+from snaps.config.qos import QoSConfig
from snaps.openstack.openstack_creator import OpenStackVolumeObject
from snaps.openstack.utils import cinder_utils
@@ -89,6 +90,8 @@ class OpenStackQoS(OpenStackVolumeObject):
self.__qos = None
+ super(self.__class__, self).clean()
+
def get_qos(self):
"""
Returns the domain QoS object as it was populated when create() was
@@ -101,68 +104,25 @@ class OpenStackQoS(OpenStackVolumeObject):
class Consumer(enum.Enum):
"""
QoS Specification consumer types
+ deprecated - use snaps.config.qos.Consumer
"""
front_end = 'front-end'
back_end = 'back-end'
both = 'both'
-class QoSSettings:
- def __init__(self, **kwargs):
- """
- Constructor
- :param name: the qos's name (required)
- :param consumer: the qos's consumer type (required)
- :param specs: dict of key/values
- """
-
- self.name = kwargs.get('name')
-
- if kwargs.get('consumer'):
- self.consumer = map_consumer(kwargs['consumer'])
- else:
- self.consumer = None
-
- self.specs = kwargs.get('specs')
- if not self.specs:
- self.specs = dict()
-
- if not self.name or not self.consumer:
- raise QoSSettingsError(
- "The attributes name and consumer are required")
-
-
-def map_consumer(consumer):
- """
- Takes a the protocol value maps it to the Consumer enum. When None return
- None
- :param consumer: the value to map to the Enum
- :return: the Protocol enum object
- :raise: Exception if value is invalid
+class QoSSettings(QoSConfig):
"""
- if not consumer:
- return None
- elif isinstance(consumer, Consumer):
- return consumer
- else:
- proto_str = str(consumer)
- if proto_str == 'front-end':
- return Consumer.front_end
- elif proto_str == 'back-end':
- return Consumer.back_end
- elif proto_str == 'both':
- return Consumer.both
- else:
- raise QoSSettingsError('Invalid Consumer - ' + proto_str)
-
-
-class QoSSettingsError(Exception):
- """
- Exception to be thrown when an qos settings are incorrect
+ Class to hold the configuration settings required for creating OpenStack
+ QoS objects
+ deprecated
"""
- def __init__(self, message):
- Exception.__init__(self, message)
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.qos.QoSConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
class QoSCreationError(Exception):
diff --git a/snaps/openstack/create_router.py b/snaps/openstack/create_router.py
index 98e3e14..3269bbd 100644
--- a/snaps/openstack/create_router.py
+++ b/snaps/openstack/create_router.py
@@ -14,10 +14,11 @@
# limitations under the License.
import logging
-from neutronclient.common.exceptions import NotFound
-from snaps.openstack.create_network import PortSettings
+from neutronclient.common.exceptions import NotFound, Unauthorized
+
+from snaps.config.router import RouterConfig
from snaps.openstack.openstack_creator import OpenStackNetworkObject
-from snaps.openstack.utils import neutron_utils, keystone_utils
+from snaps.openstack.utils import neutron_utils
__author__ = 'spisarski'
@@ -34,7 +35,7 @@ class OpenStackRouter(OpenStackNetworkObject):
Constructor - all parameters are required
:param os_creds: The credentials to connect with OpenStack
:param router_settings: The settings used to create a router object
- (must be an instance of the RouterSettings
+ (must be an instance of the RouterConfig
class)
"""
super(self.__class__, self).__init__(os_creds)
@@ -60,23 +61,30 @@ class OpenStackRouter(OpenStackNetworkObject):
"""
super(self.__class__, self).initialize()
- self.__router = neutron_utils.get_router(
- self._neutron, router_settings=self.router_settings)
+ try:
+ self.__router = neutron_utils.get_router(
+ self._neutron, self._keystone,
+ router_settings=self.router_settings,
+ project_name=self._os_creds.project_name)
+ except Unauthorized as e:
+ logger.warn('Unable to lookup router with name %s - %s',
+ self.router_settings.name, e)
- for internal_subnet_name in self.router_settings.internal_subnets:
- internal_subnet = neutron_utils.get_subnet(
- self._neutron, subnet_name=internal_subnet_name)
- if internal_subnet:
- self.__internal_subnets.append(internal_subnet)
- else:
- raise RouterCreationError(
- 'Subnet not found with name ' + internal_subnet_name)
+ if self.__router:
+ for sub_config in self.router_settings.internal_subnets:
+ internal_subnet = self.__get_internal_subnet(sub_config)
+ if internal_subnet:
+ self.__internal_subnets.append(internal_subnet)
+ else:
+ raise RouterCreationError(
+ 'Subnet not found with name ' + internal_subnet.name)
- for port_setting in self.router_settings.port_settings:
- port = neutron_utils.get_port(
- self._neutron, port_settings=port_setting)
- if port:
- self.__ports.append(port)
+ for port_setting in self.router_settings.port_settings:
+ port = neutron_utils.get_port(
+ self._neutron, self._keystone, port_settings=port_setting,
+ project_name=self._os_creds.project_name)
+ if port:
+ self.__ports.append(port)
return self.__router
@@ -91,9 +99,8 @@ class OpenStackRouter(OpenStackNetworkObject):
self.__router = neutron_utils.create_router(
self._neutron, self._os_creds, self.router_settings)
- for internal_subnet_name in self.router_settings.internal_subnets:
- internal_subnet = neutron_utils.get_subnet(
- self._neutron, subnet_name=internal_subnet_name)
+ for sub_config in self.router_settings.internal_subnets:
+ internal_subnet = self.__get_internal_subnet(sub_config)
if internal_subnet:
self.__internal_subnets.append(internal_subnet)
if internal_subnet:
@@ -104,11 +111,12 @@ class OpenStackRouter(OpenStackNetworkObject):
self.__internal_router_interface = router_intf
else:
raise RouterCreationError(
- 'Subnet not found with name ' + internal_subnet_name)
+ 'Subnet not found with name {}'.format(sub_config))
for port_setting in self.router_settings.port_settings:
port = neutron_utils.get_port(
- self._neutron, port_settings=port_setting)
+ self._neutron, self._keystone, port_settings=port_setting,
+ project_name=self._os_creds.project_name)
logger.info(
'Retrieved port %s for router - %s', port_setting.name,
self.router_settings.name)
@@ -124,16 +132,39 @@ class OpenStackRouter(OpenStackNetworkObject):
port_setting.name,
self.router_settings.name)
self.__ports.append(port)
- neutron_utils.add_interface_router(self._neutron,
- self.__router,
- port=port)
+ neutron_utils.add_interface_router(
+ self._neutron, self.__router, port=port)
else:
raise RouterCreationError(
'Error creating port with name - '
+ port_setting.name)
+ self.__router = neutron_utils.get_router_by_id(
+ self._neutron, self.__router.id)
return self.__router
+ def __get_internal_subnet(self, sub_config):
+ """
+ returns the Subnet domain object from the subnet configurator
+ :param sub_config:
+ :return:
+ """
+ if isinstance(sub_config, dict):
+ sub_dict = sub_config['subnet']
+ network = neutron_utils.get_network(
+ self._neutron, self._keystone,
+ network_name=sub_dict['network_name'],
+ project_name=sub_dict['project_name'])
+ if network:
+ return neutron_utils.get_subnet(
+ self._neutron, network,
+ subnet_name=sub_dict['subnet_name'])
+ else:
+ return neutron_utils.get_subnet_by_name(
+ self._neutron, self._keystone,
+ subnet_name=sub_config,
+ project_name=self._os_creds.project_name)
+
def clean(self):
"""
Removes and deletes all items created in reverse order.
@@ -169,6 +200,8 @@ class OpenStackRouter(OpenStackNetworkObject):
pass
self.__router = None
+ super(self.__class__, self).clean()
+
def get_router(self):
"""
Returns the OpenStack router object
@@ -190,101 +223,15 @@ class RouterCreationError(Exception):
"""
-class RouterSettings:
+class RouterSettings(RouterConfig):
"""
- Class representing a router configuration
+ Class to hold the configuration settings required for creating OpenStack
+ router objects
+ deprecated
"""
def __init__(self, **kwargs):
- """
- Constructor - all parameters are optional
- :param name: The router name.
- :param project_name: The name of the project who owns the network. Only
- administrative users can specify a project ID
- other than their own. You cannot change this value
- through authorization policies.
- :param external_gateway: Name of the external network to which to route
- :param admin_state_up: The administrative status of the router.
- True = up / False = down (default True)
- :param external_fixed_ips: Dictionary containing the IP address
- parameters.
- :param internal_subnets: List of subnet names to which to connect this
- router for Floating IP purposes
- :param port_settings: List of PortSettings objects
- :return:
- """
- self.name = kwargs.get('name')
- self.project_name = kwargs.get('project_name')
- self.external_gateway = kwargs.get('external_gateway')
-
- self.admin_state_up = kwargs.get('admin_state_up')
- self.enable_snat = kwargs.get('enable_snat')
- self.external_fixed_ips = kwargs.get('external_fixed_ips')
- if kwargs.get('internal_subnets'):
- self.internal_subnets = kwargs['internal_subnets']
- else:
- self.internal_subnets = list()
-
- self.port_settings = list()
- if kwargs.get('interfaces', kwargs.get('port_settings')):
- interfaces = kwargs.get('interfaces', kwargs.get('port_settings'))
- for interface in interfaces:
- if isinstance(interface, PortSettings):
- self.port_settings.append(interface)
- else:
- self.port_settings.append(
- PortSettings(**interface['port']))
-
- if not self.name:
- raise RouterSettingsError('Name is required')
-
- def dict_for_neutron(self, neutron, os_creds):
- """
- Returns a dictionary object representing this object.
- This is meant to be converted into JSON designed for use by the Neutron
- API
-
- TODO - expand automated testing to exercise all parameters
- :param neutron: The neutron client to retrieve external network
- information if necessary
- :param os_creds: The OpenStack credentials
- :return: the dictionary object
- """
- out = dict()
- ext_gw = dict()
-
- if self.name:
- out['name'] = self.name
- if self.project_name:
- keystone = keystone_utils.keystone_client(os_creds)
- project = keystone_utils.get_project(
- keystone=keystone, project_name=self.project_name)
- project_id = None
- if project:
- project_id = project.id
- if project_id:
- out['tenant_id'] = project_id
- else:
- raise RouterSettingsError(
- 'Could not find project ID for project named - ' +
- self.project_name)
- if self.admin_state_up is not None:
- out['admin_state_up'] = self.admin_state_up
- if self.external_gateway:
- ext_net = neutron_utils.get_network(
- neutron, network_name=self.external_gateway)
- if ext_net:
- ext_gw['network_id'] = ext_net.id
- out['external_gateway_info'] = ext_gw
- else:
- raise RouterSettingsError(
- 'Could not find the external network named - ' +
- self.external_gateway)
-
- return {'router': out}
-
-
-class RouterSettingsError(Exception):
- """
- Exception to be thrown when router settings attributes are incorrect
- """
+ from warnings import warn
+ warn('Use snaps.config.router.RouterConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_security_group.py b/snaps/openstack/create_security_group.py
index 8218c83..490f419 100644
--- a/snaps/openstack/create_security_group.py
+++ b/snaps/openstack/create_security_group.py
@@ -17,8 +17,9 @@ import logging
import enum
from neutronclient.common.exceptions import NotFound, Conflict
+from snaps.config.security_group import (
+ SecurityGroupConfig, SecurityGroupRuleConfig)
from snaps.openstack.openstack_creator import OpenStackNetworkObject
-from snaps.openstack.utils import keystone_utils
from snaps.openstack.utils import neutron_utils
__author__ = 'spisarski'
@@ -55,7 +56,9 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
super(self.__class__, self).initialize()
self.__security_group = neutron_utils.get_security_group(
- self._neutron, sec_grp_settings=self.sec_grp_settings)
+ self._neutron, self._keystone,
+ sec_grp_settings=self.sec_grp_settings,
+ project_name=self._os_creds.project_name)
if self.__security_group:
# Populate rules
existing_rules = neutron_utils.get_rules_by_security_group(
@@ -66,6 +69,9 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
rule_setting = self.__get_setting_from_rule(existing_rule)
self.__rules[rule_setting] = existing_rule
+ self.__security_group = neutron_utils.get_security_group_by_id(
+ self._neutron, self.__security_group.id)
+
return self.__security_group
def create(self):
@@ -79,10 +85,8 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
logger.info(
'Creating security group %s...' % self.sec_grp_settings.name)
- keystone = keystone_utils.keystone_client(self._os_creds)
self.__security_group = neutron_utils.create_security_group(
- self._neutron, keystone,
- self.sec_grp_settings)
+ self._neutron, self._keystone, self.sec_grp_settings)
# Get the rules added for free
auto_rules = neutron_utils.get_rules_by_security_group(
@@ -98,29 +102,30 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
for sec_grp_rule_setting in self.sec_grp_settings.rule_settings:
try:
custom_rule = neutron_utils.create_security_group_rule(
- self._neutron, sec_grp_rule_setting)
+ self._neutron, self._keystone, sec_grp_rule_setting,
+ self._os_creds.project_name)
self.__rules[sec_grp_rule_setting] = custom_rule
except Conflict as e:
logger.warn('Unable to create rule due to conflict - %s',
e)
# Refresh security group object to reflect the new rules added
- self.__security_group = neutron_utils.get_security_group(
- self._neutron, sec_grp_settings=self.sec_grp_settings)
+ self.__security_group = neutron_utils.get_security_group_by_id(
+ self._neutron, self.__security_group.id)
return self.__security_group
def __generate_rule_setting(self, rule):
"""
- Creates a SecurityGroupRuleSettings object for a given rule
+ Creates a SecurityGroupRuleConfig object for a given rule
:param rule: the rule from which to create the
- SecurityGroupRuleSettings object
- :return: the newly instantiated SecurityGroupRuleSettings object
+ SecurityGroupRuleConfig object
+ :return: the newly instantiated SecurityGroupRuleConfig object
"""
sec_grp = neutron_utils.get_security_group_by_id(
self._neutron, rule.security_group_id)
- setting = SecurityGroupRuleSettings(
+ setting = SecurityGroupRuleConfig(
description=rule.description,
direction=rule.direction,
ethertype=rule.ethertype,
@@ -154,6 +159,8 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
self.__security_group = None
+ super(self.__class__, self).clean()
+
def get_security_group(self):
"""
Returns the OpenStack security group object
@@ -174,8 +181,9 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
:param rule_setting: the rule configuration
"""
rule_setting.sec_grp_name = self.sec_grp_settings.name
- new_rule = neutron_utils.create_security_group_rule(self._neutron,
- rule_setting)
+ new_rule = neutron_utils.create_security_group_rule(
+ self._neutron, self._keystone, rule_setting,
+ self._os_creds.project_name)
self.__rules[rule_setting] = new_rule
self.sec_grp_settings.rule_settings.append(rule_setting)
@@ -215,82 +223,24 @@ class OpenStackSecurityGroup(OpenStackNetworkObject):
return None
-class SecurityGroupSettings:
+class SecurityGroupSettings(SecurityGroupConfig):
"""
- Class representing a keypair configuration
+ Class to hold the configuration settings required for creating OpenStack
+ SecurityGroup objects
+ deprecated - use snaps.config.security_group.SecurityGroupConfig instead
"""
def __init__(self, **kwargs):
- """
- Constructor - all parameters are optional
- :param name: The keypair name.
- :param description: The security group's description
- :param project_name: The name of the project under which the security
- group will be created
- :return:
- """
- self.name = kwargs.get('name')
- self.description = kwargs.get('description')
- self.project_name = kwargs.get('project_name')
- self.rule_settings = list()
-
- rule_settings = kwargs.get('rules')
- if not rule_settings:
- rule_settings = kwargs.get('rule_settings')
-
- if rule_settings:
- for rule_setting in rule_settings:
- if isinstance(rule_setting, SecurityGroupRuleSettings):
- self.rule_settings.append(rule_setting)
- else:
- rule_setting['sec_grp_name'] = self.name
- self.rule_settings.append(SecurityGroupRuleSettings(
- **rule_setting))
-
- if not self.name:
- raise SecurityGroupSettingsError('The attribute name is required')
-
- for rule_setting in self.rule_settings:
- if rule_setting.sec_grp_name is not self.name:
- raise SecurityGroupSettingsError(
- 'Rule settings must correspond with the name of this '
- 'security group')
-
- def dict_for_neutron(self, keystone):
- """
- Returns a dictionary object representing this object.
- This is meant to be converted into JSON designed for use by the Neutron
- API
-
- TODO - expand automated testing to exercise all parameters
- :param keystone: the Keystone client
- :return: the dictionary object
- """
- out = dict()
-
- if self.name:
- out['name'] = self.name
- if self.description:
- out['description'] = self.description
- if self.project_name:
- project = keystone_utils.get_project(
- keystone=keystone, project_name=self.project_name)
- project_id = None
- if project:
- project_id = project.id
- if project_id:
- out['tenant_id'] = project_id
- else:
- raise SecurityGroupSettingsError(
- 'Could not find project ID for project named - ' +
- self.project_name)
-
- return {'security_group': out}
+ from warnings import warn
+ warn('Use snaps.config.security_group.SecurityGroupConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
class Direction(enum.Enum):
"""
A rule's direction
+ deprecated - use snaps.config.security_group.Direction
"""
ingress = 'ingress'
egress = 'egress'
@@ -299,270 +249,53 @@ class Direction(enum.Enum):
class Protocol(enum.Enum):
"""
A rule's protocol
+ deprecated - use snaps.config.security_group.Protocol
"""
- icmp = 'icmp'
- tcp = 'tcp'
- udp = 'udp'
+ ah = 51
+ dccp = 33
+ egp = 8
+ esp = 50
+ gre = 47
+ icmp = 1
+ icmpv6 = 58
+ igmp = 2
+ ipv6_encap = 41
+ ipv6_frag = 44
+ ipv6_icmp = 58
+ ipv6_nonxt = 59
+ ipv6_opts = 60
+ ipv6_route = 43
+ ospf = 89
+ pgm = 113
+ rsvp = 46
+ sctp = 132
+ tcp = 6
+ udp = 17
+ udplite = 136
+ vrrp = 112
+ any = 'any'
null = 'null'
class Ethertype(enum.Enum):
"""
A rule's ethertype
+ deprecated - use snaps.config.security_group.Ethertype
"""
IPv4 = 4
IPv6 = 6
-class SecurityGroupSettingsError(Exception):
+class SecurityGroupRuleSettings(SecurityGroupRuleConfig):
"""
- Exception to be thrown when security group settings attributes are
- invalid
- """
-
-
-class SecurityGroupRuleSettings:
- """
- Class representing a keypair configuration
+ Class to hold the configuration settings required for creating OpenStack
+ SecurityGroupRule objects
+ deprecated - use snaps.config.security_group.SecurityGroupRuleConfig
+ instead
"""
def __init__(self, **kwargs):
- """
- Constructor - all parameters are optional
- :param sec_grp_name: The security group's name on which to add the
- rule. (required)
- :param description: The rule's description
- :param direction: An enumeration of type
- create_security_group.RULE_DIRECTION (required)
- :param remote_group_id: The group ID to associate with this rule
- (this should be changed to group name once
- snaps support Groups) (optional)
- :param protocol: An enumeration of type
- create_security_group.RULE_PROTOCOL or a string value
- that will be mapped accordingly (optional)
- :param ethertype: An enumeration of type
- create_security_group.RULE_ETHERTYPE (optional)
- :param port_range_min: The minimum port number in the range that is
- matched by the security group rule. When the
- protocol is TCP or UDP, this value must be <=
- port_range_max. When the protocol is ICMP, this
- value must be an ICMP type.
- :param port_range_max: The maximum port number in the range that is
- matched by the security group rule. When the
- protocol is TCP or UDP, this value must be <=
- port_range_max. When the protocol is ICMP, this
- value must be an ICMP type.
- :param sec_grp_rule: The OpenStack rule object to a security group rule
- object to associate
- (note: Cannot be set using the config object nor
- can I see any real uses for this parameter)
- :param remote_ip_prefix: The remote IP prefix to associate with this
- metering rule packet (optional)
-
- TODO - Need to support the tenant...
- """
-
- self.description = kwargs.get('description')
- self.sec_grp_name = kwargs.get('sec_grp_name')
- self.remote_group_id = kwargs.get('remote_group_id')
- self.direction = None
- if kwargs.get('direction'):
- self.direction = map_direction(kwargs['direction'])
-
- self.protocol = None
- if kwargs.get('protocol'):
- self.protocol = map_protocol(kwargs['protocol'])
- else:
- self.protocol = Protocol.null
-
- self.ethertype = None
- if kwargs.get('ethertype'):
- self.ethertype = map_ethertype(kwargs['ethertype'])
-
- self.port_range_min = kwargs.get('port_range_min')
- self.port_range_max = kwargs.get('port_range_max')
- self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
-
- if not self.direction or not self.sec_grp_name:
- raise SecurityGroupRuleSettingsError(
- 'direction and sec_grp_name are required')
-
- def dict_for_neutron(self, neutron):
- """
- Returns a dictionary object representing this object.
- This is meant to be converted into JSON designed for use by the Neutron
- API
-
- :param neutron: the neutron client for performing lookups
- :return: the dictionary object
- """
- out = dict()
-
- if self.description:
- out['description'] = self.description
- if self.direction:
- out['direction'] = self.direction.name
- if self.port_range_min:
- out['port_range_min'] = self.port_range_min
- if self.port_range_max:
- out['port_range_max'] = self.port_range_max
- if self.ethertype:
- out['ethertype'] = self.ethertype.name
- if self.protocol and self.protocol.name != 'null':
- out['protocol'] = self.protocol.name
- if self.sec_grp_name:
- sec_grp = neutron_utils.get_security_group(
- neutron, sec_grp_name=self.sec_grp_name)
- if sec_grp:
- out['security_group_id'] = sec_grp.id
- else:
- raise SecurityGroupRuleSettingsError(
- 'Cannot locate security group with name - ' +
- self.sec_grp_name)
- if self.remote_group_id:
- out['remote_group_id'] = self.remote_group_id
- if self.remote_ip_prefix:
- out['remote_ip_prefix'] = self.remote_ip_prefix
-
- return {'security_group_rule': out}
-
- def rule_eq(self, rule):
- """
- Returns True if this setting created the rule
- :param rule: the rule to evaluate
- :return: T/F
- """
- if self.description is not None:
- if (rule.description is not None and
- rule.description != ''):
- return False
- elif self.description != rule.description:
- if rule.description != '':
- return False
-
- if self.direction.name != rule.direction:
- return False
-
- if self.ethertype and rule.ethertype:
- if self.ethertype.name != rule.ethertype:
- return False
-
- if self.port_range_min and rule.port_range_min:
- if self.port_range_min != rule.port_range_min:
- return False
-
- if self.port_range_max and rule.port_range_max:
- if self.port_range_max != rule.port_range_max:
- return False
-
- if self.protocol and rule.protocol:
- if self.protocol.name != rule.protocol:
- return False
-
- if self.remote_group_id and rule.remote_group_id:
- if self.remote_group_id != rule.remote_group_id:
- return False
-
- if self.remote_ip_prefix and rule.remote_ip_prefix:
- if self.remote_ip_prefix != rule.remote_ip_prefix:
- return False
-
- return True
-
- def __eq__(self, other):
- return (
- self.description == other.description and
- self.direction == other.direction and
- self.port_range_min == other.port_range_min and
- self.port_range_max == other.port_range_max and
- self.ethertype == other.ethertype and
- self.protocol == other.protocol and
- self.sec_grp_name == other.sec_grp_name and
- self.remote_group_id == other.remote_group_id and
- self.remote_ip_prefix == other.remote_ip_prefix)
-
- def __hash__(self):
- return hash((self.sec_grp_name, self.description, self.direction,
- self.remote_group_id,
- self.protocol, self.ethertype, self.port_range_min,
- self.port_range_max, self.remote_ip_prefix))
-
-
-def map_direction(direction):
- """
- Takes a the direction value maps it to the Direction enum. When None return
- None
- :param direction: the direction value
- :return: the Direction enum object
- :raise: Exception if value is invalid
- """
- if not direction:
- return None
- if isinstance(direction, Direction):
- return direction
- else:
- dir_str = str(direction)
- if dir_str == 'egress':
- return Direction.egress
- elif dir_str == 'ingress':
- return Direction.ingress
- else:
- raise SecurityGroupRuleSettingsError(
- 'Invalid Direction - ' + dir_str)
-
-
-def map_protocol(protocol):
- """
- Takes a the protocol value maps it to the Protocol enum. When None return
- None
- :param protocol: the protocol value
- :return: the Protocol enum object
- :raise: Exception if value is invalid
- """
- if not protocol:
- return None
- elif isinstance(protocol, Protocol):
- return protocol
- else:
- proto_str = str(protocol)
- if proto_str == 'icmp':
- return Protocol.icmp
- elif proto_str == 'tcp':
- return Protocol.tcp
- elif proto_str == 'udp':
- return Protocol.udp
- elif proto_str == 'null':
- return Protocol.null
- else:
- raise SecurityGroupRuleSettingsError(
- 'Invalid Protocol - ' + proto_str)
-
-
-def map_ethertype(ethertype):
- """
- Takes a the ethertype value maps it to the Ethertype enum. When None return
- None
- :param ethertype: the ethertype value
- :return: the Ethertype enum object
- :raise: Exception if value is invalid
- """
- if not ethertype:
- return None
- elif isinstance(ethertype, Ethertype):
- return ethertype
- else:
- eth_str = str(ethertype)
- if eth_str == 'IPv6':
- return Ethertype.IPv6
- elif eth_str == 'IPv4':
- return Ethertype.IPv4
- else:
- raise SecurityGroupRuleSettingsError(
- 'Invalid Ethertype - ' + eth_str)
-
-
-class SecurityGroupRuleSettingsError(Exception):
- """
- Exception to be thrown when security group rule settings attributes are
- invalid
- """
+ from warnings import warn
+ warn('Use snaps.config.security_group.SecurityGroupRuleConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_stack.py b/snaps/openstack/create_stack.py
index c161973..71e5d0a 100644
--- a/snaps/openstack/create_stack.py
+++ b/snaps/openstack/create_stack.py
@@ -18,25 +18,27 @@ import time
from heatclient.exc import HTTPNotFound
+import snaps
+from snaps.config.stack import StackConfig
+from snaps.openstack.create_flavor import OpenStackFlavor
from snaps.openstack.create_instance import OpenStackVmInstance
-from snaps.openstack.openstack_creator import OpenStackCloudObject
-from snaps.openstack.utils import nova_utils, settings_utils, glance_utils
-
+from snaps.openstack.create_keypairs import OpenStackKeypair
from snaps.openstack.create_network import OpenStackNetwork
+from snaps.openstack.create_router import OpenStackRouter
+from snaps.openstack.create_security_group import OpenStackSecurityGroup
+from snaps.openstack.create_volume import OpenStackVolume
+from snaps.openstack.create_volume_type import OpenStackVolumeType
+from snaps.openstack.openstack_creator import OpenStackCloudObject
+from snaps.openstack.utils import (
+ nova_utils, settings_utils, glance_utils, cinder_utils)
from snaps.openstack.utils import heat_utils, neutron_utils
+from snaps.thread_utils import worker_pool
+
__author__ = 'spisarski'
logger = logging.getLogger('create_stack')
-STACK_DELETE_TIMEOUT = 1200
-STACK_COMPLETE_TIMEOUT = 1200
-POLL_INTERVAL = 3
-STATUS_CREATE_FAILED = 'CREATE_FAILED'
-STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE'
-STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE'
-STATUS_DELETE_FAILED = 'DELETE_FAILED'
-
class OpenStackHeatStack(OpenStackCloudObject, object):
"""
@@ -49,11 +51,9 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
Constructor
:param os_creds: The OpenStack connection credentials
:param stack_settings: The stack settings
- :param image_settings: A list of ImageSettings objects that were used
- for spawning this stack
- :param image_settings: A list of ImageSettings objects that were used
+ :param image_settings: A list of ImageConfig objects that were used
for spawning this stack
- :param keypair_settings: A list of KeypairSettings objects that were
+ :param keypair_settings: A list of KeypairConfig objects that were
used for spawning this stack
:return:
"""
@@ -74,19 +74,35 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
self.__stack = None
self.__heat_cli = None
+ self.__neutron = None
+ self.__nova = None
+ self.__glance = None
+ self.__cinder = None
+
def initialize(self):
"""
Loads the existing heat stack
:return: The Stack domain object or None
"""
- self.__heat_cli = heat_utils.heat_client(self._os_creds)
+ super(self.__class__, self).initialize()
+
+ self.__neutron = neutron_utils.neutron_client(
+ self._os_creds, self._os_session)
+ self.__nova = nova_utils.nova_client(self._os_creds, self._os_session)
+ self.__glance = glance_utils.glance_client(
+ self._os_creds, self._os_session)
+ self.__cinder = cinder_utils.cinder_client(
+ self._os_creds, self._os_session)
+
+ self.__heat_cli = heat_utils.heat_client(
+ self._os_creds, self._os_session)
self.__stack = heat_utils.get_stack(
self.__heat_cli, stack_settings=self.stack_settings)
if self.__stack:
logger.info('Found stack with name - ' + self.stack_settings.name)
return self.__stack
- def create(self):
+ def create(self, block=False):
"""
Creates the heat stack in OpenStack if it does not already exist and
returns the domain Stack object
@@ -95,25 +111,44 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
self.initialize()
if self.__stack:
- logger.info('Found stack with name - ' + self.stack_settings.name)
+ logger.info('Found stack with name - %s', self.stack_settings.name)
return self.__stack
else:
self.__stack = heat_utils.create_stack(self.__heat_cli,
self.stack_settings)
logger.info(
- 'Created stack with name - ' + self.stack_settings.name)
- if self.__stack and self.stack_complete(block=True):
- logger.info(
- 'Stack is now active with name - ' +
- self.stack_settings.name)
+ 'Created stack with name - %s', self.stack_settings.name)
+ if self.__stack and self.stack_complete(block=block):
+ logger.info('Stack is now active with name - %s',
+ self.stack_settings.name)
return self.__stack
else:
status = heat_utils.get_stack_status_reason(self.__heat_cli,
self.__stack.id)
- logger.error(
- 'ERROR: STACK CREATION FAILED: ' + status)
- raise StackCreationError(
- 'Failure while creating stack')
+ logger.error('ERROR: STACK CREATION FAILED: %s', status)
+ raise StackCreationError('Failure while creating stack')
+
+ def update(self, env_vals, block=False):
+ """
+ Updates the heat stack in OpenStack
+ :param: env_vals - the values to update
+ :return: The Stack domain object or None
+ """
+ if self.__stack:
+ logger.info('Updating stack - %s', self.__stack.name)
+ heat_utils.update_stack(self.__heat_cli, self.__stack, env_vals)
+ if self.stack_updated(block=block):
+ logger.info('Stack %s is now updated with params: %s',
+ self.__stack.name, env_vals)
+ self.stack_settings.env_values = env_vals
+ self.__stack = heat_utils.get_stack_by_id(
+ self.__heat_cli, self.__stack.id)
+ return self.__stack
+ else:
+ status = heat_utils.get_stack_status_reason(self.__heat_cli,
+ self.__stack.id)
+ logger.error('ERROR: STACK UPDATE FAILED: %s', status)
+ raise StackUpdateError('Failure while updating stack')
def clean(self):
"""
@@ -122,7 +157,7 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
"""
if self.__stack:
try:
- logger.info('Deleting stack - %s' + self.__stack.name)
+ logger.info('Deleting stack - %s', self.__stack.name)
heat_utils.delete_stack(self.__heat_cli, self.__stack)
try:
@@ -156,13 +191,21 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
self.__stack = None
+ self.__neutron.httpclient.session.session.close()
+ self.__nova.client.session.session.close()
+ self.__glance.http_client.session.session.close()
+ self.__cinder.client.session.session.close()
+
+ super(self.__class__, self).clean()
+
def get_stack(self):
"""
Returns the domain Stack object as it was populated when create() was
called
:return: the object
"""
- return self.__stack
+ if self.__stack:
+ return heat_utils.get_stack_by_id(self.__heat_cli, self.__stack.id)
def get_outputs(self):
"""
@@ -178,10 +221,12 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
object
:return:
"""
- return heat_utils.get_stack_status(self.__heat_cli, self.__stack.id)
+ stack = self.get_stack()
+ if stack:
+ return stack.status
def stack_complete(self, block=False, timeout=None,
- poll_interval=POLL_INTERVAL):
+ poll_interval=snaps.config.stack.POLL_INTERVAL):
"""
Returns true when the stack status returns the value of
expected_status_code
@@ -193,11 +238,13 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
"""
if not timeout:
timeout = self.stack_settings.stack_create_timeout
- return self._stack_status_check(STATUS_CREATE_COMPLETE, block, timeout,
- poll_interval, STATUS_CREATE_FAILED)
+ return self._stack_status_check(
+ snaps.config.stack.STATUS_CREATE_COMPLETE, block, timeout,
+ poll_interval, snaps.config.stack.STATUS_CREATE_FAILED)
- def stack_deleted(self, block=False, timeout=STACK_DELETE_TIMEOUT,
- poll_interval=POLL_INTERVAL):
+ def stack_updated(self, block=False,
+ timeout=snaps.config.stack.STACK_UPDATE_TIMEOUT,
+ poll_interval=snaps.config.stack.POLL_INTERVAL):
"""
Returns true when the stack status returns the value of
expected_status_code
@@ -207,8 +254,25 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
:param poll_interval: The polling interval in seconds
:return: T/F
"""
- return self._stack_status_check(STATUS_DELETE_COMPLETE, block, timeout,
- poll_interval, STATUS_DELETE_FAILED)
+ return self._stack_status_check(
+ snaps.config.stack.STATUS_UPDATE_COMPLETE, block, timeout,
+ poll_interval, snaps.config.stack.STATUS_UPDATE_FAILED)
+
+ def stack_deleted(self, block=False,
+ timeout=snaps.config.stack.STACK_DELETE_TIMEOUT,
+ poll_interval=snaps.config.stack.POLL_INTERVAL):
+ """
+ Returns true when the stack status returns the value of
+ expected_status_code
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param timeout: The timeout value
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ return self._stack_status_check(
+ snaps.config.stack.STATUS_DELETE_COMPLETE, block, timeout,
+ poll_interval, snaps.config.stack.STATUS_DELETE_FAILED)
def get_network_creators(self):
"""
@@ -217,21 +281,76 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
:return: list() of OpenStackNetwork objects
"""
- neutron = neutron_utils.neutron_client(self._os_creds)
-
out = list()
stack_networks = heat_utils.get_stack_networks(
- self.__heat_cli, neutron, self.__stack)
+ self.__heat_cli, self.__neutron, self.__stack)
for stack_network in stack_networks:
- net_settings = settings_utils.create_network_settings(
- neutron, stack_network)
+ net_settings = settings_utils.create_network_config(
+ self.__neutron, stack_network)
net_creator = OpenStackNetwork(self._os_creds, net_settings)
out.append(net_creator)
net_creator.initialize()
return out
+ def get_security_group_creators(self):
+ """
+ Returns a list of security group creator objects as configured by the
+ heat template
+ :return: list() of OpenStackNetwork objects
+ """
+
+ out = list()
+ stack_security_groups = heat_utils.get_stack_security_groups(
+ self.__heat_cli, self.__neutron, self.__stack)
+
+ for stack_security_group in stack_security_groups:
+ settings = settings_utils.create_security_group_config(
+ self.__neutron, stack_security_group)
+ creator = OpenStackSecurityGroup(self._os_creds, settings)
+ out.append(creator)
+ creator.initialize()
+
+ return out
+
+ def get_router_creators(self):
+ """
+ Returns a list of router creator objects as configured by the heat
+ template
+ :return: list() of OpenStackRouter objects
+ """
+
+ out = list()
+ stack_routers = heat_utils.get_stack_routers(
+ self.__heat_cli, self.__neutron, self.__stack)
+
+ for routers in stack_routers:
+ settings = settings_utils.create_router_config(
+ self.__neutron, routers)
+ creator = OpenStackRouter(self._os_creds, settings)
+ out.append(creator)
+ creator.initialize()
+
+ return out
+
+ def __create_vm_inst(self, heat_keypair_option, stack_server):
+
+ vm_inst_settings = settings_utils.create_vm_inst_config(
+ self.__nova, self._keystone, self.__neutron, stack_server,
+ self._os_creds.project_name)
+ image_settings = settings_utils.determine_image_config(
+ self.__glance, stack_server, self.image_settings)
+ keypair_settings = settings_utils.determine_keypair_config(
+ self.__heat_cli, self.__stack, stack_server,
+ keypair_settings=self.keypair_settings,
+ priv_key_key=heat_keypair_option)
+ vm_inst_creator = OpenStackVmInstance(
+ self._os_creds, vm_inst_settings, image_settings,
+ keypair_settings)
+ vm_inst_creator.initialize()
+ return vm_inst_creator
+
def get_vm_inst_creators(self, heat_keypair_option=None):
"""
Returns a list of VM Instance creator objects as configured by the heat
@@ -240,28 +359,122 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
"""
out = list()
- nova = nova_utils.nova_client(self._os_creds)
stack_servers = heat_utils.get_stack_servers(
- self.__heat_cli, nova, self.__stack)
-
- neutron = neutron_utils.neutron_client(self._os_creds)
- glance = glance_utils.glance_client(self._os_creds)
+ self.__heat_cli, self.__nova, self.__neutron, self._keystone,
+ self.__stack, self._os_creds.project_name)
+ workers = []
for stack_server in stack_servers:
- vm_inst_settings = settings_utils.create_vm_inst_settings(
- nova, neutron, stack_server)
- image_settings = settings_utils.determine_image_settings(
- glance, stack_server, self.image_settings)
- keypair_settings = settings_utils.determine_keypair_settings(
- self.__heat_cli, self.__stack, stack_server,
- keypair_settings=self.keypair_settings,
- priv_key_key=heat_keypair_option)
- vm_inst_creator = OpenStackVmInstance(
- self._os_creds, vm_inst_settings, image_settings,
- keypair_settings)
- out.append(vm_inst_creator)
- vm_inst_creator.initialize()
+ worker = worker_pool().apply_async(
+ self.__create_vm_inst,
+ (heat_keypair_option,
+ stack_server))
+ workers.append(worker)
+
+ for worker in workers:
+ out.append(worker.get())
+
+ return out
+
+ def get_volume_creators(self):
+ """
+ Returns a list of Volume creator objects as configured by the heat
+ template
+ :return: list() of OpenStackVolume objects
+ """
+
+ out = list()
+ volumes = heat_utils.get_stack_volumes(
+ self.__heat_cli, self.__cinder, self.__stack)
+
+ for volume in volumes:
+ settings = settings_utils.create_volume_config(volume)
+ creator = OpenStackVolume(self._os_creds, settings)
+ out.append(creator)
+
+ try:
+ creator.initialize()
+ except Exception as e:
+ logger.error(
+ 'Unexpected error initializing volume creator - %s', e)
+
+ return out
+
+ def get_volume_type_creators(self):
+ """
+ Returns a list of VolumeType creator objects as configured by the heat
+ template
+ :return: list() of OpenStackVolumeType objects
+ """
+
+ out = list()
+ vol_types = heat_utils.get_stack_volume_types(
+ self.__heat_cli, self.__cinder, self.__stack)
+
+ for volume in vol_types:
+ settings = settings_utils.create_volume_type_config(volume)
+ creator = OpenStackVolumeType(self._os_creds, settings)
+ out.append(creator)
+
+ try:
+ creator.initialize()
+ except Exception as e:
+ logger.error(
+ 'Unexpected error initializing volume type creator - %s',
+ e)
+
+ return out
+
+ def get_keypair_creators(self, outputs_pk_key=None):
+ """
+ Returns a list of keypair creator objects as configured by the heat
+ template
+ :return: list() of OpenStackKeypair objects
+ """
+
+ out = list()
+
+ keypairs = heat_utils.get_stack_keypairs(
+ self.__heat_cli, self.__nova, self.__stack)
+
+ for keypair in keypairs:
+ settings = settings_utils.create_keypair_config(
+ self.__heat_cli, self.__stack, keypair, outputs_pk_key)
+ creator = OpenStackKeypair(self._os_creds, settings)
+ out.append(creator)
+
+ try:
+ creator.initialize()
+ except Exception as e:
+ logger.error(
+ 'Unexpected error initializing volume type creator - %s',
+ e)
+
+ return out
+
+ def get_flavor_creators(self):
+ """
+ Returns a list of Flavor creator objects as configured by the heat
+ template
+ :return: list() of OpenStackFlavor objects
+ """
+
+ out = list()
+
+ flavors = heat_utils.get_stack_flavors(
+ self.__heat_cli, self.__nova, self.__stack)
+
+ for flavor in flavors:
+ settings = settings_utils.create_flavor_config(flavor)
+ creator = OpenStackFlavor(self._os_creds, settings)
+ out.append(creator)
+
+ try:
+ creator.initialize()
+ except Exception as e:
+ logger.error(
+ 'Unexpected error initializing volume creator - %s', e)
return out
@@ -302,7 +515,8 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
'Timeout checking for stack status for ' + expected_status_code)
return False
- def _status(self, expected_status_code, fail_status=STATUS_CREATE_FAILED):
+ def _status(self, expected_status_code,
+ fail_status=snaps.config.stack.STATUS_CREATE_FAILED):
"""
Returns True when active else False
:param expected_status_code: stack status evaluated with this string
@@ -316,58 +530,65 @@ class OpenStackHeatStack(OpenStackCloudObject, object):
return False
if fail_status and status == fail_status:
+ resources = heat_utils.get_resources(
+ self.__heat_cli, self.__stack.id)
+ logger.error('Stack %s failed', self.__stack.name)
+ for resource in resources:
+ if (resource.status !=
+ snaps.config.stack.STATUS_CREATE_COMPLETE):
+ logger.error(
+ 'Resource: [%s] status: [%s] reason: [%s]',
+ resource.name, resource.status, resource.status_reason)
+ else:
+ logger.debug(
+ 'Resource: [%s] status: [%s] reason: [%s]',
+ resource.name, resource.status, resource.status_reason)
+
raise StackError('Stack had an error')
logger.debug('Stack status is - ' + status)
return status == expected_status_code
-class StackSettings:
- def __init__(self, **kwargs):
- """
- Constructor
- :param name: the stack's name (required)
- :param template: the heat template in dict() format (required if
- template_path attribute is None)
- :param template_path: the location of the heat template file (required
- if template attribute is None)
- :param env_values: k/v pairs of strings for substitution of template
- default values (optional)
- """
-
- self.name = kwargs.get('name')
- self.template = kwargs.get('template')
- self.template_path = kwargs.get('template_path')
- self.env_values = kwargs.get('env_values')
- if 'stack_create_timeout' in kwargs:
- self.stack_create_timeout = kwargs['stack_create_timeout']
- else:
- self.stack_create_timeout = STACK_COMPLETE_TIMEOUT
-
- if not self.name:
- raise StackSettingsError('name is required')
-
- if not self.template and not self.template_path:
- raise StackSettingsError('A Heat template is required')
+def generate_creator(os_creds, stack_inst, image_settings):
+ """
+ Initializes an OpenStackHeatStack object
+ :param os_creds: the OpenStack credentials
+ :param stack_inst: the SNAPS-OO VmInst domain object
+ :param image_settings: list of SNAPS-OO ImageConfig objects
+ :return: an initialized OpenStackHeatStack object
+ """
- def __eq__(self, other):
- return (self.name == other.name and
- self.template == other.template and
- self.template_path == other.template_path and
- self.env_values == other.env_values and
- self.stack_create_timeout == other.stack_create_timeout)
+ heat_config = StackConfig(
+ name=stack_inst.name, template={'place': 'holder'})
+ heat_creator = OpenStackHeatStack(os_creds, heat_config, image_settings)
+ heat_creator.initialize()
+ return heat_creator
-class StackSettingsError(Exception):
+class StackSettings(StackConfig):
"""
- Exception to be thrown when an stack settings are incorrect
+ Class to hold the configuration settings required for creating OpenStack
+ stack objects
+ deprecated
"""
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.stack.StackConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
+
class StackCreationError(Exception):
"""
Exception to be thrown when an stack cannot be created
"""
+class StackUpdateError(Exception):
+ """
+ Exception to be thrown when an stack update failed
+ """
+
class StackError(Exception):
"""
diff --git a/snaps/openstack/create_user.py b/snaps/openstack/create_user.py
index bcf4790..b187f4e 100644
--- a/snaps/openstack/create_user.py
+++ b/snaps/openstack/create_user.py
@@ -16,6 +16,7 @@ import logging
from keystoneclient.exceptions import NotFound
+from snaps.config.user import UserConfig
from snaps.openstack.openstack_creator import OpenStackIdentityObject
from snaps.openstack.os_credentials import OSCreds
from snaps.openstack.utils import keystone_utils
@@ -76,6 +77,8 @@ class OpenStackUser(OpenStackIdentityObject):
pass
self.__user = None
+ super(self.__class__, self).clean()
+
def get_user(self):
"""
Returns the OpenStack user object populated in create()
@@ -90,12 +93,21 @@ class OpenStackUser(OpenStackIdentityObject):
credentials
:return:
"""
+ if not project_name:
+ project_name = self._os_creds.project_name
+
return OSCreds(
username=self.user_settings.name,
password=self.user_settings.password,
auth_url=self._os_creds.auth_url,
project_name=project_name,
identity_api_version=self._os_creds.identity_api_version,
+ image_api_version=self._os_creds.image_api_version,
+ network_api_version=self._os_creds.network_api_version,
+ compute_api_version=self._os_creds.compute_api_version,
+ heat_api_version=self._os_creds.heat_api_version,
+ volume_api_version=self._os_creds.volume_api_version,
+ region_name=self._os_creds.region_name,
user_domain_name=self._os_creds.user_domain_name,
user_domain_id=self._os_creds.user_domain_id,
project_domain_name=self._os_creds.project_domain_name,
@@ -105,43 +117,15 @@ class OpenStackUser(OpenStackIdentityObject):
cacert=self._os_creds.cacert)
-class UserSettings:
- def __init__(self, **kwargs):
-
- """
- Constructor
- :param name: the user's name (required)
- :param password: the user's password (required)
- :param project_name: the user's primary project name (optional)
- :param domain_name: the user's domain name (default='default'). For v3
- APIs
- :param email: the user's email address (optional)
- :param enabled: denotes whether or not the user is enabled
- (default True)
- :param roles: dict where key is the role's name and value is the name
- the project to associate with the role
- """
-
- self.name = kwargs.get('name')
- self.password = kwargs.get('password')
- self.project_name = kwargs.get('project_name')
- self.email = kwargs.get('email')
- self.domain_name = kwargs.get('domain_name', 'Default')
- self.enabled = kwargs.get('enabled', True)
- self.roles = kwargs.get('roles', dict())
-
- if not self.name or not self.password:
- raise UserSettingsException(
- 'The attributes name and password are required for '
- 'UserSettings')
-
- if not isinstance(self.enabled, bool):
- raise UserSettingsException('The attribute enabled must be of type'
- ' boolean')
-
-
-class UserSettingsException(Exception):
+class UserSettings(UserConfig):
"""
- Raised when there is a problem with the values set in the UserSettings
- class
+ Class to hold the configuration settings required for creating OpenStack
+ user objects
+ deprecated
"""
+
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.user.UserConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
diff --git a/snaps/openstack/create_volume.py b/snaps/openstack/create_volume.py
new file mode 100644
index 0000000..b35cd89
--- /dev/null
+++ b/snaps/openstack/create_volume.py
@@ -0,0 +1,256 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+import logging
+import time
+
+from cinderclient.exceptions import NotFound
+
+from snaps.config.volume import VolumeConfig
+from snaps.openstack.openstack_creator import OpenStackVolumeObject
+from snaps.openstack.utils import cinder_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('create_volume')
+
+VOLUME_ACTIVE_TIMEOUT = 300
+VOLUME_DELETE_TIMEOUT = 60
+POLL_INTERVAL = 3
+STATUS_ACTIVE = 'available'
+STATUS_IN_USE = 'in-use'
+STATUS_FAILED = 'error'
+STATUS_DELETED = 'deleted'
+
+
+class OpenStackVolume(OpenStackVolumeObject):
+ """
+ Class responsible for managing an volume in OpenStack
+ """
+
+ def __init__(self, os_creds, volume_settings):
+ """
+ Constructor
+ :param os_creds: The OpenStack connection credentials
+ :param volume_settings: The volume settings
+ :return:
+ """
+ super(self.__class__, self).__init__(os_creds)
+
+ self.volume_settings = volume_settings
+ self.__volume = None
+
+ def initialize(self):
+ """
+ Loads the existing Volume
+ :return: The Volume domain object or None
+ """
+ super(self.__class__, self).initialize()
+
+ self.__volume = cinder_utils.get_volume(
+ self._cinder, self._keystone,
+ volume_settings=self.volume_settings,
+ project_name=self._os_creds.project_name)
+ return self.__volume
+
+ def create(self, block=False):
+ """
+ Creates the volume in OpenStack if it does not already exist and
+ returns the domain Volume object
+ :return: The Volume domain object or None
+ """
+ self.initialize()
+
+ if not self.__volume:
+ self.__volume = cinder_utils.create_volume(
+ self._cinder, self._keystone, self.volume_settings)
+
+ logger.info(
+ 'Created volume with name - %s', self.volume_settings.name)
+ if self.__volume:
+ if block:
+ if self.volume_active(block=True):
+ logger.info('Volume is now active with name - %s',
+ self.volume_settings.name)
+ return self.__volume
+ else:
+ raise VolumeCreationError(
+ 'Volume was not created or activated in the '
+ 'alloted amount of time')
+ else:
+ logger.info('Did not create volume due to cleanup mode')
+
+ return self.__volume
+
+ def clean(self):
+ """
+ Cleanse environment of all artifacts
+ :return: void
+ """
+ if self.__volume:
+ try:
+ if self.volume_active():
+ cinder_utils.delete_volume(self._cinder, self.__volume)
+ else:
+ logger.warn('Timeout waiting to delete volume %s',
+ self.__volume.name)
+ except NotFound:
+ pass
+
+ try:
+ if self.volume_deleted(block=True):
+ logger.info(
+ 'Volume has been properly deleted with name - %s',
+ self.volume_settings.name)
+ self.__vm = None
+ else:
+ logger.error(
+ 'Volume not deleted within the timeout period of %s '
+ 'seconds', VOLUME_DELETE_TIMEOUT)
+ except Exception as e:
+ logger.error(
+ 'Unexpected error while checking VM instance status - %s',
+ e)
+
+ self.__volume = None
+
+ super(self.__class__, self).clean()
+
+ def get_volume(self):
+ """
+ Returns the domain Volume object as it was populated when create() was
+ called
+ :return: the object
+ """
+ return self.__volume
+
+ def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT,
+ poll_interval=POLL_INTERVAL):
+ """
+ Returns true when the volume status returns the value of
+ expected_status_code
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param timeout: The timeout value
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ return self._volume_status_check(STATUS_ACTIVE, block, timeout,
+ poll_interval)
+
+ def volume_in_use(self):
+ """
+ Returns true when the volume status returns the value of
+ expected_status_code
+ :return: T/F
+ """
+ return self._volume_status_check(STATUS_IN_USE, False, 0, 0)
+
+ def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL):
+ """
+ Returns true when the VM status returns the value of
+ expected_status_code or instance retrieval throws a NotFound exception.
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ try:
+ return self._volume_status_check(
+ STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval)
+ except NotFound as e:
+ logger.debug(
+ "Volume not found when querying status for %s with message "
+ "%s", STATUS_DELETED, e)
+ return True
+
+ def _volume_status_check(self, expected_status_code, block, timeout,
+ poll_interval):
+ """
+ Returns true when the volume status returns the value of
+ expected_status_code
+ :param expected_status_code: instance status evaluated with this string
+ value
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param timeout: The timeout value
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ # sleep and wait for volume status change
+ if block:
+ start = time.time()
+ else:
+ start = time.time() - timeout + 1
+
+ while timeout > time.time() - start:
+ status = self._status(expected_status_code)
+ if status:
+ logger.debug('Volume is active with name - %s',
+ self.volume_settings.name)
+ return True
+
+ logger.debug('Retry querying volume status in %s seconds',
+ str(poll_interval))
+ time.sleep(poll_interval)
+ logger.debug('Volume status query timeout in %s',
+ str(timeout - (time.time() - start)))
+
+ logger.error(
+ 'Timeout checking for volume status for ' + expected_status_code)
+ return False
+
+ def _status(self, expected_status_code):
+ """
+ Returns True when active else False
+ :param expected_status_code: instance status evaluated with this string
+ value
+ :return: T/F
+ """
+ status = cinder_utils.get_volume_status(self._cinder, self.__volume)
+ if not status:
+ logger.warning(
+ 'Cannot volume status for volume with ID - %s',
+ self.__volume.id)
+ return False
+
+ if status == 'ERROR':
+ raise VolumeCreationError(
+ 'Instance had an error during deployment')
+ logger.debug('Instance status is - ' + status)
+ return status == expected_status_code
+
+
+class VolumeSettings(VolumeConfig):
+ """
+ Class to hold the configuration settings required for creating OpenStack
+ Volume Type Encryption objects
+ deprecated
+ """
+
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.volume.VolumeConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
+
+
+class VolumeCreationError(Exception):
+ """
+ Exception to be thrown when an volume cannot be created
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
diff --git a/snaps/openstack/create_volume_type.py b/snaps/openstack/create_volume_type.py
index a60bb1e..9a45561 100644
--- a/snaps/openstack/create_volume_type.py
+++ b/snaps/openstack/create_volume_type.py
@@ -15,10 +15,10 @@
import logging
-import enum
from cinderclient.exceptions import NotFound
-from neutronclient.common.utils import str2bool
+from snaps.config.volume_type import (
+ VolumeTypeConfig, VolumeTypeEncryptionConfig)
from snaps.openstack.openstack_creator import OpenStackVolumeObject
from snaps.openstack.utils import cinder_utils
@@ -56,7 +56,7 @@ class OpenStackVolumeType(OpenStackVolumeObject):
return self.__volume_type
- def create(self, block=False):
+ def create(self):
"""
Creates the volume in OpenStack if it does not already exist and
returns the domain Volume object
@@ -87,6 +87,8 @@ class OpenStackVolumeType(OpenStackVolumeObject):
self.__volume_type = None
+ super(self.__class__, self).clean()
+
def get_volume_type(self):
"""
Returns the domain Volume object as it was populated when create() was
@@ -96,134 +98,32 @@ class OpenStackVolumeType(OpenStackVolumeObject):
return self.__volume_type
-class VolumeTypeSettings:
- def __init__(self, **kwargs):
- """
- Constructor
- :param name: the volume's name (required)
- :param description: the volume's name (optional)
- :param encryption: VolumeTypeEncryptionSettings (optional)
- :param qos_spec_name: name of the QoS Spec to associate (optional)
- :param public: When True, an image will be created with public
- visibility (default - False)
-
- TODO - Implement project_access parameter that will associate this
- VolumeType to a list of project names
- """
-
- self.name = kwargs.get('name')
- self.description = kwargs.get('description')
- self.qos_spec_name = kwargs.get('qos_spec_name')
-
- if 'encryption' in kwargs:
- if isinstance(kwargs['encryption'], dict):
- self.encryption = VolumeTypeEncryptionSettings(
- **kwargs['encryption'])
- elif isinstance(kwargs['encryption'],
- VolumeTypeEncryptionSettings):
- self.encryption = kwargs['encryption']
- else:
- self.encryption = None
-
- if 'public' in kwargs:
- if isinstance(kwargs['public'], str):
- self.public = str2bool(kwargs['public'])
- else:
- self.public = kwargs['public']
- else:
- self.public = False
-
- if not self.name:
- raise VolumeTypeSettingsError("The attribute name is required")
-
- def __eq__(self, other):
- return (self.name == other.name
- and self.description == other.description
- and self.qos_spec_name == other.qos_spec_name
- and self.encryption == other.encryption
- and self.public == other.public)
-
-
-class ControlLocation(enum.Enum):
+class VolumeTypeSettings(VolumeTypeConfig):
"""
- QoS Specification consumer types
+ Class to hold the configuration settings required for creating OpenStack
+ Volume Type objects
+ deprecated
"""
- front_end = 'front-end'
- back_end = 'back-end'
-
-class VolumeTypeEncryptionSettings:
def __init__(self, **kwargs):
- """
- Constructor
- :param name: the volume's name (required)
- :param provider_class: the volume's provider class (e.g. LuksEncryptor)
- :param control_location: the notional service where encryption is
- performed (e.g., front-end=Nova). The default
- value is 'front-end.'
- :param cipher: the encryption algorithm/mode to use
- (e.g., aes-xts-plain64). If the field is left empty,
- the provider default will be used
- :param key_size: the size of the encryption key, in bits
- (e.g., 128, 256). If the field is left empty, the
- provider default will be used
- """
+ from warnings import warn
+ warn('Use snaps.config.volume_type.VolumeTypeConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
- self.name = kwargs.get('name')
- self.provider_class = kwargs.get('provider_class')
- self.control_location = kwargs.get('control_location')
- if kwargs.get('control_location'):
- self.control_location = map_control_location(
- kwargs['control_location'])
- else:
- self.control_location = None
-
- self.cipher = kwargs.get('cipher')
- self.key_size = kwargs.get('key_size')
-
- if (not self.name or not self.provider_class
- or not self.control_location):
- raise VolumeTypeSettingsError(
- 'The attributes name, provider_class, and control_location '
- 'are required')
-
- def __eq__(self, other):
- return (self.name == other.name
- and self.provider_class == other.provider_class
- and self.control_location == other.control_location
- and self.cipher == other.cipher
- and self.key_size == other.key_size)
-
-
-def map_control_location(control_location):
- """
- Takes a the protocol value maps it to the Consumer enum. When None return
- None
- :param control_location: the value to map to the Enum
- :return: a ControlLocation enum object
- :raise: Exception if control_location parameter is invalid
- """
- if not control_location:
- return None
- elif isinstance(control_location, ControlLocation):
- return control_location
- else:
- proto_str = str(control_location)
- if proto_str == 'front-end':
- return ControlLocation.front_end
- elif proto_str == 'back-end':
- return ControlLocation.back_end
- else:
- raise VolumeTypeSettingsError('Invalid Consumer - ' + proto_str)
-
-
-class VolumeTypeSettingsError(Exception):
+
+class VolumeTypeEncryptionSettings(VolumeTypeEncryptionConfig):
"""
- Exception to be thrown when an volume settings are incorrect
+ Class to hold the configuration settings required for creating OpenStack
+ Volume Type Encryption objects
+ deprecated
"""
- def __init__(self, message):
- Exception.__init__(self, message)
+ def __init__(self, **kwargs):
+ from warnings import warn
+ warn('Use snaps.config.volume_type.VolumeTypeEncryptionConfig instead',
+ DeprecationWarning)
+ super(self.__class__, self).__init__(**kwargs)
class VolumeTypeCreationError(Exception):
diff --git a/snaps/openstack/openstack_creator.py b/snaps/openstack/openstack_creator.py
index 945a78b..6eeac37 100644
--- a/snaps/openstack/openstack_creator.py
+++ b/snaps/openstack/openstack_creator.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from snaps.domain.creator import CloudObject
-from snaps.openstack.utils import (nova_utils, neutron_utils, keystone_utils,
- cinder_utils)
+from snaps.openstack.utils import (
+ nova_utils, neutron_utils, keystone_utils, cinder_utils, magnum_utils)
__author__ = 'spisarski'
@@ -29,17 +29,24 @@ class OpenStackCloudObject(CloudObject):
Constructor
:param os_creds: the OpenStack credentials object
"""
- # super(self.__class__, self, os_creds)
self._os_creds = os_creds
+ self._os_session = None
+ self._keystone = None
def initialize(self):
- raise NotImplementedError('Do not override abstract method')
+ self._os_session = keystone_utils.keystone_session(self._os_creds)
+ self._keystone = keystone_utils.keystone_client(
+ self._os_creds, session=self._os_session)
+
+ def get_os_creds(self):
+ return self._os_creds
def create(self):
raise NotImplementedError('Do not override abstract method')
def clean(self):
- raise NotImplementedError('Do not override abstract method')
+ if self._os_session:
+ keystone_utils.close_session(self._os_session)
class OpenStackComputeObject(OpenStackCloudObject):
@@ -56,14 +63,12 @@ class OpenStackComputeObject(OpenStackCloudObject):
self._nova = None
def initialize(self):
- self._nova = nova_utils.nova_client(self._os_creds)
+ super(OpenStackComputeObject, self).initialize()
+ self._nova = nova_utils.nova_client(self._os_creds, self._os_session)
def create(self):
raise NotImplementedError('Do not override abstract method')
- def clean(self):
- raise NotImplementedError('Do not override abstract method')
-
class OpenStackNetworkObject(OpenStackCloudObject):
"""
@@ -79,14 +84,13 @@ class OpenStackNetworkObject(OpenStackCloudObject):
self._neutron = None
def initialize(self):
- self._neutron = neutron_utils.neutron_client(self._os_creds)
+ super(OpenStackNetworkObject, self).initialize()
+ self._neutron = neutron_utils.neutron_client(
+ self._os_creds, self._os_session)
def create(self):
raise NotImplementedError('Do not override abstract method')
- def clean(self):
- raise NotImplementedError('Do not override abstract method')
-
class OpenStackIdentityObject(OpenStackCloudObject):
"""
@@ -99,17 +103,13 @@ class OpenStackIdentityObject(OpenStackCloudObject):
:param os_creds: the OpenStack credentials object
"""
super(OpenStackIdentityObject, self).__init__(os_creds)
- self._keystone = None
def initialize(self):
- self._keystone = keystone_utils.keystone_client(self._os_creds)
+ super(OpenStackIdentityObject, self).initialize()
def create(self):
raise NotImplementedError('Do not override abstract method')
- def clean(self):
- raise NotImplementedError('Do not override abstract method')
-
class OpenStackVolumeObject(OpenStackCloudObject):
"""
@@ -125,10 +125,31 @@ class OpenStackVolumeObject(OpenStackCloudObject):
self._cinder = None
def initialize(self):
- self._cinder = cinder_utils.cinder_client(self._os_creds)
+ super(OpenStackVolumeObject, self).initialize()
+ self._cinder = cinder_utils.cinder_client(
+ self._os_creds, self._os_session)
def create(self):
raise NotImplementedError('Do not override abstract method')
- def clean(self):
+
+class OpenStackMagnumObject(OpenStackCloudObject):
+ """
+ Abstract class for all OpenStack compute creators
+ """
+
+ def __init__(self, os_creds):
+ """
+ Constructor
+ :param os_creds: the OpenStack credentials object
+ """
+ super(OpenStackMagnumObject, self).__init__(os_creds)
+ self._magnum = None
+
+ def initialize(self):
+ super(OpenStackMagnumObject, self).initialize()
+ self._magnum = magnum_utils.magnum_client(
+ self._os_creds, self._os_session)
+
+ def create(self):
raise NotImplementedError('Do not override abstract method')
diff --git a/snaps/openstack/os_credentials.py b/snaps/openstack/os_credentials.py
index cff2dd8..11ef8ff 100644
--- a/snaps/openstack/os_credentials.py
+++ b/snaps/openstack/os_credentials.py
@@ -44,6 +44,8 @@ class OSCreds:
clients
:param volume_api_version: The OpenStack's API version to use
for Cinder clients
+ :param magnum_api_version: The OpenStack's API version to use
+ for magnum clients
:param user_domain_id: Used for v3 APIs (default='default')
:param user_domain_name: Used for v3 APIs (default='Default')
:param project_domain_id: Used for v3 APIs (default='default')
@@ -51,10 +53,8 @@ class OSCreds:
:param interface: Used to specify the endpoint type for keystone as
public, admin, internal
:param proxy_settings: instance of os_credentials.ProxySettings class
- :param cacert: Default to be True for http, or the certification file
- is specified for https verification, or set to be False
- to disable server certificate verification without cert
- file
+ :param cacert: True for https or the certification file for https
+ verification (default=False)
:param region_name: the region (optional default = None)
"""
self.username = kwargs.get('username')
@@ -63,7 +63,7 @@ class OSCreds:
self.project_name = kwargs.get('project_name')
if kwargs.get('identity_api_version') is None:
- self.identity_api_version = keystone_utils.V2_VERSION_NUM
+ self.identity_api_version = keystone_utils.V3_VERSION_NUM
else:
self.identity_api_version = float(kwargs['identity_api_version'])
@@ -85,13 +85,19 @@ class OSCreds:
if kwargs.get('heat_api_version') is None:
self.heat_api_version = 1
else:
- self.heat_api_version = float(kwargs['heat_api_version'])
+ val = kwargs['heat_api_version']
+ ver = float(val)
+ self.heat_api_version = int(ver)
if kwargs.get('volume_api_version') is None:
self.volume_api_version = cinder_utils.VERSION_2
else:
- self.volume_api_version = float(
- kwargs['volume_api_version'])
+ self.volume_api_version = float(kwargs['volume_api_version'])
+
+ if kwargs.get('magnum_api_version') is None:
+ self.magnum_api_version = 1
+ else:
+ self.magnum_api_version = float(kwargs['magnum_api_version'])
self.user_domain_id = kwargs.get('user_domain_id', 'default')
@@ -165,7 +171,45 @@ class OSCreds:
return new_url
- @property
+ def to_dict(self):
+ """Converts object to a dict that can be used to construct another"""
+ return {'username': self.username,
+ 'password': self.password,
+ 'auth_url': self.auth_url,
+ 'project_name': self.project_name,
+ 'identity_api_version': self.identity_api_version,
+ 'image_api_version': self.image_api_version,
+ 'network_api_version': self.network_api_version,
+ 'compute_api_version': self.compute_api_version,
+ 'heat_api_version': self.heat_api_version,
+ 'user_domain_id': self.user_domain_id,
+ 'user_domain_name': self.user_domain_name,
+ 'project_domain_id': self.project_domain_id,
+ 'project_domain_name': self.project_domain_name,
+ 'interface': self.interface,
+ 'region_name': self.region_name,
+ 'proxy_settings': self.proxy_settings,
+ 'cacert': self.cacert}
+
+ def __eq__(self, other):
+ return (self.username == other.username and
+ self.password == other.password and
+ self.auth_url == other.auth_url and
+ self.project_name == other.project_name and
+ float(self.identity_api_version) == float(other.identity_api_version) and
+ float(self.image_api_version) == float(other.image_api_version) and
+ float(self.network_api_version) == float(other.network_api_version) and
+ float(self.compute_api_version) == float(other.compute_api_version) and
+ float(self.heat_api_version) == float(other.heat_api_version) and
+ self.user_domain_id == other.user_domain_id and
+ self.user_domain_name == other.user_domain_name and
+ self.project_domain_id == other.project_domain_id and
+ self.project_domain_name == other.project_domain_name and
+ self.interface == other.interface and
+ self.region_name == other.region_name and
+ self.proxy_settings == other.proxy_settings and
+ self.cacert == other.cacert)
+
def __str__(self):
"""Converts object to a string"""
return ('OSCreds - username=' + str(self.username) +
@@ -200,7 +244,6 @@ class ProxySettings:
:param port: the HTTP proxy port
:param https_host: the HTTPS proxy host (defaults to host)
:param https_port: the HTTPS proxy port (defaults to port)
- :param port: the HTTP proxy port
:param ssh_proxy_cmd: the SSH proxy command string (optional)
"""
self.host = kwargs.get('host')
diff --git a/snaps/openstack/tests/cluster_template_tests.py b/snaps/openstack/tests/cluster_template_tests.py
new file mode 100644
index 0000000..eda790d
--- /dev/null
+++ b/snaps/openstack/tests/cluster_template_tests.py
@@ -0,0 +1,284 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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 magnumclient.common.apiclient.exceptions import BadRequest
+
+from snaps.config.cluster_template import ClusterTemplateConfig
+from snaps.config.flavor import FlavorConfig
+from snaps.config.keypair import KeypairConfig
+from snaps.openstack.cluster_template import OpenStackClusterTemplate
+from snaps.openstack.create_flavor import OpenStackFlavor
+from snaps.openstack.create_image import OpenStackImage
+from snaps.openstack.create_keypairs import OpenStackKeypair
+from snaps.openstack.tests import openstack_tests
+
+try:
+ from urllib.request import URLError
+except ImportError:
+ from urllib2 import URLError
+
+import logging
+import uuid
+
+from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
+from snaps.openstack.utils import magnum_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('cluster_template_tests')
+
+
+class CreateClusterTemplateTests(OSIntegrationTestCase):
+ """
+ Test for the OpenStackClusterTemplate class defined in py
+ without any QoS Specs or Encryption
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateClusterTemplate object that is responsible for
+ downloading and creating an OS template config file within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.cluster_type_name = self.guid + '-cluster-type'
+ self.magnum = magnum_utils.magnum_client(
+ self.os_creds, self.os_session)
+
+ metadata = self.image_metadata
+ if not metadata:
+ metadata = dict()
+ if 'extra_properties' not in metadata:
+ metadata['extra_properties'] = dict()
+ metadata['extra_properties']['os_distro'] = 'cirros'
+
+ os_image_settings = openstack_tests.cirros_image_settings(
+ name=self.guid + '-image', image_metadata=metadata)
+
+ self.image_creator = OpenStackImage(self.os_creds, os_image_settings)
+
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor', ram=512, disk=10,
+ vcpus=1, metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_config)
+
+ keypair_priv_filepath = 'tmp/' + self.guid
+ keypair_pub_filepath = keypair_priv_filepath + '.pub'
+
+ self.keypair_creator = OpenStackKeypair(
+ self.os_creds, KeypairConfig(
+ name=self.guid + '-keypair',
+ public_filepath=keypair_pub_filepath,
+ private_filepath=keypair_priv_filepath))
+
+ self.cluster_template_creator = None
+
+ self.cluster_template_config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name)
+
+ try:
+ self.image_creator.create()
+ self.flavor_creator.create()
+ self.keypair_creator.create()
+ except:
+ self.tearDown()
+ raise
+
+ def tearDown(self):
+ """
+ Cleans the template config
+ """
+ if self.cluster_template_creator:
+ try:
+ self.cluster_template_creator.clean()
+ except:
+ pass
+ if self.keypair_creator:
+ try:
+ self.keypair_creator.clean()
+ except:
+ pass
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except:
+ pass
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_cluster_template(self):
+ """
+ Tests the creation of an OpenStack cluster template.
+ """
+ # Create ClusterTemplate
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, self.cluster_template_config)
+ created_cluster_template = self.cluster_template_creator.create()
+ self.assertIsNotNone(created_cluster_template)
+ self.assertEqual(self.cluster_template_config.name,
+ created_cluster_template.name)
+
+ retrieved_cluster_template1 = magnum_utils.get_cluster_template(
+ self.magnum, template_config=self.cluster_template_config)
+ self.assertIsNotNone(retrieved_cluster_template1)
+ self.assertEqual(created_cluster_template, retrieved_cluster_template1)
+
+ retrieved_cluster_template2 = magnum_utils.get_cluster_template_by_id(
+ self.magnum, created_cluster_template.id)
+ self.assertEqual(created_cluster_template, retrieved_cluster_template2)
+
+ def test_create_delete_cluster_template(self):
+ """
+ Tests the creation then deletion of an OpenStack template config to
+ ensure clean() does not raise an Exception.
+ """
+ # Create ClusterTemplate
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, self.cluster_template_config)
+ created_cluster_template = self.cluster_template_creator.create()
+ self.assertIsNotNone(created_cluster_template)
+
+ self.cluster_template_creator.clean()
+
+ tmplt = magnum_utils.get_cluster_template(
+ self.magnum, template_name=self.cluster_template_config.name)
+ self.assertIsNone(tmplt)
+
+ def test_create_same_cluster_template(self):
+ """
+ Tests the creation of an OpenStack cluster_template when one already
+ exists.
+ """
+ # Create ClusterTemplate
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, self.cluster_template_config)
+ cluster_template1 = self.cluster_template_creator.create()
+
+ retrieved_cluster_template = magnum_utils.get_cluster_template(
+ self.magnum, template_config=self.cluster_template_config)
+ self.assertEqual(cluster_template1, retrieved_cluster_template)
+
+ # Should be retrieving the instance data
+ os_cluster_template_2 = OpenStackClusterTemplate(
+ self.os_creds, self.cluster_template_config)
+ cluster_template2 = os_cluster_template_2.create()
+ self.assertEqual(cluster_template2, cluster_template2)
+
+ def test_create_cluster_template_bad_flavor(self):
+ """
+ Tests the creation of an OpenStack cluster template raises an
+ exception with an invalid flavor.
+ """
+ # Create ClusterTemplate
+ cluster_template_config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor='foo')
+
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, cluster_template_config)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template_creator.create()
+
+ def test_create_cluster_template_bad_master_flavor(self):
+ """
+ Tests the creation of an OpenStack cluster template raises an
+ exception with an invalid master flavor.
+ """
+ # Create ClusterTemplate
+ cluster_template_config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ master_flavor='foo')
+
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, cluster_template_config)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template_creator.create()
+
+ def test_create_cluster_template_bad_image(self):
+ """
+ Tests the creation of an OpenStack cluster template raises an
+ exception with an invalid image.
+ """
+ # Create ClusterTemplate
+ cluster_template_config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image='foo',
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name)
+
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, cluster_template_config)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template_creator.create()
+
+ def test_create_cluster_template_bad_network_driver(self):
+ """
+ Tests the creation of an OpenStack cluster template raises an
+ exception with an invalid keypair.
+ """
+ # Create ClusterTemplate
+ cluster_template_config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ network_driver='foo')
+
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, cluster_template_config)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template_creator.create()
+
+ def test_create_cluster_template_bad_volume_driver(self):
+ """
+ Tests the creation of an OpenStack cluster template raises an
+ exception with an invalid keypair.
+ """
+ # Create ClusterTemplate
+ cluster_template_config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ volume_driver='foo')
+
+ self.cluster_template_creator = OpenStackClusterTemplate(
+ self.os_creds, cluster_template_config)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template_creator.create()
diff --git a/snaps/openstack/tests/conf/os_credentials_tests.py b/snaps/openstack/tests/conf/os_credentials_tests.py
index 5efb32c..696ca2d 100644
--- a/snaps/openstack/tests/conf/os_credentials_tests.py
+++ b/snaps/openstack/tests/conf/os_credentials_tests.py
@@ -18,6 +18,7 @@ import unittest
from snaps.openstack.os_credentials import (
OSCredsError, OSCreds, ProxySettings, ProxySettingsError)
+from snaps.openstack.utils import cinder_utils
__author__ = 'spisarski'
@@ -141,12 +142,14 @@ class OSCredsUnitTests(unittest.TestCase):
project_name='hello')
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
- self.assertEqual('http://foo.bar:5000/v2.0', os_creds.auth_url)
+ self.assertEqual('http://foo.bar:5000/v3', os_creds.auth_url)
self.assertEqual('hello', os_creds.project_name)
- self.assertEqual(2, os_creds.identity_api_version)
+ self.assertEqual(3, os_creds.identity_api_version)
self.assertEqual(2, os_creds.image_api_version)
self.assertEqual(2, os_creds.compute_api_version)
self.assertEqual(1, os_creds.heat_api_version)
+ self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version)
+ self.assertEqual(1, os_creds.magnum_api_version)
self.assertEqual('default', os_creds.user_domain_id)
self.assertEqual('Default', os_creds.user_domain_name)
self.assertEqual('default', os_creds.project_domain_id)
@@ -162,12 +165,14 @@ class OSCredsUnitTests(unittest.TestCase):
'project_name': 'hello'})
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
- self.assertEqual('http://foo.bar:5000/v2.0', os_creds.auth_url)
+ self.assertEqual('http://foo.bar:5000/v3', os_creds.auth_url)
self.assertEqual('hello', os_creds.project_name)
- self.assertEqual(2, os_creds.identity_api_version)
+ self.assertEqual(3, os_creds.identity_api_version)
self.assertEqual(2, os_creds.image_api_version)
self.assertEqual(2, os_creds.compute_api_version)
self.assertEqual(1, os_creds.heat_api_version)
+ self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version)
+ self.assertEqual(1, os_creds.magnum_api_version)
self.assertEqual('default', os_creds.user_domain_id)
self.assertEqual('Default', os_creds.user_domain_name)
self.assertEqual('default', os_creds.project_domain_id)
@@ -183,6 +188,7 @@ class OSCredsUnitTests(unittest.TestCase):
'auth_url': 'http://foo.bar:5000/v2', 'project_name': 'hello',
'identity_api_version': '5', 'image_api_version': '6',
'compute_api_version': '7', 'heat_api_version': '8.0',
+ 'volume_api_version': '9.5', 'magnum_api_version': '10.6',
'cacert': 'true', 'region_name': 'test_region'})
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
@@ -192,6 +198,8 @@ class OSCredsUnitTests(unittest.TestCase):
self.assertEqual(6, os_creds.image_api_version)
self.assertEqual(7, os_creds.compute_api_version)
self.assertEqual(8.0, os_creds.heat_api_version)
+ self.assertEqual(9.5, os_creds.volume_api_version)
+ self.assertEqual(10.6, os_creds.magnum_api_version)
self.assertEqual('default', os_creds.user_domain_id)
self.assertEqual('Default', os_creds.user_domain_name)
self.assertEqual('default', os_creds.project_domain_id)
@@ -207,6 +215,7 @@ class OSCredsUnitTests(unittest.TestCase):
'auth_url': 'http://foo.bar:5000/v2', 'project_name': 'hello',
'identity_api_version': 5, 'image_api_version': 6,
'compute_api_version': 7, 'heat_api_version': 8.0,
+ 'volume_api_version': 9.5, 'magnum_api_version': 10.6,
'cacert': True, 'region_name': 'test_region'})
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
@@ -216,6 +225,8 @@ class OSCredsUnitTests(unittest.TestCase):
self.assertEqual(6, os_creds.image_api_version)
self.assertEqual(7, os_creds.compute_api_version)
self.assertEqual(8.0, os_creds.heat_api_version)
+ self.assertEqual(9.5, os_creds.volume_api_version)
+ self.assertEqual(10.6, os_creds.magnum_api_version)
self.assertEqual('default', os_creds.user_domain_id)
self.assertEqual('Default', os_creds.user_domain_name)
self.assertEqual('default', os_creds.project_domain_id)
@@ -232,12 +243,14 @@ class OSCredsUnitTests(unittest.TestCase):
project_name='hello', proxy_settings=proxy_settings)
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
- self.assertEqual('http://foo.bar:5000/v2.0', os_creds.auth_url)
+ self.assertEqual('http://foo.bar:5000/v3', os_creds.auth_url)
self.assertEqual('hello', os_creds.project_name)
- self.assertEqual(2, os_creds.identity_api_version)
+ self.assertEqual(3, os_creds.identity_api_version)
self.assertEqual(2, os_creds.image_api_version)
self.assertEqual(2, os_creds.compute_api_version)
self.assertEqual(1, os_creds.heat_api_version)
+ self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version)
+ self.assertEqual(1, os_creds.magnum_api_version)
self.assertEqual('default', os_creds.user_domain_id)
self.assertEqual('Default', os_creds.user_domain_name)
self.assertEqual('default', os_creds.project_domain_id)
@@ -249,6 +262,11 @@ class OSCredsUnitTests(unittest.TestCase):
self.assertIsNone(os_creds.proxy_settings.ssh_proxy_cmd)
self.assertIsNone(os_creds.region_name)
+ creds_dict = os_creds.to_dict()
+ creds_from_dict = OSCreds(**creds_dict)
+
+ self.assertEqual(os_creds, creds_from_dict)
+
def test_proxy_settings_obj_kwargs(self):
proxy_settings = ProxySettings(host='foo', port=1234)
os_creds = OSCreds(
@@ -260,12 +278,14 @@ class OSCredsUnitTests(unittest.TestCase):
'project_domain_name': 'domain4'})
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
- self.assertEqual('http://foo.bar:5000/v2.0', os_creds.auth_url)
+ self.assertEqual('http://foo.bar:5000/v3', os_creds.auth_url)
self.assertEqual('hello', os_creds.project_name)
- self.assertEqual(2, os_creds.identity_api_version)
+ self.assertEqual(3, os_creds.identity_api_version)
self.assertEqual(2, os_creds.image_api_version)
self.assertEqual(2, os_creds.compute_api_version)
self.assertEqual(1, os_creds.heat_api_version)
+ self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version)
+ self.assertEqual(1, os_creds.magnum_api_version)
self.assertEqual('domain1', os_creds.user_domain_id)
self.assertEqual('domain2', os_creds.user_domain_name)
self.assertEqual('domain3', os_creds.project_domain_id)
@@ -285,12 +305,14 @@ class OSCredsUnitTests(unittest.TestCase):
project_domain_id='domain3', project_domain_name='domain4')
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
- self.assertEqual('http://foo.bar:5000/v2.0', os_creds.auth_url)
+ self.assertEqual('http://foo.bar:5000/v3', os_creds.auth_url)
self.assertEqual('hello', os_creds.project_name)
- self.assertEqual(2, os_creds.identity_api_version)
+ self.assertEqual(3, os_creds.identity_api_version)
self.assertEqual(2, os_creds.image_api_version)
self.assertEqual(2, os_creds.compute_api_version)
self.assertEqual(1, os_creds.heat_api_version)
+ self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version)
+ self.assertEqual(1, os_creds.magnum_api_version)
self.assertEqual('domain1', os_creds.user_domain_id)
self.assertEqual('domain2', os_creds.user_domain_name)
self.assertEqual('domain3', os_creds.project_domain_id)
@@ -309,12 +331,14 @@ class OSCredsUnitTests(unittest.TestCase):
'region_name': 'test_region'})
self.assertEqual('foo', os_creds.username)
self.assertEqual('bar', os_creds.password)
- self.assertEqual('http://foo.bar:5000/v2.0', os_creds.auth_url)
+ self.assertEqual('http://foo.bar:5000/v3', os_creds.auth_url)
self.assertEqual('hello', os_creds.project_name)
- self.assertEqual(2, os_creds.identity_api_version)
+ self.assertEqual(3, os_creds.identity_api_version)
self.assertEqual(2, os_creds.image_api_version)
self.assertEqual(2, os_creds.compute_api_version)
self.assertEqual(1, os_creds.heat_api_version)
+ self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version)
+ self.assertEqual(1, os_creds.magnum_api_version)
self.assertEqual('default', os_creds.user_domain_id)
self.assertEqual('Default', os_creds.user_domain_name)
self.assertEqual('default', os_creds.project_domain_id)
diff --git a/snaps/openstack/tests/conf/os_env.yaml.template b/snaps/openstack/tests/conf/os_env.yaml.template
index e88bfaa..53d500f 100644
--- a/snaps/openstack/tests/conf/os_env.yaml.template
+++ b/snaps/openstack/tests/conf/os_env.yaml.template
@@ -1,17 +1,20 @@
# Keystone v2.0
#username: admin
#password: admin
-#os_auth_url: http://<host>:<port>/v2.0/
+#os_auth_url: http://<host>:<port>/
#project_name: admin
#ext_net: <external network name>
#http_proxy: <host>:<port>
#ssh_proxy_cmd: '/usr/local/bin/corkscrew <host> <port> %h %p'
#ssh_proxy_cmd: 'ssh <host> nc %h %p'
-# Keystone v2.0
+# Keystone v3
#username: admin
#password: admin
-#os_auth_url: http://<host>:<port>/v3
+#os_auth_url: http://<host>:<port>/
#project_name: admin
#identity_api_version: 3
-#ext_net: <external network name> \ No newline at end of file
+#ext_net: <external network name>
+
+flavor_metadata:
+ hw:mem_page_size: large
diff --git a/snaps/openstack/tests/create_flavor_tests.py b/snaps/openstack/tests/create_flavor_tests.py
index 4852d06..a69de40 100644
--- a/snaps/openstack/tests/create_flavor_tests.py
+++ b/snaps/openstack/tests/create_flavor_tests.py
@@ -15,11 +15,12 @@
import unittest
import uuid
+from snaps.config.flavor import FlavorConfig, FlavorConfigError
from snaps.openstack import create_flavor
-from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor, \
- FlavorSettingsError
+from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
from snaps.openstack.utils import nova_utils
+from snaps.openstack.tests import openstack_tests
__author__ = 'spisarski'
@@ -30,169 +31,169 @@ class FlavorSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings()
def test_empty_config(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(config=dict())
def test_name_only(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(config={'name': 'foo'})
def test_name_ram_only(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1)
def test_config_with_name_ram_only(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(config={'name': 'foo', 'ram': 1})
def test_name_ram_disk_only(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=1)
def test_config_with_name_ram_disk_only(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(config={'name': 'foo', 'ram': 1, 'disk': 1})
def test_ram_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram='bar', disk=2, vcpus=3, ephemeral=4,
swap=5, rxtx_factor=6.0,
is_public=False)
def test_config_ram_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 'bar', 'disk': 2, 'vcpus': 3,
'ephemeral': 4, 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_ram_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1.5, disk=2, vcpus=3, ephemeral=4,
swap=5, rxtx_factor=6.0, is_public=False)
def test_config_ram_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1.5, 'disk': 2, 'vcpus': 3,
'ephemeral': 4, 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_disk_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk='bar', vcpus=3, ephemeral=4,
swap=5, rxtx_factor=6.0,
is_public=False)
def test_config_disk_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 'bar', 'vcpus': 3,
'ephemeral': 4, 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_disk_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2.5, vcpus=3, ephemeral=4,
swap=5, rxtx_factor=6.0, is_public=False)
def test_config_disk_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2.5, 'vcpus': 3,
'ephemeral': 4, 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_vcpus_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus='bar', ephemeral=4,
swap=5, rxtx_factor=6.0,
is_public=False)
def test_config_vcpus_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 'bar',
'ephemeral': 4, 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_ephemeral_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral='bar',
swap=5, rxtx_factor=6.0,
is_public=False)
def test_config_ephemeral_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
'ephemeral': 'bar', 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_ephemeral_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4.5,
swap=5, rxtx_factor=6.0, is_public=False)
def test_config_ephemeral_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
'ephemeral': 4.5, 'swap': 5,
'rxtx_factor': 6.0, 'is_public': False})
def test_swap_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
swap='bar', rxtx_factor=6.0,
is_public=False)
def test_config_swap_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
'ephemeral': 4, 'swap': 'bar',
'rxtx_factor': 6.0, 'is_public': False})
def test_swap_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
swap=5.5, rxtx_factor=6.0, is_public=False)
def test_config_swap_float(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
'ephemeral': 4, 'swap': 5.5,
'rxtx_factor': 6.0, 'is_public': False})
def test_rxtx_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
swap=5, rxtx_factor='bar', is_public=False)
def test_config_rxtx_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
'ephemeral': 4, 'swap': 5,
'rxtx_factor': 'bar', 'is_public': False})
def test_is_pub_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4,
swap=5, rxtx_factor=6.0, is_public='bar')
def test_config_is_pub_string(self):
- with self.assertRaises(FlavorSettingsError):
+ with self.assertRaises(FlavorConfigError):
FlavorSettings(
config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3,
'ephemeral': 4, 'swap': 5,
@@ -274,7 +275,7 @@ class CreateFlavorTests(OSComponentTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.flavor_name = guid + 'name'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
# Initialize for cleanup
self.flavor_creator = None
@@ -286,13 +287,15 @@ class CreateFlavorTests(OSComponentTestCase):
if self.flavor_creator:
self.flavor_creator.clean()
+ super(self.__class__, self).__clean__()
+
def test_create_flavor(self):
"""
Tests the creation of an OpenStack flavor.
"""
# Create Flavor
- flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1,
- vcpus=1)
+ flavor_settings = FlavorConfig(
+ name=self.flavor_name, ram=1, disk=1, vcpus=1)
self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings)
flavor = self.flavor_creator.create()
self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor))
@@ -303,8 +306,8 @@ class CreateFlavorTests(OSComponentTestCase):
to ensure it has not been done twice.
"""
# Create Flavor
- flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1,
- vcpus=1)
+ flavor_settings = FlavorConfig(
+ name=self.flavor_name, ram=1, disk=1, vcpus=1)
self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings)
flavor = self.flavor_creator.create()
self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor))
@@ -319,8 +322,8 @@ class CreateFlavorTests(OSComponentTestCase):
Tests the creation and cleanup of an OpenStack flavor.
"""
# Create Flavor
- flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1,
- vcpus=1)
+ flavor_settings = FlavorConfig(
+ name=self.flavor_name, ram=1, disk=1, vcpus=1)
self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings)
flavor = self.flavor_creator.create()
self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor))
@@ -339,8 +342,8 @@ class CreateFlavorTests(OSComponentTestCase):
raise any exceptions.
"""
# Create Flavor
- flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1,
- vcpus=1)
+ flavor_settings = FlavorConfig(
+ name=self.flavor_name, ram=1, disk=1, vcpus=1)
self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings)
flavor = self.flavor_creator.create()
self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor))
@@ -362,10 +365,12 @@ class CreateFlavorTests(OSComponentTestCase):
raise any exceptions.
"""
# Create Flavor
- flavor_settings = FlavorSettings(
+ if self.flavor_metadata:
+ self.flavor_metadata.update(create_flavor.MEM_PAGE_SIZE_ANY)
+ flavor_settings = openstack_tests.get_flavor_config(
name=self.flavor_name, ram=1, disk=1, vcpus=1, ephemeral=2, swap=3,
rxtx_factor=2.2, is_public=False,
- metadata=create_flavor.MEM_PAGE_SIZE_ANY)
+ metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings)
flavor = self.flavor_creator.create()
self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor))
@@ -399,7 +404,7 @@ def validate_flavor(nova, flavor_settings, flavor):
equals = False
break
- swap = str()
+ swap = None
if flavor_settings.swap != 0:
swap = flavor_settings.swap
diff --git a/snaps/openstack/tests/create_image_tests.py b/snaps/openstack/tests/create_image_tests.py
index f70a71c..ae59a67 100644
--- a/snaps/openstack/tests/create_image_tests.py
+++ b/snaps/openstack/tests/create_image_tests.py
@@ -27,9 +27,10 @@ import uuid
import os
from snaps import file_utils
+from snaps.config.image import ImageConfigError
from snaps.openstack import create_image
-from snaps.openstack.create_image import (ImageSettings, ImageCreationError,
- ImageSettingsError)
+from snaps.openstack.create_image import ImageSettings, ImageCreationError
+from snaps.config.image import ImageConfig
from snaps.openstack.tests import openstack_tests
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
from snaps.openstack.utils import glance_utils
@@ -42,38 +43,40 @@ logger = logging.getLogger('create_image_tests')
class ImageSettingsUnitTests(unittest.TestCase):
"""
Tests the construction of the ImageSettings class
+ To be removed once the deprecated class ImageSettings is finally removed
+ from the source tree
"""
def test_no_params(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings()
def test_empty_config(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(**dict())
def test_name_only(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(**{'name': 'foo'})
def test_name_user_only(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(name='foo', image_user='bar')
def test_config_with_name_user_only(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(**{'name': 'foo', 'image_user': 'bar'})
def test_name_user_format_only(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(name='foo', image_user='bar', img_format='qcow2')
def test_config_with_name_user_format_only(self):
- with self.assertRaises(ImageSettingsError):
+ with self.assertRaises(ImageConfigError):
ImageSettings(
**{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2'})
@@ -262,7 +265,8 @@ class CreateImageSuccessTests(OSIntegrationTestCase):
guid = uuid.uuid4()
self.image_name = self.__class__.__name__ + '-' + str(guid)
- self.glance = glance_utils.glance_client(self.os_creds)
+ self.glance = glance_utils.glance_client(
+ self.os_creds, self.os_session)
self.image_creator = None
if self.image_metadata and 'glance_tests' in self.image_metadata:
@@ -376,8 +380,8 @@ class CreateImageSuccessTests(OSIntegrationTestCase):
clean() does not raise an Exception.
"""
# Create Image
- self.image_creator = create_image.OpenStackImage(self.os_creds,
- self.image_settings)
+ self.image_creator = create_image.OpenStackImage(
+ self.os_creds, self.image_settings)
created_image = self.image_creator.create()
self.assertIsNotNone(created_image)
@@ -448,8 +452,8 @@ class CreateImageSuccessTests(OSIntegrationTestCase):
self.assertEqual(image1.properties, retrieved_image.properties)
# Should be retrieving the instance data
- image_2_settings = ImageSettings(name=self.image_settings.name,
- image_user='foo', exists=True)
+ image_2_settings = ImageConfig(name=self.image_settings.name,
+ image_user='foo', exists=True)
os_image_2 = create_image.OpenStackImage(self.os_creds,
image_2_settings)
image2 = os_image_2.create()
@@ -478,8 +482,8 @@ class CreateImageNegativeTests(OSIntegrationTestCase):
Expect an ImageCreationError when the image name does not exist when a
file or URL has not been configured
"""
- os_image_settings = ImageSettings(name='foo', image_user='bar',
- exists=True)
+ os_image_settings = ImageConfig(name='foo', image_user='bar',
+ exists=True)
self.image_creator = create_image.OpenStackImage(self.os_creds,
os_image_settings)
@@ -497,10 +501,11 @@ class CreateImageNegativeTests(OSIntegrationTestCase):
name=self.image_name)
self.image_creator = create_image.OpenStackImage(
self.os_creds,
- create_image.ImageSettings(name=os_image_settings.name,
- image_user=os_image_settings.image_user,
- img_format=os_image_settings.format,
- url="http://foo.bar"))
+ ImageConfig(
+ name=os_image_settings.name,
+ image_user=os_image_settings.image_user,
+ img_format=os_image_settings.format,
+ url="http://foo.bar"))
try:
self.image_creator.create()
@@ -519,10 +524,10 @@ class CreateImageNegativeTests(OSIntegrationTestCase):
name=self.image_name)
self.image_creator = create_image.OpenStackImage(
self.os_creds,
- create_image.ImageSettings(name=os_image_settings.name,
- image_user=os_image_settings.image_user,
- img_format='foo',
- url=os_image_settings.url))
+ ImageConfig(
+ name=os_image_settings.name,
+ image_user=os_image_settings.image_user,
+ img_format='foo', url=os_image_settings.url))
with self.assertRaises(Exception):
self.image_creator.create()
@@ -535,10 +540,10 @@ class CreateImageNegativeTests(OSIntegrationTestCase):
name=self.image_name)
self.image_creator = create_image.OpenStackImage(
self.os_creds,
- create_image.ImageSettings(name=os_image_settings.name,
- image_user=os_image_settings.image_user,
- img_format=os_image_settings.format,
- image_file="/foo/bar.qcow"))
+ ImageConfig(
+ name=os_image_settings.name,
+ image_user=os_image_settings.image_user,
+ img_format=os_image_settings.format, image_file="/foo/bar.qcow"))
with self.assertRaises(IOError):
self.image_creator.create()
@@ -558,7 +563,8 @@ class CreateMultiPartImageTests(OSIntegrationTestCase):
guid = uuid.uuid4()
self.image_creators = list()
self.image_name = self.__class__.__name__ + '-' + str(guid)
- self.glance = glance_utils.glance_client(self.os_creds)
+ self.glance = glance_utils.glance_client(
+ self.os_creds, self.os_session)
self.tmp_dir = 'tmp/' + str(guid)
if not os.path.exists(self.tmp_dir):
diff --git a/snaps/openstack/tests/create_instance_tests.py b/snaps/openstack/tests/create_instance_tests.py
index 9c872bc..17831b3 100644
--- a/snaps/openstack/tests/create_instance_tests.py
+++ b/snaps/openstack/tests/create_instance_tests.py
@@ -20,26 +20,37 @@ import unittest
import uuid
import os
-from neutronclient.common.exceptions import InvalidIpForSubnetClient
+from neutronclient.common.exceptions import (
+ InvalidIpForSubnetClient, BadRequest)
from snaps import file_utils
-from snaps.openstack import create_network, create_router
-from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
-from snaps.openstack.create_image import OpenStackImage, ImageSettings
+from snaps.config.image import ImageConfig
+from snaps.config.keypair import KeypairConfig
+from snaps.config.network import PortConfig, NetworkConfig, SubnetConfig
+from snaps.config.router import RouterConfig
+from snaps.config.security_group import (
+ Protocol, SecurityGroupRuleConfig, Direction, SecurityGroupConfig)
+from snaps.config.vm_inst import (
+ VmInstanceConfig, FloatingIpConfig, VmInstanceConfigError,
+ FloatingIpConfigError)
+from snaps.config.volume import VolumeConfig
+from snaps.openstack import create_network, create_router, create_instance
+from snaps.openstack.create_flavor import OpenStackFlavor
+from snaps.openstack.create_image import OpenStackImage
from snaps.openstack.create_instance import (
- VmInstanceSettings, OpenStackVmInstance, FloatingIpSettings,
- VmInstanceSettingsError, FloatingIpSettingsError)
-from snaps.openstack.create_keypairs import OpenStackKeypair, KeypairSettings
-from snaps.openstack.create_network import (
- OpenStackNetwork, PortSettings, NetworkSettings)
-from snaps.openstack.create_router import OpenStackRouter, RouterSettings
-from snaps.openstack.create_security_group import (
- SecurityGroupSettings, OpenStackSecurityGroup, SecurityGroupRuleSettings,
- Direction, Protocol)
+ VmInstanceSettings, OpenStackVmInstance, FloatingIpSettings)
+from snaps.openstack.create_keypairs import OpenStackKeypair
+from snaps.openstack.create_network import OpenStackNetwork
+from snaps.openstack.create_router import OpenStackRouter
+from snaps.openstack.create_security_group import OpenStackSecurityGroup
+from snaps.openstack.create_volume import OpenStackVolume
+from snaps.openstack.os_credentials import OSCreds
from snaps.openstack.tests import openstack_tests, validation_utils
from snaps.openstack.tests.os_source_file_test import (
OSIntegrationTestCase, OSComponentTestCase)
-from snaps.openstack.utils import nova_utils
+from snaps.openstack.utils import nova_utils, keystone_utils, neutron_utils
+from snaps.openstack.utils.nova_utils import RebootType
+from snaps.openstack.utils import nova_utils, settings_utils, neutron_utils
__author__ = 'spisarski'
@@ -54,31 +65,31 @@ class VmInstanceSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(VmInstanceSettingsError):
+ with self.assertRaises(VmInstanceConfigError):
VmInstanceSettings()
def test_empty_config(self):
- with self.assertRaises(VmInstanceSettingsError):
+ with self.assertRaises(VmInstanceConfigError):
VmInstanceSettings(config=dict())
def test_name_only(self):
- with self.assertRaises(VmInstanceSettingsError):
+ with self.assertRaises(VmInstanceConfigError):
VmInstanceSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(VmInstanceSettingsError):
+ with self.assertRaises(VmInstanceConfigError):
VmInstanceSettings(config={'name': 'foo'})
def test_name_flavor_only(self):
- with self.assertRaises(VmInstanceSettingsError):
+ with self.assertRaises(VmInstanceConfigError):
VmInstanceSettings(name='foo', flavor='bar')
def test_config_with_name_flavor_only(self):
- with self.assertRaises(VmInstanceSettingsError):
+ with self.assertRaises(VmInstanceConfigError):
VmInstanceSettings(config={'name': 'foo', 'flavor': 'bar'})
def test_name_flavor_port_only(self):
- port_settings = PortSettings(name='foo-port', network_name='bar-net')
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
settings = VmInstanceSettings(name='foo', flavor='bar',
port_settings=[port_settings])
self.assertEqual('foo', settings.name)
@@ -93,9 +104,10 @@ class VmInstanceSettingsUnitTests(unittest.TestCase):
self.assertEqual(300, settings.vm_delete_timeout)
self.assertEqual(180, settings.ssh_connect_timeout)
self.assertIsNone(settings.availability_zone)
+ self.assertIsNone(settings.volume_names)
def test_config_with_name_flavor_port_only(self):
- port_settings = PortSettings(name='foo-port', network_name='bar-net')
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
settings = VmInstanceSettings(
**{'name': 'foo', 'flavor': 'bar', 'ports': [port_settings]})
self.assertEqual('foo', settings.name)
@@ -110,20 +122,20 @@ class VmInstanceSettingsUnitTests(unittest.TestCase):
self.assertEqual(300, settings.vm_delete_timeout)
self.assertEqual(180, settings.ssh_connect_timeout)
self.assertIsNone(settings.availability_zone)
+ self.assertIsNone(settings.volume_names)
def test_all(self):
- port_settings = PortSettings(name='foo-port', network_name='bar-net')
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
fip_settings = FloatingIpSettings(name='foo-fip', port_name='bar-port',
router_name='foo-bar-router')
- settings = VmInstanceSettings(name='foo', flavor='bar',
- port_settings=[port_settings],
- security_group_names=['sec_grp_1'],
- floating_ip_settings=[fip_settings],
- sudo_user='joe', vm_boot_timeout=999,
- vm_delete_timeout=333,
- ssh_connect_timeout=111,
- availability_zone='server name')
+ settings = VmInstanceSettings(
+ name='foo', flavor='bar', port_settings=[port_settings],
+ security_group_names=['sec_grp_1'],
+ floating_ip_settings=[fip_settings], sudo_user='joe',
+ vm_boot_timeout=999, vm_delete_timeout=333,
+ ssh_connect_timeout=111, availability_zone='server name',
+ volume_names=['vol1'])
self.assertEqual('foo', settings.name)
self.assertEqual('bar', settings.flavor)
self.assertEqual(1, len(settings.port_settings))
@@ -142,9 +154,10 @@ class VmInstanceSettingsUnitTests(unittest.TestCase):
self.assertEqual(333, settings.vm_delete_timeout)
self.assertEqual(111, settings.ssh_connect_timeout)
self.assertEqual('server name', settings.availability_zone)
+ self.assertEqual('vol1', settings.volume_names[0])
def test_config_all(self):
- port_settings = PortSettings(name='foo-port', network_name='bar-net')
+ port_settings = PortConfig(name='foo-port', network_name='bar-net')
fip_settings = FloatingIpSettings(name='foo-fip', port_name='bar-port',
router_name='foo-bar-router')
@@ -153,7 +166,8 @@ class VmInstanceSettingsUnitTests(unittest.TestCase):
'security_group_names': ['sec_grp_1'],
'floating_ips': [fip_settings], 'sudo_user': 'joe',
'vm_boot_timeout': 999, 'vm_delete_timeout': 333,
- 'ssh_connect_timeout': 111, 'availability_zone': 'server name'})
+ 'ssh_connect_timeout': 111, 'availability_zone': 'server name',
+ 'volume_names': ['vol2']})
self.assertEqual('foo', settings.name)
self.assertEqual('bar', settings.flavor)
self.assertEqual(1, len(settings.port_settings))
@@ -171,6 +185,7 @@ class VmInstanceSettingsUnitTests(unittest.TestCase):
self.assertEqual(333, settings.vm_delete_timeout)
self.assertEqual(111, settings.ssh_connect_timeout)
self.assertEqual('server name', settings.availability_zone)
+ self.assertEqual('vol2', settings.volume_names[0])
class FloatingIpSettingsUnitTests(unittest.TestCase):
@@ -179,35 +194,35 @@ class FloatingIpSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings()
def test_empty_config(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(**dict())
def test_name_only(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(**{'name': 'foo'})
def test_name_port_only(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(name='foo', port_name='bar')
def test_config_with_name_port_only(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(**{'name': 'foo', 'port_name': 'bar'})
def test_name_router_only(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(name='foo', router_name='bar')
def test_config_with_name_router_only(self):
- with self.assertRaises(FloatingIpSettingsError):
+ with self.assertRaises(FloatingIpConfigError):
FloatingIpSettings(**{'name': 'foo', 'router_name': 'bar'})
def test_name_port_router_name_only(self):
@@ -279,7 +294,7 @@ class SimpleHealthCheck(OSIntegrationTestCase):
"""
super(self.__class__, self).__start__()
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.vm_inst_name = guid + '-inst'
self.port_1_name = guid + 'port-1'
@@ -291,8 +306,11 @@ class SimpleHealthCheck(OSIntegrationTestCase):
self.inst_creator = None
self.priv_net_config = openstack_tests.get_priv_net_config(
- net_name=guid + '-priv-net', subnet_name=guid + '-priv-subnet')
- self.port_settings = PortSettings(
+ project_name=self.os_creds.project_name,
+ net_name=guid + '-priv-net',
+ subnet_name=guid + '-priv-subnet',
+ netconf_override=self.netconf_override)
+ self.port_settings = PortConfig(
name=self.port_1_name,
network_name=self.priv_net_config.network_settings.name)
@@ -313,10 +331,15 @@ class SimpleHealthCheck(OSIntegrationTestCase):
self.network_creator.create()
# Create Flavor
+ self.flavor_ram = 256
+ if (self.flavor_metadata and
+ self.flavor_metadata.get('hw:mem_page_size') == 'large'):
+ self.flavor_ram = 1024
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=self.flavor_ram, disk=10,
+ vcpus=1, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10,
- vcpus=1, metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
except Exception as e:
self.tearDown()
@@ -365,7 +388,7 @@ class SimpleHealthCheck(OSIntegrationTestCase):
Tests the creation of an OpenStack instance with a single port and
ensures that it's assigned IP address is the actual.
"""
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -390,21 +413,16 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase):
def setUp(self):
"""
- Instantiates the CreateImage object that is responsible for downloading
- and creating an OS image file
- within OpenStack
+ Setup the objects required for the test
"""
super(self.__class__, self).__start__()
- guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.vm_inst_name = guid + '-inst'
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.vm_inst_name = self.guid + '-inst'
self.nova = nova_utils.nova_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(self.os_creds)
os_image_settings = openstack_tests.cirros_image_settings(
- name=guid + '-image', image_metadata=self.image_metadata)
-
- net_config = openstack_tests.get_priv_net_config(
- net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
- router_name=guid + '-pub-router', external_net=self.ext_net_name)
+ name=self.guid + '-image', image_metadata=self.image_metadata)
# Initialize for tearDown()
self.image_creator = None
@@ -420,21 +438,13 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase):
self.image_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10,
- vcpus=2, metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
-
- # Create Network
- self.network_creator = OpenStackNetwork(
- self.os_creds, net_config.network_settings)
- self.network_creator.create()
-
- self.port_settings = PortSettings(
- name=guid + '-port',
- network_name=net_config.network_settings.name)
-
+ self.network_creator = None
except Exception as e:
self.tearDown()
raise e
@@ -481,7 +491,23 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase):
Tests the creation of an OpenStack instance with a single port with a
static IP without a Floating IP.
"""
- instance_settings = VmInstanceSettings(
+ # Create Network
+ net_config = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
+ net_name=self.guid + '-pub-net',
+ subnet_name=self.guid + '-pub-subnet',
+ router_name=self.guid + '-pub-router',
+ external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
+ self.network_creator = OpenStackNetwork(
+ self.os_creds, net_config.network_settings)
+ self.network_creator.create()
+
+ self.port_settings = PortConfig(
+ name=self.guid + '-port',
+ network_name=net_config.network_settings.name)
+
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -490,20 +516,174 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase):
self.os_creds, instance_settings,
self.image_creator.image_settings)
- vm_inst = self.inst_creator.create()
- self.assertIsNotNone(nova_utils.get_server(
- self.nova, vm_inst_settings=instance_settings))
+ vm_inst = self.inst_creator.create(block=True)
+ vm_inst_get = nova_utils.get_server(
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=instance_settings)
+ self.assertEqual(vm_inst, vm_inst_get)
+
+ self.assertIsNotNone(self.inst_creator.get_vm_inst().availability_zone)
+ self.assertIsNone(self.inst_creator.get_vm_inst().compute_host)
# Delete instance
nova_utils.delete_vm_instance(self.nova, vm_inst)
self.assertTrue(self.inst_creator.vm_deleted(block=True))
self.assertIsNone(nova_utils.get_server(
- self.nova, vm_inst_settings=instance_settings))
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=instance_settings))
# Exception should not be thrown
self.inst_creator.clean()
+ def test_create_admin_instance(self):
+ """
+ Tests the creation of an OpenStack instance with a single port with a
+ static IP without a Floating IP.
+ """
+ # Create Network
+ net_config = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
+ net_name=self.guid + '-pub-net',
+ subnet_name=self.guid + '-pub-subnet',
+ router_name=self.guid + '-pub-router',
+ external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
+ self.network_creator = OpenStackNetwork(
+ self.admin_os_creds, net_config.network_settings)
+ self.network_creator.create()
+
+ self.port_settings = PortConfig(
+ name=self.guid + '-port',
+ network_name=net_config.network_settings.name)
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[self.port_settings])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.admin_os_creds, instance_settings,
+ self.image_creator.image_settings)
+
+ admin_nova = nova_utils.nova_client(self.admin_os_creds)
+ admin_neutron = neutron_utils.neutron_client(self.admin_os_creds)
+ admin_key = keystone_utils.keystone_client(self.admin_os_creds)
+ vm_inst = self.inst_creator.create(block=True)
+
+ self.assertIsNotNone(vm_inst)
+ vm_inst_get = nova_utils.get_server(
+ admin_nova, admin_neutron, admin_key,
+ vm_inst_settings=instance_settings)
+ self.assertEqual(vm_inst, vm_inst_get)
+
+ self.assertIsNone(nova_utils.get_server(
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=instance_settings))
+
+ self.assertIsNotNone(self.inst_creator.get_vm_inst().availability_zone)
+ self.assertIsNotNone(self.inst_creator.get_vm_inst().compute_host)
+
+
+class CreateInstanceExternalNetTests(OSIntegrationTestCase):
+ """
+ Simple instance creation tests where the network is external
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateImage object that is responsible for downloading
+ and creating an OS image file
+ within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.vm_inst_name = guid + '-inst'
+ self.nova = nova_utils.nova_client(self.admin_os_creds)
+ self.neutron = neutron_utils.neutron_client(self.admin_os_creds)
+ os_image_settings = openstack_tests.cirros_image_settings(
+ name=guid + '-image', image_metadata=self.image_metadata)
+
+ # Initialize for tearDown()
+ self.image_creator = None
+ self.flavor_creator = None
+ self.inst_creator = None
+
+ try:
+ # Create Image
+ self.image_creator = OpenStackImage(self.os_creds,
+ os_image_settings)
+ self.image_creator.create()
+
+ # Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(
+ self.admin_os_creds, flavor_config)
+ self.flavor_creator.create()
+
+ self.port_settings = PortConfig(
+ name=guid + '-port',
+ network_name=self.ext_net_name)
+
+ except Exception as e:
+ self.tearDown()
+ raise e
+
+ def tearDown(self):
+ """
+ Cleans the created object
+ """
+ if self.inst_creator:
+ try:
+ self.inst_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning VM instance with message '
+ '- %s', e)
+
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning flavor with message - %s',
+ e)
+
+ if self.image_creator and not self.image_creator.image_settings.exists:
+ try:
+ self.image_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning image with message - %s', e)
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_instance_public_net(self):
+ """
+ Tests the creation of an OpenStack instance with a single port to
+ the external network.
+ """
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[self.port_settings])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.admin_os_creds, instance_settings,
+ self.image_creator.image_settings)
+
+ vm_inst = self.inst_creator.create(block=True)
+ vm_inst_get = nova_utils.get_server(
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=instance_settings)
+ self.assertEqual(vm_inst, vm_inst_get)
+ ip = self.inst_creator.get_port_ip(self.port_settings.name)
+
+ check_dhcp_lease(self.inst_creator, ip)
+
class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
"""
@@ -515,9 +695,10 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
Instantiates the CreateImage object that is responsible for downloading
and creating an OS image file within OpenStack
"""
+ self.proj_users = ['admin']
super(self.__class__, self).__start__()
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.keypair_priv_filepath = 'tmp/' + guid
self.keypair_pub_filepath = self.keypair_priv_filepath + '.pub'
@@ -537,8 +718,10 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
self.inst_creators = list()
self.pub_net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
- router_name=guid + '-pub-router', external_net=self.ext_net_name)
+ router_name=guid + '-pub-router', external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
os_image_settings = openstack_tests.cirros_image_settings(
name=guid + '-image', image_metadata=self.image_metadata)
try:
@@ -558,32 +741,31 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
self.router_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10,
- vcpus=2, metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name,
public_filepath=self.keypair_pub_filepath,
private_filepath=self.keypair_priv_filepath))
self.keypair_creator.create()
sec_grp_name = guid + '-sec-grp'
- rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.icmp)
- rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.tcp,
- port_range_min=22,
- port_range_max=22)
+ rule1 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.icmp)
+ rule2 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.tcp, port_range_min=22, port_range_max=22)
self.sec_grp_creator = OpenStackSecurityGroup(
self.os_creds,
- SecurityGroupSettings(name=sec_grp_name,
- rule_settings=[rule1, rule2]))
+ SecurityGroupConfig(
+ name=sec_grp_name, rule_settings=[rule1, rule2]))
self.sec_grp_creator.create()
except Exception as e:
self.tearDown()
@@ -609,12 +791,6 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
'Unexpected exception cleaning keypair with message - %s',
e)
- if os.path.isfile(self.keypair_pub_filepath):
- os.remove(self.keypair_pub_filepath)
-
- if os.path.isfile(self.keypair_priv_filepath):
- os.remove(self.keypair_priv_filepath)
-
if self.flavor_creator:
try:
self.flavor_creator.clean()
@@ -663,17 +839,17 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
"""
ip_1 = '10.55.1.100'
sub_settings = self.pub_net_config.network_settings.subnet_settings
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.pub_net_config.network_settings.name,
ip_addrs=[
{'subnet_name': sub_settings[0].name, 'ip': ip_1}])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings],
- floating_ip_settings=[FloatingIpSettings(
+ floating_ip_settings=[FloatingIpConfig(
name=self.floating_ip_name, port_name=self.port_1_name,
router_name=self.pub_net_config.router_settings.name)])
@@ -682,7 +858,7 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
self.image_creator.image_settings,
keypair_settings=self.keypair_creator.keypair_settings)
self.inst_creators.append(inst_creator)
- vm_inst = inst_creator.create()
+ vm_inst = inst_creator.create(block=True)
self.assertEqual(ip_1, inst_creator.get_port_ip(self.port_1_name))
self.assertTrue(inst_creator.vm_active(block=True))
@@ -693,15 +869,16 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
Tests the ability to access a VM via SSH and a floating IP when it has
been assigned prior to being active.
"""
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.pub_net_config.network_settings.name)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings],
- floating_ip_settings=[FloatingIpSettings(
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name],
+ floating_ip_settings=[FloatingIpConfig(
name=self.floating_ip_name, port_name=self.port_1_name,
router_name=self.pub_net_config.router_settings.name)])
@@ -718,8 +895,6 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
ip = inst_creator.get_port_ip(port_settings.name)
self.assertTrue(check_dhcp_lease(inst_creator, ip))
- inst_creator.add_security_group(
- self.sec_grp_creator.get_security_group())
self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id)
self.assertTrue(validate_ssh_client(inst_creator))
@@ -729,15 +904,16 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
Tests the ability to access a VM via SSH and a floating IP when it has
been assigned prior to being active.
"""
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.pub_net_config.network_settings.name)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings],
- floating_ip_settings=[FloatingIpSettings(
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name],
+ floating_ip_settings=[FloatingIpConfig(
name=self.floating_ip_name, port_name=self.port_1_name,
router_name=self.pub_net_config.router_settings.name)])
@@ -756,26 +932,184 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
ip = inst_creator.get_port_ip(port_settings.name)
self.assertTrue(check_dhcp_lease(inst_creator, ip))
- inst_creator.add_security_group(
- self.sec_grp_creator.get_security_group())
self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id)
self.assertTrue(validate_ssh_client(inst_creator))
+ def test_ssh_client_fip_after_reboot(self):
+ """
+ Tests the ability to access a VM via SSH and a floating IP after it has
+ been rebooted.
+ """
+ port_settings = PortConfig(
+ name=self.port_1_name,
+ network_name=self.pub_net_config.network_settings.name)
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings],
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name],
+ floating_ip_settings=[FloatingIpConfig(
+ name=self.floating_ip_name, port_name=self.port_1_name,
+ router_name=self.pub_net_config.router_settings.name)])
+
+ inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings,
+ keypair_settings=self.keypair_creator.keypair_settings)
+ self.inst_creators.append(inst_creator)
+
+ # block=True will force the create() method to block until the
+ vm_inst = inst_creator.create(block=True)
+ self.assertIsNotNone(vm_inst)
+
+ self.assertTrue(inst_creator.vm_active(block=True))
+
+ ip = inst_creator.get_port_ip(port_settings.name)
+ self.assertTrue(check_dhcp_lease(inst_creator, ip))
+
+ self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id)
+
+ self.assertTrue(validate_ssh_client(inst_creator))
+
+ # Test default reboot which should be 'SOFT'
+ inst_creator.reboot()
+ # Lag time to allow for shutdown routine to take effect
+ time.sleep(15)
+ self.assertTrue(check_dhcp_lease(inst_creator, ip))
+ self.assertTrue(validate_ssh_client(inst_creator))
+
+ # Test 'SOFT' reboot
+ inst_creator.reboot(reboot_type=RebootType.soft)
+ time.sleep(15)
+ self.assertTrue(check_dhcp_lease(inst_creator, ip))
+ self.assertTrue(validate_ssh_client(inst_creator))
+
+ # Test 'HARD' reboot
+ inst_creator.reboot(reboot_type=RebootType.hard)
+ time.sleep(15)
+ self.assertTrue(check_dhcp_lease(inst_creator, ip))
+ self.assertTrue(validate_ssh_client(inst_creator))
+
+ def test_ssh_client_fip_after_init(self):
+ """
+ Tests the ability to assign a floating IP to an already initialized
+ OpenStackVmInstance object. After the floating IP has been allocated
+ and assigned, this test will ensure that it can be accessed via SSH.
+ """
+ port_settings = PortConfig(
+ name=self.port_1_name,
+ network_name=self.pub_net_config.network_settings.name)
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings],
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name])
+
+ inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings,
+ keypair_settings=self.keypair_creator.keypair_settings)
+ self.inst_creators.append(inst_creator)
+
+ # block=True will force the create() method to block until the
+ vm_inst = inst_creator.create(block=True)
+ self.assertIsNotNone(vm_inst)
+
+ self.assertTrue(inst_creator.vm_active(block=True))
+ ip = inst_creator.get_port_ip(port_settings.name)
+ self.assertTrue(check_dhcp_lease(inst_creator, ip))
+ self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id)
+
+ inst_creator.add_floating_ip(FloatingIpConfig(
+ name=self.floating_ip_name, port_name=self.port_1_name,
+ router_name=self.pub_net_config.router_settings.name))
+
+ self.assertTrue(validate_ssh_client(inst_creator))
+
+ def test_ssh_client_fip_reverse_engineer(self):
+ """
+ Tests the ability to assign a floating IP to a reverse engineered
+ OpenStackVmInstance object. After the floating IP has been allocated
+ and assigned, this test will ensure that it can be accessed via SSH.
+ """
+ port_settings = PortConfig(
+ name=self.port_1_name,
+ network_name=self.pub_net_config.network_settings.name)
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings],
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name])
+
+ inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings,
+ keypair_settings=self.keypair_creator.keypair_settings)
+ self.inst_creators.append(inst_creator)
+
+ # block=True will force the create() method to block until the
+ vm_inst = inst_creator.create(block=True)
+ self.assertIsNotNone(vm_inst)
+
+ self.assertTrue(inst_creator.vm_active(block=True))
+
+ vm_os_creds = OSCreds(
+ auth_url=self.admin_os_creds.auth_url,
+ username=self.admin_os_creds.username,
+ password=self.admin_os_creds.password,
+ project_name=self.os_creds.project_name,
+ identity_api_version=self.os_creds.identity_api_version)
+ derived_inst_creator = create_instance.generate_creator(
+ vm_os_creds, vm_inst, self.image_creator.image_settings,
+ self.os_creds.project_name, self.keypair_creator.keypair_settings)
+
+ # Tests to ensure that a instance can be returned with an invalid
+ # image config object and admin credentials (when the 'admin' user has
+ # been added to the project) as this should not matter unless one
+ # needs to access the machine via ssh and its floating IP
+
+ # Invalid ImageConfig
+ derived_foo_image_creator = create_instance.generate_creator(
+ vm_os_creds, vm_inst, ImageConfig(
+ name='foo', image_user='bar', format='qcow2',
+ image_file='foo/bar'),
+ vm_os_creds.project_name)
+ self.assertIsNotNone(derived_foo_image_creator)
+ self.assertTrue(derived_foo_image_creator.vm_active())
+
+ # None ImageConfig
+ derived_none_image_creator = create_instance.generate_creator(
+ vm_os_creds, vm_inst, None, vm_os_creds.project_name)
+ self.assertIsNotNone(derived_none_image_creator)
+ self.assertTrue(derived_none_image_creator.vm_active())
+
+ derived_inst_creator.add_floating_ip(FloatingIpConfig(
+ name=self.floating_ip_name, port_name=self.port_1_name,
+ router_name=self.pub_net_config.router_settings.name))
+ self.inst_creators.append(derived_inst_creator)
+
+ self.assertTrue(validate_ssh_client(
+ derived_inst_creator, fip_name=self.floating_ip_name))
+
def test_ssh_client_fip_second_creator(self):
"""
Tests the ability to access a VM via SSH and a floating IP via a
creator that is identical to the original creator.
"""
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.pub_net_config.network_settings.name)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings],
- floating_ip_settings=[FloatingIpSettings(
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name],
+ floating_ip_settings=[FloatingIpConfig(
name=self.floating_ip_name, port_name=self.port_1_name,
router_name=self.pub_net_config.router_settings.name)])
@@ -794,8 +1128,6 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
ip = inst_creator.get_port_ip(port_settings.name)
self.assertTrue(check_dhcp_lease(inst_creator, ip))
- inst_creator.add_security_group(
- self.sec_grp_creator.get_security_group())
self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id)
self.assertTrue(validate_ssh_client(inst_creator))
@@ -808,6 +1140,235 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase):
self.assertTrue(validate_ssh_client(inst_creator2))
+class CreateInstanceIPv6NetworkTests(OSIntegrationTestCase):
+ """
+ Test for the CreateInstance class with a single NIC/Port with Floating IPs
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateImage object that is responsible for downloading
+ and creating an OS image file within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.keypair_priv_filepath = 'tmp/' + self.guid
+ self.keypair_pub_filepath = self.keypair_priv_filepath + '.pub'
+ self.keypair_name = self.guid + '-kp'
+ self.vm_inst_name = self.guid + '-inst'
+ self.port1_name = self.guid + 'port1'
+ self.port2_name = self.guid + 'port2'
+
+ # Initialize for tearDown()
+ self.image_creator = None
+ self.network_creator = None
+ self.router_creator = None
+ self.flavor_creator = None
+ self.keypair_creator = None
+ self.sec_grp_creator = None
+ self.inst_creator = None
+
+ os_image_settings = openstack_tests.cirros_image_settings(
+ name=self.guid + '-image', image_metadata=self.image_metadata)
+ try:
+ self.image_creator = OpenStackImage(
+ self.os_creds, os_image_settings)
+ self.image_creator.create()
+
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(
+ self.admin_os_creds, flavor_config)
+ self.flavor_creator.create()
+
+ self.keypair_creator = OpenStackKeypair(
+ self.os_creds, KeypairConfig(
+ name=self.keypair_name,
+ public_filepath=self.keypair_pub_filepath,
+ private_filepath=self.keypair_priv_filepath))
+ self.keypair_creator.create()
+
+ sec_grp_name = self.guid + '-sec-grp'
+ rule1 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.icmp)
+ rule2 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.tcp, port_range_min=22, port_range_max=22)
+ self.sec_grp_creator = OpenStackSecurityGroup(
+ self.os_creds,
+ SecurityGroupConfig(
+ name=sec_grp_name, rule_settings=[rule1, rule2]))
+ self.sec_grp_creator.create()
+ except Exception as e:
+ self.tearDown()
+ raise e
+
+ def tearDown(self):
+ """
+ Cleans the created object
+ """
+ if self.inst_creator:
+ try:
+ self.inst_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning VM instance with message '
+ '- %s', e)
+
+ if self.keypair_creator:
+ try:
+ self.keypair_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning keypair with message - %s',
+ e)
+
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning flavor with message - %s',
+ e)
+
+ if self.sec_grp_creator:
+ try:
+ self.sec_grp_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning security group with message'
+ ' - %s', e)
+
+ if self.router_creator:
+ try:
+ self.router_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning router with message - %s',
+ e)
+
+ if self.network_creator:
+ try:
+ self.network_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning network with message - %s',
+ e)
+
+ if self.image_creator and not self.image_creator.image_settings.exists:
+ try:
+ self.image_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning image with message - %s', e)
+
+ super(self.__class__, self).__clean__()
+
+ def test_v4fip_v6overlay(self):
+ """
+ Tests the ability to assign an IPv4 floating IP to an IPv6 overlay
+ network when the external network does not have an IPv6 subnet.
+ """
+ subnet_settings = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6)
+ network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[subnet_settings])
+ router_settings = RouterConfig(
+ name=self.guid + '-router', external_gateway=self.ext_net_name,
+ internal_subnets=[{'subnet': {
+ 'project_name': self.os_creds.project_name,
+ 'network_name': network_settings.name,
+ 'subnet_name': subnet_settings.name}}])
+
+ # Create Network
+ self.network_creator = OpenStackNetwork(
+ self.os_creds, network_settings)
+ self.network_creator.create()
+
+ # Create Router
+ self.router_creator = OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
+
+ port_settings = PortConfig(
+ name=self.port1_name, network_name=network_settings.name)
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings],
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name],
+ floating_ip_settings=[FloatingIpConfig(
+ name='fip1', port_name=self.port1_name,
+ router_name=router_settings.name)])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings,
+ keypair_settings=self.keypair_creator.keypair_settings)
+
+ with self.assertRaises(BadRequest):
+ self.inst_creator.create(block=True)
+
+ def test_fip_v4and6_overlay(self):
+ """
+ Tests the ability to assign an IPv4 floating IP to an IPv6 overlay
+ network when the external network does not have an IPv6 subnet.
+ """
+ subnet4_settings = SubnetConfig(
+ name=self.guid + '-subnet4', cidr='10.0.1.0/24',
+ ip_version=4)
+ subnet6_settings = SubnetConfig(
+ name=self.guid + '-subnet6', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6)
+ network_settings = NetworkConfig(
+ name=self.guid + '-net',
+ subnet_settings=[subnet4_settings, subnet6_settings])
+ router_settings = RouterConfig(
+ name=self.guid + '-router', external_gateway=self.ext_net_name,
+ internal_subnets=[{'subnet': {
+ 'project_name': self.os_creds.project_name,
+ 'network_name': network_settings.name,
+ 'subnet_name': subnet4_settings.name}}])
+
+ # Create Network
+ self.network_creator = OpenStackNetwork(
+ self.os_creds, network_settings)
+ self.network_creator.create()
+
+ # Create Router
+ self.router_creator = OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
+
+ port_settings = PortConfig(
+ name=self.port1_name, network_name=network_settings.name)
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings],
+ security_group_names=[self.sec_grp_creator.sec_grp_settings.name],
+ floating_ip_settings=[FloatingIpConfig(
+ name='fip1', port_name=self.port1_name,
+ router_name=router_settings.name)])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings,
+ keypair_settings=self.keypair_creator.keypair_settings)
+
+ self.inst_creator.create(block=True)
+ self.inst_creator.cloud_init_complete(block=True)
+ ssh_client = self.inst_creator.ssh_client()
+ self.assertIsNotNone(ssh_client)
+
+
class CreateInstancePortManipulationTests(OSIntegrationTestCase):
"""
Test for the CreateInstance class with a single NIC/Port where mac and IP
@@ -821,23 +1382,28 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
"""
super(self.__class__, self).__start__()
- guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.vm_inst_name = guid + '-inst'
- self.port_1_name = guid + 'port-1'
- self.port_2_name = guid + 'port-2'
- self.floating_ip_name = guid + 'fip1'
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.vm_inst_name = self.guid + '-inst'
+ self.port_1_name = self.guid + 'port-1'
+ self.port_2_name = self.guid + 'port-2'
+ self.floating_ip_name = self.guid + 'fip1'
# Initialize for tearDown()
self.image_creator = None
self.network_creator = None
+ self.network_creator2 = None
self.flavor_creator = None
self.inst_creator = None
self.net_config = openstack_tests.get_priv_net_config(
- net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
- router_name=guid + '-pub-router', external_net=self.ext_net_name)
+ project_name=self.os_creds.project_name,
+ net_name=self.guid + '-pub-net',
+ subnet_name=self.guid + '-pub-subnet',
+ router_name=self.guid + '-pub-router',
+ external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
os_image_settings = openstack_tests.cirros_image_settings(
- name=guid + '-image', image_metadata=self.image_metadata)
+ name=self.guid + '-image', image_metadata=self.image_metadata)
try:
# Create Image
@@ -851,10 +1417,11 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
self.network_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10,
- vcpus=2, metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
except Exception as e:
self.tearDown()
@@ -888,6 +1455,14 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
'Unexpected exception cleaning network with message - %s',
e)
+ if self.network_creator2:
+ try:
+ self.network_creator2.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning network with message - %s',
+ e)
+
if self.image_creator and not self.image_creator.image_settings.exists:
try:
self.image_creator.clean()
@@ -904,12 +1479,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
"""
ip = '10.55.0.101'
sub_settings = self.net_config.network_settings.subnet_settings
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip}])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -924,6 +1499,86 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
subnet_name=self.net_config.network_settings.subnet_settings[
0].name))
+ def test_set_one_port_two_ip_one_subnet(self):
+ """
+ Tests the creation of an OpenStack instance with a single port with a
+ two static IPs on a network with one subnet.
+ """
+ ip1 = '10.55.0.101'
+ ip2 = '10.55.0.102'
+ sub_settings = self.net_config.network_settings.subnet_settings
+ port_settings = PortConfig(
+ name=self.port_1_name,
+ network_name=self.net_config.network_settings.name,
+ ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip1},
+ {'subnet_name': sub_settings[0].name, 'ip': ip2}])
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings)
+ vm_inst = self.inst_creator.create(block=True)
+
+ self.assertEqual(ip1, vm_inst.ports[0].ips[0]['ip_address'])
+ self.assertEqual(self.network_creator.get_network().subnets[0].id,
+ vm_inst.ports[0].ips[0]['subnet_id'])
+ self.assertEqual(ip2, vm_inst.ports[0].ips[1]['ip_address'])
+ self.assertEqual(self.network_creator.get_network().subnets[0].id,
+ vm_inst.ports[0].ips[1]['subnet_id'])
+
+ def test_set_one_port_two_ip_two_subnets(self):
+ """
+ Tests the creation of an OpenStack instance with a single port with a
+ two static IPs on a network with one subnet.
+ """
+ net2_config = NetworkConfig(
+ name=self.guid + 'net2', subnets=[
+ SubnetConfig(name=self.guid + '-subnet1', cidr='10.55.0.0/24'),
+ SubnetConfig(name=self.guid + '-subnet2', cidr='10.65.0.0/24'),
+ ])
+
+ # Create Network
+ self.network_creator2 = OpenStackNetwork(self.os_creds, net2_config)
+ net2 = self.network_creator2.create()
+
+ ip1 = '10.55.0.101'
+ ip2 = '10.65.0.101'
+
+ port_settings = PortConfig(
+ name=self.port_1_name,
+ network_name=net2_config.name,
+ ip_addrs=[
+ {'subnet_name': net2_config.subnet_settings[0].name,
+ 'ip': ip1},
+ {'subnet_name': net2_config.subnet_settings[1].name,
+ 'ip': ip2}])
+
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings)
+ vm_inst = self.inst_creator.create(block=True)
+
+ subnet1_id = None
+ subnet2_id = None
+ for subnet in net2.subnets:
+ if subnet.name == net2_config.subnet_settings[0].name:
+ subnet1_id = subnet.id
+ if subnet.name == net2_config.subnet_settings[1].name:
+ subnet2_id = subnet.id
+ self.assertEqual(ip1, vm_inst.ports[0].ips[0]['ip_address'])
+ self.assertEqual(subnet1_id, vm_inst.ports[0].ips[0]['subnet_id'])
+ self.assertEqual(ip2, vm_inst.ports[0].ips[1]['ip_address'])
+ self.assertEqual(subnet2_id, vm_inst.ports[0].ips[1]['subnet_id'])
+
def test_set_custom_invalid_ip_one_subnet(self):
"""
Tests the creation of an OpenStack instance with a single port with a
@@ -931,12 +1586,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
"""
ip = '10.66.0.101'
sub_settings = self.net_config.network_settings.subnet_settings
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip}])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -954,12 +1609,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
the MAC address is assigned.
"""
mac_addr = '0a:1b:2c:3d:4e:5f'
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
mac_address=mac_addr)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -978,12 +1633,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
invalid MAC address value is being
assigned. This should raise an Exception
"""
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
mac_address='foo')
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -1003,13 +1658,13 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
ip = '10.55.0.101'
mac_addr = '0a:1b:2c:3d:4e:5f'
sub_settings = self.net_config.network_settings.subnet_settings
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
mac_address=mac_addr,
ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip}])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -1034,12 +1689,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
ip = '10.55.0.101'
mac_addr = '0a:1b:2c:3d:4e:5f'
pair = {'ip_address': ip, 'mac_address': mac_addr}
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
allowed_address_pairs=[pair])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -1066,12 +1721,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
pair = {'ip_address': ip, 'mac_address': mac_addr}
pairs = set()
pairs.add((ip, mac_addr))
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
allowed_address_pairs=[pair])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -1092,12 +1747,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase):
pair = {'ip_address': ip, 'mac_address': mac_addr}
pairs = set()
pairs.add((ip, mac_addr))
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_1_name,
network_name=self.net_config.network_settings.name,
allowed_address_pairs=[pair])
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -1132,7 +1787,9 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase):
self.inst_creators = list()
self.priv_net_config = openstack_tests.get_priv_net_config(
- net_name=guid + '-priv-net', subnet_name=guid + '-priv-subnet')
+ project_name=self.os_creds.project_name,
+ net_name=guid + '-priv-net', subnet_name=guid + '-priv-subnet',
+ netconf_override=self.netconf_override)
os_image_settings = openstack_tests.cirros_image_settings(
name=guid + '-image', image_metadata=self.image_metadata)
@@ -1144,10 +1801,11 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase):
self.network_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=512, disk=1,
+ vcpus=1, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=guid + '-flavor-name', ram=512, disk=1,
- vcpus=1, metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
# Create Image
@@ -1201,7 +1859,8 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase):
Tests the creation of OpenStack VM instances to each compute node.
"""
from snaps.openstack.utils import nova_utils
- nova = nova_utils.nova_client(self.admin_os_creds)
+ nova = nova_utils.nova_client(
+ self.admin_os_creds, self.admin_os_session)
zone_hosts = nova_utils.get_availability_zone_hosts(nova)
# Create Instance on each server/zone
@@ -1209,11 +1868,11 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase):
for zone in zone_hosts:
inst_name = self.vm_inst_name + '-' + zone
ctr += 1
- port_settings = PortSettings(
+ port_settings = PortConfig(
name=self.port_base_name + '-' + str(ctr),
network_name=self.priv_net_config.network_settings.name)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=inst_name,
flavor=self.flavor_creator.flavor_settings.name,
availability_zone=zone,
@@ -1222,7 +1881,11 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase):
self.admin_os_creds, instance_settings,
self.image_creator.image_settings)
self.inst_creators.append(inst_creator)
- inst_creator.create()
+ inst_creator.create(block=True)
+ avail_zone = inst_creator.get_vm_inst().availability_zone
+ self.assertTrue(avail_zone in zone)
+ compute_host = inst_creator.get_vm_inst().compute_host
+ self.assertTrue(compute_host in zone)
# Validate instances to ensure they've been deployed to the correct
# server
@@ -1237,235 +1900,6 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase):
index += 1
-class CreateInstancePubPrivNetTests(OSIntegrationTestCase):
- """
- Test for the CreateInstance class with two NIC/Ports, eth0 with floating IP
- and eth1 w/o.
- These tests require a Centos image
- """
-
- def setUp(self):
- """
- Instantiates the CreateImage object that is responsible for downloading
- and creating an OS image file within OpenStack
- """
- super(self.__class__, self).__start__()
-
- self.nova = nova_utils.nova_client(self.os_creds)
-
- # Initialize for tearDown()
- self.image_creator = None
- self.network_creators = list()
- self.router_creators = list()
- self.flavor_creator = None
- self.keypair_creator = None
- self.sec_grp_creator = None
- self.inst_creator = None
-
- self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.keypair_priv_filepath = 'tmp/' + self.guid
- self.keypair_pub_filepath = self.keypair_priv_filepath + '.pub'
- self.keypair_name = self.guid + '-kp'
- self.vm_inst_name = self.guid + '-inst'
- self.port_1_name = self.guid + '-port-1'
- self.port_2_name = self.guid + '-port-2'
- self.floating_ip_name = self.guid + 'fip1'
- self.priv_net_config = openstack_tests.get_priv_net_config(
- net_name=self.guid + '-priv-net',
- subnet_name=self.guid + '-priv-subnet',
- router_name=self.guid + '-priv-router',
- external_net=self.ext_net_name)
- self.pub_net_config = openstack_tests.get_pub_net_config(
- net_name=self.guid + '-pub-net',
- subnet_name=self.guid + '-pub-subnet',
- router_name=self.guid + '-pub-router',
- external_net=self.ext_net_name)
-
- image_name = self.__class__.__name__ + '-' + str(uuid.uuid4())
- os_image_settings = openstack_tests.centos_image_settings(
- name=image_name, image_metadata=self.image_metadata)
-
- try:
- # Create Image
- self.image_creator = OpenStackImage(self.os_creds,
- os_image_settings)
- self.image_creator.create()
-
- # First network is public
- self.network_creators.append(OpenStackNetwork(
- self.os_creds, self.pub_net_config.network_settings))
- # Second network is private
- self.network_creators.append(OpenStackNetwork(
- self.os_creds, self.priv_net_config.network_settings))
- for network_creator in self.network_creators:
- network_creator.create()
-
- self.router_creators.append(OpenStackRouter(
- self.os_creds, self.pub_net_config.router_settings))
- self.router_creators.append(OpenStackRouter(
- self.os_creds, self.priv_net_config.router_settings))
-
- # Create Routers
- for router_creator in self.router_creators:
- router_creator.create()
-
- # Create Flavor
- self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=self.guid + '-flavor-name', ram=512,
- disk=10, vcpus=2,
- metadata=self.flavor_metadata))
- self.flavor_creator.create()
-
- # Create Keypair
- self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
- name=self.keypair_name,
- public_filepath=self.keypair_pub_filepath,
- private_filepath=self.keypair_priv_filepath))
- self.keypair_creator.create()
-
- sec_grp_name = self.guid + '-sec-grp'
- rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.icmp)
- rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.tcp,
- port_range_min=22,
- port_range_max=22)
- self.sec_grp_creator = OpenStackSecurityGroup(
- self.os_creds,
- SecurityGroupSettings(name=sec_grp_name,
- rule_settings=[rule1, rule2]))
- self.sec_grp_creator.create()
- except:
- self.tearDown()
- raise
-
- def tearDown(self):
- """
- Cleans the created objects
- """
- if self.inst_creator:
- try:
- self.inst_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning VM instance with message '
- '- %s', e)
-
- if self.keypair_creator:
- try:
- self.keypair_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning keypair with message - %s',
- e)
-
- if os.path.isfile(self.keypair_pub_filepath):
- os.remove(self.keypair_pub_filepath)
-
- if os.path.isfile(self.keypair_priv_filepath):
- os.remove(self.keypair_priv_filepath)
-
- if self.flavor_creator:
- try:
- self.flavor_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning flavor with message - %s',
- e)
-
- for router_creator in self.router_creators:
- try:
- router_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning router with message - %s',
- e)
-
- for network_creator in self.network_creators:
- try:
- network_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning network with message - %s',
- e)
-
- if self.sec_grp_creator:
- try:
- self.sec_grp_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning security group with message'
- ' - %s', e)
-
- if self.image_creator and not self.image_creator.image_settings.exists:
- try:
- self.image_creator.clean()
- except Exception as e:
- logger.error(
- 'Unexpected exception cleaning image with message - %s', e)
-
- super(self.__class__, self).__clean__()
-
- def test_dual_ports_dhcp(self):
- """
- Tests the creation of an OpenStack instance with a dual ports/NICs with
- a DHCP assigned IP.
- NOTE: This test and any others that call ansible will most likely fail
- unless you do one of two things:
- 1. Have a ~/.ansible.cfg (or alternate means) to
- set host_key_checking = False
- 2. Set the following environment variable in your executing shell:
- ANSIBLE_HOST_KEY_CHECKING=False
- Should this not be performed, the creation of the host ssh key will
- cause your ansible calls to fail.
- """
- # Create ports/NICs for instance
- ports_settings = []
- ctr = 1
- for network_creator in self.network_creators:
- ports_settings.append(PortSettings(
- name=self.guid + '-port-' + str(ctr),
- network_name=network_creator.network_settings.name))
- ctr += 1
-
- # Create instance
- instance_settings = VmInstanceSettings(
- name=self.vm_inst_name,
- flavor=self.flavor_creator.flavor_settings.name,
- port_settings=ports_settings,
- floating_ip_settings=[FloatingIpSettings(
- name=self.floating_ip_name, port_name=self.port_1_name,
- router_name=self.pub_net_config.router_settings.name)])
-
- self.inst_creator = OpenStackVmInstance(
- self.os_creds, instance_settings,
- self.image_creator.image_settings,
- keypair_settings=self.keypair_creator.keypair_settings)
-
- vm_inst = self.inst_creator.create(block=True)
-
- self.assertEqual(vm_inst.id, self.inst_creator.get_vm_inst().id)
-
- # Effectively blocks until VM has been properly activated
- self.assertTrue(self.inst_creator.vm_active(block=True))
-
- ip = self.inst_creator.get_port_ip(ports_settings[0].name)
- self.assertTrue(check_dhcp_lease(self.inst_creator, ip))
-
- # Add security group to VM
- self.inst_creator.add_security_group(
- self.sec_grp_creator.get_security_group())
-
- # Effectively blocks until VM's ssh port has been opened
- self.assertTrue(self.inst_creator.vm_ssh_active(block=True))
-
- self.assertEqual(0, self.inst_creator.config_nics())
-
-
class InstanceSecurityGroupTests(OSIntegrationTestCase):
"""
Tests that include, add, and remove security groups from VM instances
@@ -1480,7 +1914,7 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.vm_inst_name = self.guid + '-inst'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
os_image_settings = openstack_tests.cirros_image_settings(
name=self.guid + '-image', image_metadata=self.image_metadata)
@@ -1490,10 +1924,12 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
self.floating_ip_name = self.guid + 'fip1'
net_config = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
net_name=self.guid + '-pub-net',
subnet_name=self.guid + '-pub-subnet',
router_name=self.guid + '-pub-router',
- external_net=self.ext_net_name)
+ external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
# Initialize for tearDown()
self.image_creator = None
@@ -1515,14 +1951,14 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
self.network_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=self.guid + '-flavor-name', ram=256,
- disk=10, vcpus=2,
- metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
- self.port_settings = PortSettings(
+ self.port_settings = PortConfig(
name=self.guid + '-port',
network_name=net_config.network_settings.name)
except Exception as e:
@@ -1579,7 +2015,7 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
Tests the addition of a security group created after the instance.
"""
# Create instance
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -1590,8 +2026,8 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
self.assertIsNotNone(vm_inst)
# Create security group object to add to instance
- sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name',
- description='hello group')
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group')
sec_grp_creator = OpenStackSecurityGroup(self.os_creds,
sec_grp_settings)
sec_grp = sec_grp_creator.create()
@@ -1613,7 +2049,7 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
Tests the addition of a security group that no longer exists.
"""
# Create instance
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -1624,8 +2060,8 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
self.assertIsNotNone(vm_inst)
# Create security group object to add to instance
- sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name',
- description='hello group')
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group')
sec_grp_creator = OpenStackSecurityGroup(self.os_creds,
sec_grp_settings)
sec_grp = sec_grp_creator.create()
@@ -1649,15 +2085,15 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
instance.
"""
# Create security group object to add to instance
- sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name',
- description='hello group')
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group')
sec_grp_creator = OpenStackSecurityGroup(self.os_creds,
sec_grp_settings)
sec_grp = sec_grp_creator.create()
self.sec_grp_creators.append(sec_grp_creator)
# Create instance
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
security_group_names=[sec_grp_settings.name],
@@ -1685,15 +2121,15 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
place.
"""
# Create security group object to add to instance
- sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name',
- description='hello group')
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group')
sec_grp_creator = OpenStackSecurityGroup(self.os_creds,
sec_grp_settings)
sec_grp = sec_grp_creator.create()
self.sec_grp_creators.append(sec_grp_creator)
# Create instance
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -1720,15 +2156,15 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase):
instance.
"""
# Create security group object to add to instance
- sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name',
- description='hello group')
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group')
sec_grp_creator = OpenStackSecurityGroup(self.os_creds,
sec_grp_settings)
sec_grp = sec_grp_creator.create()
self.sec_grp_creators.append(sec_grp_creator)
# Create instance
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
security_group_names=[sec_grp_settings.name],
@@ -1766,17 +2202,18 @@ def inst_has_sec_grp(nova, vm_inst, sec_grp_name):
return False
-def validate_ssh_client(instance_creator):
+def validate_ssh_client(instance_creator, fip_name=None):
"""
Returns True if instance_creator returns an SSH client that is valid
:param instance_creator: the object responsible for creating the VM
instance
+ :param fip_name: the name of the floating IP to use
:return: T/F
"""
ssh_active = instance_creator.vm_ssh_active(block=True)
if ssh_active:
- ssh_client = instance_creator.ssh_client()
+ ssh_client = instance_creator.ssh_client(fip_name=fip_name)
if ssh_client:
try:
out = ssh_client.exec_command('pwd')[1]
@@ -1809,11 +2246,13 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.image_name = guid
self.vm_inst_name = guid + '-inst'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
net_config = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
- router_name=guid + '-pub-router', external_net=self.ext_net_name)
+ router_name=guid + '-pub-router', external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
# Initialize for tearDown()
self.image_creator = None
@@ -1849,10 +2288,11 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase):
self.image_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10,
- vcpus=2, metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
# Create Network
@@ -1860,7 +2300,7 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase):
self.os_creds, net_config.network_settings)
self.network_creator.create()
- self.port_settings = PortSettings(
+ self.port_settings = PortConfig(
name=guid + '-port',
network_name=net_config.network_settings.name)
except Exception as e:
@@ -1908,7 +2348,7 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase):
"""
Tests the creation of an OpenStack instance from a 3-part image.
"""
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -1953,9 +2393,10 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.inst_creator = None
self.priv_net_config = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
net_name=self.guid + '-priv-net',
subnet_name=self.guid + '-priv-subnet')
- self.port_settings = PortSettings(
+ self.port_settings = PortConfig(
name=self.port_1_name,
network_name=self.priv_net_config.network_settings.name)
@@ -1970,11 +2411,11 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.network_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.os_creds,
- FlavorSettings(
- name=self.guid + '-flavor-name', ram=256, disk=10,
- vcpus=1))
+ self.os_creds, flavor_config)
self.flavor_creator.create()
except Exception as e:
self.tearDown()
@@ -2018,6 +2459,8 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
if os.path.exists(self.tmpDir) and os.path.isdir(self.tmpDir):
shutil.rmtree(self.tmpDir)
+ super(self.__class__, self).__clean__()
+
def test_inst_from_file_image_simple_flat(self):
"""
Creates a VM instance from a locally sourced file image using simply
@@ -2043,7 +2486,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.image_creator = OpenStackImage(self.os_creds, os_image_settings)
self.image_creator.create()
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2079,7 +2522,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.image_creator = OpenStackImage(self.os_creds, os_image_settings)
self.image_creator.create()
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2104,14 +2547,14 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
image_settings = self.image_creator.image_settings
test_image_creator = OpenStackImage(
self.os_creds,
- ImageSettings(name=image_settings.name,
- image_user=image_settings.image_user,
- exists=True))
+ ImageConfig(
+ name=image_settings.name, image_user=image_settings.image_user,
+ exists=True))
test_image_creator.create()
self.assertEqual(self.image_creator.get_image().id,
test_image_creator.get_image().id)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2146,7 +2589,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
test_image = OpenStackImage(self.os_creds, test_image_settings)
test_image.create()
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2226,7 +2669,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.image_creator = OpenStackImage(self.os_creds, os_image_settings)
self.image_creator.create()
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2296,7 +2739,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.assertIsNotNone(self.image_creator.get_kernel_image())
self.assertIsNotNone(self.image_creator.get_ramdisk_image())
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2366,7 +2809,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
self.assertIsNotNone(self.image_creator.get_kernel_image())
self.assertIsNotNone(self.image_creator.get_ramdisk_image())
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2399,14 +2842,14 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase):
image_settings = self.image_creator.image_settings
test_image_creator = OpenStackImage(
self.os_creds,
- ImageSettings(name=image_settings.name,
- image_user=image_settings.image_user,
- exists=True))
+ ImageConfig(
+ name=image_settings.name, image_user=image_settings.image_user,
+ exists=True))
test_image_creator.create()
self.assertEqual(self.image_creator.get_image().id,
test_image_creator.get_image().id)
- instance_settings = VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[self.port_settings])
@@ -2438,7 +2881,7 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
self.ip1 = '10.200.201.5'
self.ip2 = '10.200.202.5'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
# Initialize for tearDown()
self.image_creator = None
@@ -2453,16 +2896,16 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
self.vm_inst2_name = self.guid + '-inst2'
self.port_1_name = self.guid + '-vm1-port'
self.port_2_name = self.guid + '-vm2-port'
- self.net_config_1 = NetworkSettings(
+ self.net_config_1 = NetworkConfig(
name=self.guid + '-net1',
subnet_settings=[
- create_network.SubnetSettings(
+ create_network.SubnetConfig(
cidr=cidr1, name=self.guid + '-subnet1',
gateway_ip=static_gateway_ip1)])
- self.net_config_2 = NetworkSettings(
+ self.net_config_2 = NetworkConfig(
name=self.guid + '-net2',
subnet_settings=[
- create_network.SubnetSettings(
+ create_network.SubnetConfig(
cidr=cidr2, name=self.guid + '-subnet2',
gateway_ip=static_gateway_ip2)])
@@ -2472,13 +2915,14 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
try:
# Create Image
- self.image_creator = OpenStackImage(self.os_creds,
- os_image_settings)
+ self.image_creator = OpenStackImage(
+ self.os_creds, os_image_settings)
self.image_creator.create()
# First network is public
self.network_creators.append(OpenStackNetwork(
self.os_creds, self.net_config_1))
+
# Second network is private
self.network_creators.append(OpenStackNetwork(
self.os_creds, self.net_config_2))
@@ -2486,47 +2930,47 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
network_creator.create()
port_settings = [
- create_network.PortSettings(
+ PortConfig(
name=self.guid + '-router-port1',
ip_addrs=[{
'subnet_name':
self.net_config_1.subnet_settings[0].name,
'ip': static_gateway_ip1
}],
- network_name=self.net_config_1.name,
- project_name=self.os_creds.project_name),
- create_network.PortSettings(
+ network_name=self.net_config_1.name),
+ PortConfig(
name=self.guid + '-router-port2',
ip_addrs=[{
'subnet_name':
self.net_config_2.subnet_settings[0].name,
'ip': static_gateway_ip2
}],
- network_name=self.net_config_2.name,
- project_name=self.os_creds.project_name)]
+ network_name=self.net_config_2.name)]
- router_settings = RouterSettings(name=self.guid + '-pub-router',
- port_settings=port_settings)
- self.router_creator = create_router.OpenStackRouter(
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', port_settings=port_settings)
+ self.router_creator = OpenStackRouter(
self.os_creds, router_settings)
self.router_creator.create()
- # Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=512, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=self.guid + '-flavor-name', ram=512,
- disk=10, vcpus=2,
- metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
- sec_grp_name = self.guid + '-sec-grp'
- rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.icmp)
+ self.sec_grp_name = self.guid + '-sec-grp'
+ rule1 = SecurityGroupRuleConfig(
+ sec_grp_name=self.sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.icmp)
+ rule2 = SecurityGroupRuleConfig(
+ sec_grp_name=self.sec_grp_name, direction=Direction.egress,
+ protocol=Protocol.icmp)
self.sec_grp_creator = OpenStackSecurityGroup(
self.os_creds,
- SecurityGroupSettings(name=sec_grp_name,
- rule_settings=[rule1]))
+ SecurityGroupConfig(
+ name=self.sec_grp_name, rule_settings=[rule1, rule2]))
self.sec_grp_creator.create()
except:
self.tearDown()
@@ -2596,17 +3040,18 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
ports_settings = []
ctr = 1
for network_creator in self.network_creators:
- ports_settings.append(PortSettings(
+ ports_settings.append(PortConfig(
name=self.guid + '-port-' + str(ctr),
network_name=network_creator.network_settings.name))
ctr += 1
# Configure instances
- instance1_settings = VmInstanceSettings(
+ instance1_settings = VmInstanceConfig(
name=self.vm_inst1_name,
flavor=self.flavor_creator.flavor_settings.name,
userdata=_get_ping_userdata(self.ip2),
- port_settings=[PortSettings(
+ security_group_names=self.sec_grp_name,
+ port_settings=[PortConfig(
name=self.port_1_name,
ip_addrs=[{
'subnet_name':
@@ -2614,11 +3059,12 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
'ip': self.ip1
}],
network_name=self.network_creators[0].network_settings.name)])
- instance2_settings = VmInstanceSettings(
+ instance2_settings = VmInstanceConfig(
name=self.vm_inst2_name,
flavor=self.flavor_creator.flavor_settings.name,
userdata=_get_ping_userdata(self.ip1),
- port_settings=[PortSettings(
+ security_group_names=self.sec_grp_name,
+ port_settings=[PortConfig(
name=self.port_2_name,
ip_addrs=[{
'subnet_name':
@@ -2647,6 +3093,188 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase):
self.assertTrue(check_ping(self.inst_creators[1]))
+class CreateInstanceVolumeTests(OSIntegrationTestCase):
+ """
+ Simple instance creation with an attached volume
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateImage object that is responsible for downloading
+ and creating an OS image file
+ within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.vm_inst_name = guid + '-inst'
+ self.nova = nova_utils.nova_client(
+ self.os_creds, self.os_session)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ os_image_settings = openstack_tests.cirros_image_settings(
+ name=guid + '-image', image_metadata=self.image_metadata)
+
+ net_config = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
+ net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
+ router_name=guid + '-pub-router', external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
+
+ self.volume_settings1 = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(guid) + '-1')
+ self.volume_settings2 = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(guid) + '-2')
+
+ # Initialize for tearDown()
+ self.image_creator = None
+ self.flavor_creator = None
+
+ self.network_creator = None
+ self.inst_creator = None
+ self.volume_creator1 = None
+ self.volume_creator2 = None
+
+ try:
+ # Create Image
+ self.image_creator = OpenStackImage(self.os_creds,
+ os_image_settings)
+ self.image_creator.create()
+
+ # Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=256, disk=1,
+ vcpus=2, metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(
+ self.admin_os_creds, flavor_config)
+ self.flavor_creator.create()
+
+ # Create Network
+ self.network_creator = OpenStackNetwork(
+ self.os_creds, net_config.network_settings)
+ self.network_creator.create()
+
+ self.port_settings = PortConfig(
+ name=guid + '-port',
+ network_name=net_config.network_settings.name)
+
+ self.volume_creator1 = OpenStackVolume(
+ self.os_creds, self.volume_settings1)
+ self.volume_creator1.create(block=True)
+
+ self.volume_creator2 = OpenStackVolume(
+ self.os_creds, self.volume_settings2)
+ self.volume_creator2.create(block=True)
+
+ except Exception as e:
+ self.tearDown()
+ raise e
+
+ def tearDown(self):
+ """
+ Cleans the created object
+ """
+ if self.inst_creator:
+ try:
+ self.inst_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning VM instance with message '
+ '- %s', e)
+
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning flavor with message - %s',
+ e)
+
+ if self.network_creator:
+ try:
+ self.network_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning network with message - %s',
+ e)
+
+ if self.volume_creator2:
+ try:
+ self.volume_creator2.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning volume with message - %s',
+ e)
+
+ if self.volume_creator1:
+ try:
+ self.volume_creator1.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning volume with message - %s',
+ e)
+
+ if self.image_creator and not self.image_creator.image_settings.exists:
+ try:
+ self.image_creator.clean()
+ except Exception as e:
+ logger.error(
+ 'Unexpected exception cleaning image with message - %s', e)
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_instance_with_one_volume(self):
+ """
+ Tests the creation of an OpenStack instance with a single volume.
+ """
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[self.port_settings],
+ volume_names=[self.volume_settings1.name])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings)
+
+ vm_inst = self.inst_creator.create(block=True)
+ self.assertIsNotNone(nova_utils.get_server(
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=instance_settings))
+
+ self.assertIsNotNone(vm_inst)
+ self.assertEqual(1, len(vm_inst.volume_ids))
+ self.assertEqual(self.volume_creator1.get_volume().id,
+ vm_inst.volume_ids[0]['id'])
+
+ def test_create_instance_with_two_volumes(self):
+ """
+ Tests the creation of an OpenStack instance with a single volume.
+ """
+ instance_settings = VmInstanceConfig(
+ name=self.vm_inst_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[self.port_settings],
+ volume_names=[self.volume_settings1.name,
+ self.volume_settings2.name])
+
+ self.inst_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings,
+ self.image_creator.image_settings)
+
+ vm_inst = self.inst_creator.create(block=True)
+ self.assertIsNotNone(nova_utils.get_server(
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=instance_settings))
+
+ self.assertIsNotNone(vm_inst)
+ self.assertEqual(2, len(vm_inst.volume_ids))
+ self.assertEqual(self.volume_creator1.get_volume().id,
+ vm_inst.volume_ids[0]['id'])
+ self.assertEqual(self.volume_creator2.get_volume().id,
+ vm_inst.volume_ids[1]['id'])
+
+
def check_dhcp_lease(inst_creator, ip, timeout=160):
"""
Returns true if the expected DHCP lease has been acquired
diff --git a/snaps/openstack/tests/create_keypairs_tests.py b/snaps/openstack/tests/create_keypairs_tests.py
index d2de6fe..44214d0 100644
--- a/snaps/openstack/tests/create_keypairs_tests.py
+++ b/snaps/openstack/tests/create_keypairs_tests.py
@@ -18,8 +18,9 @@ import uuid
import os
from snaps import file_utils
+from snaps.config.keypair import KeypairConfig, KeypairConfigError
from snaps.openstack.create_keypairs import (
- KeypairSettings, OpenStackKeypair, KeypairSettingsError)
+ KeypairSettings, OpenStackKeypair)
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
from snaps.openstack.utils import nova_utils
@@ -32,15 +33,15 @@ class KeypairSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(KeypairSettingsError):
+ with self.assertRaises(KeypairConfigError):
KeypairSettings()
def test_empty_config(self):
- with self.assertRaises(KeypairSettingsError):
+ with self.assertRaises(KeypairConfigError):
KeypairSettings(**dict())
def test_small_key_size(self):
- with self.assertRaises(KeypairSettingsError):
+ with self.assertRaises(KeypairConfigError):
KeypairSettings(name='foo', key_size=511)
def test_name_only(self):
@@ -199,7 +200,7 @@ class CreateKeypairsTests(OSIntegrationTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.priv_file_path = 'tmp/' + guid
self.pub_file_path = self.priv_file_path + '.pub'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
self.keypair_name = guid
self.keypair_creator = None
@@ -228,7 +229,7 @@ class CreateKeypairsTests(OSIntegrationTestCase):
Tests the creation of a generated keypair without saving to file
:return:
"""
- self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairSettings(
+ self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairConfig(
name=self.keypair_name))
self.keypair_creator.create()
@@ -241,7 +242,7 @@ class CreateKeypairsTests(OSIntegrationTestCase):
Tests the creation of a generated keypair without saving to file
:return:
"""
- self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairSettings(
+ self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairConfig(
name=self.keypair_name, key_size=10000))
self.keypair_creator.create()
@@ -255,7 +256,7 @@ class CreateKeypairsTests(OSIntegrationTestCase):
clean() does not raise an Exception.
"""
# Create Image
- self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairSettings(
+ self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairConfig(
name=self.keypair_name))
created_keypair = self.keypair_creator.create()
self.assertIsNotNone(created_keypair)
@@ -277,8 +278,8 @@ class CreateKeypairsTests(OSIntegrationTestCase):
:return:
"""
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(name=self.keypair_name,
- public_filepath=self.pub_file_path))
+ self.os_creds, KeypairConfig(
+ name=self.keypair_name, public_filepath=self.pub_file_path))
self.keypair_creator.create()
keypair = nova_utils.keypair_exists(self.nova,
@@ -302,7 +303,7 @@ class CreateKeypairsTests(OSIntegrationTestCase):
:return:
"""
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name, public_filepath=self.pub_file_path,
private_filepath=self.priv_file_path))
self.keypair_creator.create()
@@ -335,8 +336,8 @@ class CreateKeypairsTests(OSIntegrationTestCase):
file_utils.save_keys_to_files(keys=keys,
pub_file_path=self.pub_file_path)
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(name=self.keypair_name,
- public_filepath=self.pub_file_path))
+ self.os_creds, KeypairConfig(
+ name=self.keypair_name, public_filepath=self.pub_file_path))
self.keypair_creator.create()
keypair = nova_utils.keypair_exists(self.nova,
@@ -369,7 +370,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.priv_file_path = 'tmp/' + guid
self.pub_file_path = self.priv_file_path + '.pub'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
self.keypair_name = guid
self.keypair_creator = None
@@ -400,7 +401,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase):
:return:
"""
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name, public_filepath=self.pub_file_path,
private_filepath=self.priv_file_path))
self.keypair_creator.create()
@@ -416,7 +417,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase):
:return:
"""
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name, public_filepath=self.pub_file_path,
private_filepath=self.priv_file_path, delete_on_clean=True))
self.keypair_creator.create()
@@ -432,7 +433,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase):
:return:
"""
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name, public_filepath=self.pub_file_path,
private_filepath=self.priv_file_path, delete_on_clean=False))
self.keypair_creator.create()
@@ -452,7 +453,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase):
keys=keys, pub_file_path=self.pub_file_path,
priv_file_path=self.priv_file_path)
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name, public_filepath=self.pub_file_path,
private_filepath=self.priv_file_path, delete_on_clean=False))
self.keypair_creator.create()
@@ -472,7 +473,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase):
keys=keys, pub_file_path=self.pub_file_path,
priv_file_path=self.priv_file_path)
self.keypair_creator = OpenStackKeypair(
- self.os_creds, KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name, public_filepath=self.pub_file_path,
private_filepath=self.priv_file_path, delete_on_clean=True))
self.keypair_creator.create()
diff --git a/snaps/openstack/tests/create_network_tests.py b/snaps/openstack/tests/create_network_tests.py
index 9cee130..26c57bd 100644
--- a/snaps/openstack/tests/create_network_tests.py
+++ b/snaps/openstack/tests/create_network_tests.py
@@ -15,17 +15,20 @@
import unittest
import uuid
+from neutronclient.common.exceptions import BadRequest
+
+from snaps.config.network import (
+ NetworkConfig, SubnetConfig, SubnetConfigError, NetworkConfigError,
+ PortConfigError, IPv6Mode)
from snaps.openstack import create_router
-from snaps.openstack.create_network import (OpenStackNetwork, NetworkSettings,
- SubnetSettings, PortSettings,
- NetworkSettingsError,
- SubnetSettingsError,
- PortSettingsError)
+from snaps.openstack.create_network import (
+ OpenStackNetwork, NetworkSettings, SubnetSettings, PortSettings)
from snaps.openstack.tests import openstack_tests
-from snaps.openstack.tests.os_source_file_test import (OSIntegrationTestCase,
- OSComponentTestCase)
-from snaps.openstack.utils import neutron_utils
+from snaps.openstack.tests.os_source_file_test import (
+ OSIntegrationTestCase, OSComponentTestCase)
+from snaps.openstack.utils import neutron_utils, keystone_utils
from snaps.openstack.utils.tests import neutron_utils_tests
+from snaps.openstack.create_network import IPv6Mode as IPv6Mode_old
__author__ = 'spisarski'
@@ -36,11 +39,11 @@ class NetworkSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(NetworkSettingsError):
+ with self.assertRaises(NetworkConfigError):
NetworkSettings()
def test_empty_config(self):
- with self.assertRaises(NetworkSettingsError):
+ with self.assertRaises(NetworkConfigError):
NetworkSettings(**dict())
def test_name_only(self):
@@ -111,19 +114,19 @@ class SubnetSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(SubnetSettingsError):
+ with self.assertRaises(SubnetConfigError):
SubnetSettings()
def test_empty_config(self):
- with self.assertRaises(SubnetSettingsError):
+ with self.assertRaises(SubnetConfigError):
SubnetSettings(**dict())
def test_name_only(self):
- with self.assertRaises(SubnetSettingsError):
+ with self.assertRaises(SubnetConfigError):
SubnetSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(SubnetSettingsError):
+ with self.assertRaises(SubnetConfigError):
SubnetSettings(**{'name': 'foo'})
def test_name_cidr_only(self):
@@ -135,8 +138,7 @@ class SubnetSettingsUnitTests(unittest.TestCase):
self.assertIsNone(settings.start)
self.assertIsNone(settings.end)
self.assertIsNone(settings.enable_dhcp)
- self.assertEqual(1, len(settings.dns_nameservers))
- self.assertEqual('8.8.8.8', settings.dns_nameservers[0])
+ self.assertEqual(0, len(settings.dns_nameservers))
self.assertIsNone(settings.host_routes)
self.assertIsNone(settings.destination)
self.assertIsNone(settings.nexthop)
@@ -153,26 +155,22 @@ class SubnetSettingsUnitTests(unittest.TestCase):
self.assertIsNone(settings.end)
self.assertIsNone(settings.gateway_ip)
self.assertIsNone(settings.enable_dhcp)
- self.assertEqual(1, len(settings.dns_nameservers))
- self.assertEqual('8.8.8.8', settings.dns_nameservers[0])
+ self.assertEqual(0, len(settings.dns_nameservers))
self.assertIsNone(settings.host_routes)
self.assertIsNone(settings.destination)
self.assertIsNone(settings.nexthop)
self.assertIsNone(settings.ipv6_ra_mode)
self.assertIsNone(settings.ipv6_address_mode)
- def test_all(self):
+ def test_all_string_enums(self):
host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'}
- settings = SubnetSettings(name='foo', cidr='10.0.0.0/24', ip_version=6,
- project_name='bar-project',
- start='10.0.0.2', end='10.0.0.101',
- gateway_ip='10.0.0.1', enable_dhcp=False,
- dns_nameservers=['8.8.8.8'],
- host_routes=[host_routes],
- destination='dest',
- nexthop='hop',
- ipv6_ra_mode='dhcpv6-stateful',
- ipv6_address_mode='slaac')
+ settings = SubnetSettings(
+ name='foo', cidr='10.0.0.0/24', ip_version=6,
+ project_name='bar-project', start='10.0.0.2', end='10.0.0.101',
+ gateway_ip='10.0.0.1', enable_dhcp=False,
+ dns_nameservers=['8.8.8.8'], host_routes=[host_routes],
+ destination='dest', nexthop='hop', ipv6_ra_mode='dhcpv6-stateful',
+ ipv6_address_mode='slaac')
self.assertEqual('foo', settings.name)
self.assertEqual('10.0.0.0/24', settings.cidr)
self.assertEqual(6, settings.ip_version)
@@ -187,8 +185,38 @@ class SubnetSettingsUnitTests(unittest.TestCase):
self.assertEqual(host_routes, settings.host_routes[0])
self.assertEqual('dest', settings.destination)
self.assertEqual('hop', settings.nexthop)
- self.assertEqual('dhcpv6-stateful', settings.ipv6_ra_mode)
- self.assertEqual('slaac', settings.ipv6_address_mode)
+ self.assertEqual(IPv6Mode_old.stateful.value,
+ settings.ipv6_ra_mode.value)
+ self.assertEqual(IPv6Mode_old.slaac.value,
+ settings.ipv6_address_mode.value)
+
+ def test_all_type_enums(self):
+ host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'}
+ settings = SubnetSettings(
+ name='foo', cidr='10.0.0.0/24', ip_version=6,
+ project_name='bar-project', start='10.0.0.2', end='10.0.0.101',
+ gateway_ip='10.0.0.1', enable_dhcp=False,
+ dns_nameservers=['8.8.8.8'], host_routes=[host_routes],
+ destination='dest', nexthop='hop',
+ ipv6_ra_mode=IPv6Mode_old.stateful,
+ ipv6_address_mode=IPv6Mode.slaac)
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('10.0.0.0/24', settings.cidr)
+ self.assertEqual(6, settings.ip_version)
+ self.assertEqual('bar-project', settings.project_name)
+ self.assertEqual('10.0.0.2', settings.start)
+ self.assertEqual('10.0.0.101', settings.end)
+ self.assertEqual('10.0.0.1', settings.gateway_ip)
+ self.assertEqual(False, settings.enable_dhcp)
+ self.assertEqual(1, len(settings.dns_nameservers))
+ self.assertEqual('8.8.8.8', settings.dns_nameservers[0])
+ self.assertEqual(1, len(settings.host_routes))
+ self.assertEqual(host_routes, settings.host_routes[0])
+ self.assertEqual('dest', settings.destination)
+ self.assertEqual('hop', settings.nexthop)
+ self.assertEqual(IPv6Mode.stateful.value, settings.ipv6_ra_mode.value)
+ self.assertEqual(IPv6Mode.slaac.value,
+ settings.ipv6_address_mode.value)
def test_config_all(self):
host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'}
@@ -199,7 +227,7 @@ class SubnetSettingsUnitTests(unittest.TestCase):
'gateway_ip': '10.0.0.1', 'enable_dhcp': False,
'dns_nameservers': ['8.8.8.8'], 'host_routes': [host_routes],
'destination': 'dest', 'nexthop': 'hop',
- 'ipv6_ra_mode': 'dhcpv6-stateful',
+ 'ipv6_ra_mode': 'dhcpv6-stateless',
'ipv6_address_mode': 'slaac'})
self.assertEqual('foo', settings.name)
self.assertEqual('10.0.0.0/24', settings.cidr)
@@ -215,8 +243,9 @@ class SubnetSettingsUnitTests(unittest.TestCase):
self.assertEqual(host_routes, settings.host_routes[0])
self.assertEqual('dest', settings.destination)
self.assertEqual('hop', settings.nexthop)
- self.assertEqual('dhcpv6-stateful', settings.ipv6_ra_mode)
- self.assertEqual('slaac', settings.ipv6_address_mode)
+ self.assertEqual(IPv6Mode.stateless.value, settings.ipv6_ra_mode.value)
+ self.assertEqual(IPv6Mode.slaac.value,
+ settings.ipv6_address_mode.value)
class PortSettingsUnitTests(unittest.TestCase):
@@ -225,19 +254,19 @@ class PortSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(PortSettingsError):
+ with self.assertRaises(PortConfigError):
PortSettings()
def test_empty_config(self):
- with self.assertRaises(PortSettingsError):
+ with self.assertRaises(PortConfigError):
PortSettings(**dict())
def test_name_only(self):
- with self.assertRaises(PortSettingsError):
+ with self.assertRaises(PortConfigError):
PortSettings(name='foo')
def test_config_name_only(self):
- with self.assertRaises(PortSettingsError):
+ with self.assertRaises(PortConfigError):
PortSettings(**{'name': 'foo'})
def test_name_netname_only(self):
@@ -248,7 +277,6 @@ class PortSettingsUnitTests(unittest.TestCase):
self.assertIsNone(settings.project_name)
self.assertIsNone(settings.mac_address)
self.assertIsNone(settings.ip_addrs)
- self.assertIsNone(settings.fixed_ips)
self.assertIsNone(settings.security_groups)
self.assertIsNone(settings.allowed_address_pairs)
self.assertIsNone(settings.opt_value)
@@ -264,7 +292,6 @@ class PortSettingsUnitTests(unittest.TestCase):
self.assertIsNone(settings.project_name)
self.assertIsNone(settings.mac_address)
self.assertIsNone(settings.ip_addrs)
- self.assertIsNone(settings.fixed_ips)
self.assertIsNone(settings.security_groups)
self.assertIsNone(settings.allowed_address_pairs)
self.assertIsNone(settings.opt_value)
@@ -274,14 +301,12 @@ class PortSettingsUnitTests(unittest.TestCase):
def test_all(self):
ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}]
- fixed_ips = {'sub_id', '10.0.0.10'}
allowed_address_pairs = {'10.0.0.101', '1234.5678'}
settings = PortSettings(name='foo', network_name='bar',
admin_state_up=False,
project_name='foo-project',
mac_address='1234', ip_addrs=ip_addrs,
- fixed_ips=fixed_ips,
security_groups=['foo_grp_id'],
allowed_address_pairs=allowed_address_pairs,
opt_value='opt value', opt_name='opt name',
@@ -293,7 +318,6 @@ class PortSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo-project', settings.project_name)
self.assertEqual('1234', settings.mac_address)
self.assertEqual(ip_addrs, settings.ip_addrs)
- self.assertEqual(fixed_ips, settings.fixed_ips)
self.assertEqual(1, len(settings.security_groups))
self.assertEqual('foo_grp_id', settings.security_groups[0])
self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs)
@@ -304,25 +328,21 @@ class PortSettingsUnitTests(unittest.TestCase):
def test_config_all(self):
ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}]
- fixed_ips = {'sub_id', '10.0.0.10'}
allowed_address_pairs = {'10.0.0.101', '1234.5678'}
settings = PortSettings(
**{'name': 'foo', 'network_name': 'bar', 'admin_state_up': False,
'project_name': 'foo-project', 'mac_address': '1234',
- 'ip_addrs': ip_addrs,
- 'fixed_ips': fixed_ips, 'security_groups': ['foo_grp_id'],
+ 'ip_addrs': ip_addrs, 'security_groups': ['foo_grp_id'],
'allowed_address_pairs': allowed_address_pairs,
- 'opt_value': 'opt value',
- 'opt_name': 'opt name', 'device_owner': 'owner',
- 'device_id': 'device number'})
+ 'opt_value': 'opt value', 'opt_name': 'opt name',
+ 'device_owner': 'owner', 'device_id': 'device number'})
self.assertEqual('foo', settings.name)
self.assertEqual('bar', settings.network_name)
self.assertFalse(settings.admin_state_up)
self.assertEqual('foo-project', settings.project_name)
self.assertEqual('1234', settings.mac_address)
self.assertEqual(ip_addrs, settings.ip_addrs)
- self.assertEqual(fixed_ips, settings.fixed_ips)
self.assertEqual(1, len(settings.security_groups))
self.assertEqual('foo_grp_id', settings.security_groups[0])
self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs)
@@ -334,7 +354,7 @@ class PortSettingsUnitTests(unittest.TestCase):
class CreateNetworkSuccessTests(OSIntegrationTestCase):
"""
- Test for the CreateNework class defined in create_nework.py
+ Test for the CreateNetwork class defined in create_nework.py
"""
def setUp(self):
@@ -345,15 +365,19 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.net_config = openstack_tests.get_pub_net_config(
- net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
- router_name=guid + '-pub-router', external_net=self.ext_net_name)
+ project_name=self.os_creds.project_name,
+ net_name="{}-{}".format(guid, 'pub-net'), mtu=999,
+ subnet_name="{}-{}".format(guid, 'pub-subnet'),
+ router_name="{}-{}".format(guid, 'pub-router'),
+ external_net=self.ext_net_name,
+ netconf_override=self.netconf_override)
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
# Initialize for cleanup
self.net_creator = None
self.router_creator = None
- self.neutron = neutron_utils.neutron_client(self.os_creds)
def tearDown(self):
"""
@@ -371,18 +395,20 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
"""
Tests the creation of an OpenStack network without a router.
"""
- # Create Nework
- self.net_creator = OpenStackNetwork(self.os_creds,
- self.net_config.network_settings)
- self.net_creator.create()
+ # Create Network
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
+ network = self.net_creator.create()
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, self.net_creator.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_creator.network_settings.name, True,
+ self.os_creds.project_name, mtu=999))
# Validate subnets
self.assertTrue(neutron_utils_tests.validate_subnet(
- self.neutron,
+ self.neutron, network,
self.net_creator.network_settings.subnet_settings[0].name,
self.net_creator.network_settings.subnet_settings[0].cidr, True))
@@ -390,19 +416,23 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
"""
Tests the creation of an OpenStack network, it's deletion, then cleanup
"""
- # Create Nework
- self.net_creator = OpenStackNetwork(self.os_creds,
- self.net_config.network_settings)
+ # Create Network
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
self.net_creator.create()
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, self.net_creator.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_creator.network_settings.name, True,
+ self.os_creds.project_name, mtu=999))
- neutron_utils.delete_network(self.neutron,
- self.net_creator.get_network())
+ neutron_utils.delete_network(
+ self.neutron, self.net_creator.get_network())
self.assertIsNone(neutron_utils.get_network(
- self.neutron, network_settings=self.net_creator.network_settings))
+ self.neutron, self.keystone,
+ network_settings=self.net_creator.network_settings,
+ project_name=self.os_creds.project_name))
# This shall not throw an exception here
self.net_creator.clean()
@@ -412,9 +442,9 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
Tests the creation of an OpenStack network with a router.
"""
# Create Network
- self.net_creator = OpenStackNetwork(self.os_creds,
- self.net_config.network_settings)
- self.net_creator.create()
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
+ network = self.net_creator.create()
# Create Router
self.router_creator = create_router.OpenStackRouter(
@@ -423,35 +453,39 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, self.net_creator.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_creator.network_settings.name, True,
+ self.os_creds.project_name, mtu=999))
# Validate subnets
self.assertTrue(neutron_utils_tests.validate_subnet(
- self.neutron,
+ self.neutron, network,
self.net_creator.network_settings.subnet_settings[0].name,
self.net_creator.network_settings.subnet_settings[0].cidr, True))
# Validate routers
neutron_utils_tests.validate_router(
- self.neutron, self.router_creator.router_settings.name, True)
+ self.neutron, self.keystone,
+ self.router_creator.router_settings.name,
+ self.os_creds.project_name, True)
neutron_utils_tests.validate_interface_router(
self.router_creator.get_internal_router_interface(),
self.router_creator.get_router(),
- self.net_creator.get_subnets()[0])
+ self.net_creator.get_network().subnets[0])
def test_create_networks_same_name(self):
"""
Tests the creation of an OpenStack network and ensures that the
OpenStackNetwork object will not create a second.
"""
- # Create Nework
- self.net_creator = OpenStackNetwork(self.os_creds,
- self.net_config.network_settings)
+ # Create Network
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
self.net_creator.create()
- self.net_creator2 = OpenStackNetwork(self.os_creds,
- self.net_config.network_settings)
+ self.net_creator2 = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
self.net_creator2.create()
self.assertEqual(self.net_creator.get_network().id,
@@ -459,31 +493,42 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
def test_create_network_router_admin_user_to_new_project(self):
"""
- Tests the creation of an OpenStack network and router with the current
- user to the admin project.
+ Tests the creation of an OpenStack network to the the current using
+ the credentials to the admin project.
"""
# Create Network/Subnet where the project names have been changed
- admin_project_name = self.admin_os_creds.project_name
- self.net_config.network_settings.project_name = admin_project_name
- self.net_config.network_settings.subnet_settings[0].project_name = \
- admin_project_name
- self.net_creator = OpenStackNetwork(self.os_creds,
- self.net_config.network_settings)
+ project_name = self.os_creds.project_name
+ config = self.net_config.network_settings
+ config.project_name = project_name
+ config.subnet_settings[0].project_name = project_name
+
+ self.net_creator = OpenStackNetwork(self.admin_os_creds, config)
self.net_creator.create()
retrieved_net = neutron_utils.get_network(
- self.neutron, network_settings=self.net_config.network_settings)
+ self.neutron, self.keystone,
+ network_name=self.net_config.network_settings.name,
+ project_name=self.os_creds.project_name)
self.assertEqual(self.net_creator.get_network().id, retrieved_net.id)
+ # Initialize with actual credentials
+ config.project_name = None
+ config.subnet_settings[0].project_name = None
+ proj_net_creator = OpenStackNetwork(self.os_creds, config)
+ proj_net = proj_net_creator.create()
+ self.assertEqual(retrieved_net, proj_net)
+
# Create Router
- self.net_config.router_settings.project_name = admin_project_name
+ self.net_config.router_settings.project_name = project_name
self.router_creator = create_router.OpenStackRouter(
- self.os_creds, self.net_config.router_settings)
+ self.admin_os_creds, self.net_config.router_settings)
self.router_creator.create()
retrieved_router = neutron_utils.get_router(
- self.neutron, router_settings=self.router_creator.router_settings)
+ self.neutron, self.keystone,
+ router_settings=self.router_creator.router_settings,
+ project_name=self.os_creds.project_name)
self.assertEqual(
self.router_creator.get_router().id, retrieved_router.id)
@@ -497,12 +542,14 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
self.net_config.network_settings.project_name = new_project_name
self.net_config.network_settings.subnet_settings[0].project_name = \
new_project_name
- self.net_creator = OpenStackNetwork(self.admin_os_creds,
- self.net_config.network_settings)
+ self.net_creator = OpenStackNetwork(
+ self.admin_os_creds, self.net_config.network_settings)
self.net_creator.create()
retrieved_net = neutron_utils.get_network(
- self.neutron, network_settings=self.net_config.network_settings)
+ self.neutron, self.keystone,
+ network_settings=self.net_config.network_settings,
+ project_name=self.os_creds.project_name)
self.assertEqual(self.net_creator.get_network().id, retrieved_net.id)
@@ -513,14 +560,251 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase):
self.router_creator.create()
retrieved_router = neutron_utils.get_router(
- self.neutron, router_settings=self.router_creator.router_settings)
+ self.neutron, self.keystone,
+ router_settings=self.router_creator.router_settings,
+ project_name=self.os_creds.project_name)
self.assertEqual(
self.router_creator.get_router().id, retrieved_router.id)
+class CreateNetworkGatewayTests(OSIntegrationTestCase):
+ """
+ Test for the CreateNetwork class defined in create_nework.py
+ """
+
+ def setUp(self):
+ """
+ Sets up object for test
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ self.ip_prfx = '10.1.0.'
+
+ # Initialize for cleanup
+ self.net_creator = None
+
+ def tearDown(self):
+ """
+ Cleans the network
+ """
+ if self.net_creator:
+ self.net_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_subnet_default_gateway_ip(self):
+ """
+ Tests the creation of an OpenStack network with a subnet that has a
+ default value assigned to the gateway IP.
+ """
+ # Create Network
+ subnet_config = SubnetConfig(
+ name=self.guid + '-subnet', cidr=self.ip_prfx + '0/24')
+ net_config = NetworkConfig(
+ name=self.guid + '-net', subnets=[subnet_config])
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, net_config)
+ out_net = self.net_creator.create()
+
+ # Validate network was created
+ self.assertTrue(neutron_utils_tests.validate_network(
+ self.neutron, self.keystone,
+ self.net_creator.network_settings.name, True,
+ self.os_creds.project_name))
+
+ # Validate subnets
+ self.assertTrue(neutron_utils_tests.validate_subnet(
+ self.neutron, out_net,
+ self.net_creator.network_settings.subnet_settings[0].name,
+ self.net_creator.network_settings.subnet_settings[0].cidr, True))
+
+ self.assertEqual(self.ip_prfx + '1', out_net.subnets[0].gateway_ip)
+
+ def test_create_subnet_valid_gateway_ip(self):
+ """
+ Tests the creation of an OpenStack network with a subnet that has a
+ valid value assigned to the gateway IP.
+ """
+ # Create Network
+ subnet_config = SubnetConfig(
+ name=self.guid + '-subnet', cidr=self.ip_prfx + '0/24',
+ gateway_ip=self.ip_prfx + '2')
+ net_config = NetworkConfig(
+ name=self.guid + '-net', subnets=[subnet_config])
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, net_config)
+ out_net = self.net_creator.create()
+
+ self.assertIsNotNone(out_net)
+
+ get_net = neutron_utils.get_network_by_id(self.neutron, out_net.id)
+ self.assertIsNotNone(get_net)
+
+ # Validate subnets
+ self.assertTrue(neutron_utils_tests.validate_subnet(
+ self.neutron, out_net,
+ self.net_creator.network_settings.subnet_settings[0].name,
+ self.net_creator.network_settings.subnet_settings[0].cidr, True))
+
+ self.assertEqual(self.ip_prfx + '2', out_net.subnets[0].gateway_ip)
+
+ def test_create_subnet_no_gateway(self):
+ """
+ Tests the creation of an OpenStack network with a subnet that has a
+ valid value assigned to the gateway IP.
+ """
+ # Create Network
+ subnet_config = SubnetConfig(
+ name=self.guid + '-subnet', cidr=self.ip_prfx + '0/24',
+ gateway_ip='none')
+ net_config = NetworkConfig(
+ name=self.guid + '-net', subnets=[subnet_config])
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, net_config)
+ out_net = self.net_creator.create()
+
+ # Validate network was created
+ self.assertTrue(neutron_utils_tests.validate_network(
+ self.neutron, self.keystone,
+ self.net_creator.network_settings.name, True,
+ self.os_creds.project_name))
+
+ # Validate subnets
+ self.assertTrue(neutron_utils_tests.validate_subnet(
+ self.neutron, out_net,
+ self.net_creator.network_settings.subnet_settings[0].name,
+ self.net_creator.network_settings.subnet_settings[0].cidr, True))
+
+ self.assertIsNone(out_net.subnets[0].gateway_ip)
+
+ def test_create_subnet_invalid_gateway_ip(self):
+ """
+ Tests the creation of an OpenStack network with a subnet that has an
+ invalid value assigned to the gateway IP.
+ """
+ # Create Network
+ subnet_config = SubnetConfig(
+ name=self.guid + '-subnet', cidr=self.ip_prfx + '0/24',
+ gateway_ip='foo')
+ net_config = NetworkConfig(
+ name=self.guid + '-net', subnets=[subnet_config])
+ self.net_creator = OpenStackNetwork(
+ self.os_creds, net_config)
+
+ with self.assertRaises(BadRequest):
+ self.net_creator.create()
+
+
+class CreateNetworkIPv6Tests(OSIntegrationTestCase):
+ """
+ Test for the CreateNetwork class defined in create_nework.py when
+ """
+
+ def setUp(self):
+ """
+ Sets up object for test
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ # Initialize for cleanup
+ self.net_creator = None
+
+ def tearDown(self):
+ """
+ Cleans the network
+ """
+ if self.net_creator:
+ self.net_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_network_one_ipv6_subnet(self):
+ """
+ Tests the creation of an OpenStack network without a router.
+ """
+ # Create Network
+ subnet_settings = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6)
+ network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[subnet_settings])
+
+ self.net_creator = OpenStackNetwork(self.os_creds, network_settings)
+ self.net_creator.create()
+
+ # Validate network was created
+ self.assertTrue(neutron_utils_tests.validate_network(
+ self.neutron, self.keystone,
+ self.net_creator.network_settings.name, True,
+ self.os_creds.project_name))
+
+ network = self.net_creator.get_network()
+ self.assertEqual(1, len(network.subnets))
+ subnet = network.subnets[0]
+
+ self.assertEqual(network.id, subnet.network_id)
+ self.assertEqual(subnet_settings.name, subnet.name)
+ self.assertEqual('1:1::/64', subnet.cidr)
+ self.assertEqual(6, subnet.ip_version)
+ self.assertEqual(0, len(subnet.dns_nameservers))
+
+ def test_create_network_ipv4_ipv6_subnet(self):
+ """
+ Tests the creation of an OpenStack network without a router.
+ """
+ # Create Network
+ subnet4_settings = SubnetConfig(
+ name=self.guid + '-subnet4', cidr='10.0.1.0/24', ip_version=4)
+ subnet6_settings = SubnetConfig(
+ name=self.guid + '-subnet6', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6)
+
+ network_settings = NetworkConfig(
+ name=self.guid + '-net',
+ subnet_settings=[subnet4_settings, subnet6_settings])
+
+ self.net_creator = OpenStackNetwork(self.os_creds, network_settings)
+ self.net_creator.create()
+
+ # Validate network was created
+ network = self.net_creator.get_network()
+ self.assertEqual(2, len(network.subnets))
+
+ subnet4 = None
+ subnet6 = None
+ for subnet in network.subnets:
+ if subnet.name == subnet4_settings.name:
+ subnet4 = subnet
+ if subnet.name == subnet6_settings.name:
+ subnet6 = subnet
+
+ # Validate IPv4 subnet
+ self.assertEqual(network.id, subnet4.network_id)
+ self.assertEqual(subnet4_settings.name, subnet4.name)
+ self.assertEqual(subnet4_settings.cidr, subnet4.cidr)
+ self.assertEqual(4, subnet4.ip_version)
+ self.assertEqual(0, len(subnet4.dns_nameservers))
+
+ # Validate IPv6 subnet
+ self.assertEqual(network.id, subnet6.network_id)
+ self.assertEqual(subnet6_settings.name, subnet6.name)
+ self.assertEqual('1:1::/64', subnet6.cidr)
+ self.assertEqual(6, subnet6.ip_version)
+ self.assertEqual(0, len(subnet6.dns_nameservers))
+
+
class CreateNetworkTypeTests(OSComponentTestCase):
"""
- Test for the CreateNework class defined in create_nework.py for testing
+ Test for the CreateNetwork class defined in create_nework.py for testing
creating networks of different types
"""
@@ -530,13 +814,16 @@ class CreateNetworkTypeTests(OSComponentTestCase):
"""
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet')
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
# Initialize for cleanup
self.net_creator = None
- self.neutron = neutron_utils.neutron_client(self.os_creds)
def tearDown(self):
"""
@@ -545,13 +832,15 @@ class CreateNetworkTypeTests(OSComponentTestCase):
if self.net_creator:
self.net_creator.clean()
+ super(self.__class__, self).__clean__()
+
def test_create_network_type_vlan(self):
"""
Tests the creation of an OpenStack network of type vlan.
"""
# Create Network
network_type = 'vlan'
- net_settings = NetworkSettings(
+ net_settings = NetworkConfig(
name=self.net_config.network_settings.name,
subnet_settings=self.net_config.network_settings.subnet_settings,
network_type=network_type)
@@ -562,7 +851,8 @@ class CreateNetworkTypeTests(OSComponentTestCase):
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, net_settings.name, True))
+ self.neutron, self.keystone, net_settings.name, True,
+ self.os_creds.project_name))
self.assertEquals(network_type, network.type)
@@ -578,7 +868,7 @@ class CreateNetworkTypeTests(OSComponentTestCase):
physical_network = 'datacentre'
segmentation_id = 2366
- net_settings = NetworkSettings(
+ net_settings = NetworkConfig(
name=self.net_config.network_settings.name,
subnet_settings=self.net_config.network_settings.subnet_settings,
network_type=network_type,
@@ -591,7 +881,8 @@ class CreateNetworkTypeTests(OSComponentTestCase):
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, net_settings.name, True))
+ self.neutron, self.keystone, net_settings.name, True,
+ self.os_creds.project_name))
self.assertEquals(network_type, network.type)
@@ -601,7 +892,7 @@ class CreateNetworkTypeTests(OSComponentTestCase):
"""
# Create Network
network_type = 'vxlan'
- net_settings = NetworkSettings(
+ net_settings = NetworkConfig(
name=self.net_config.network_settings.name,
subnet_settings=self.net_config.network_settings.subnet_settings,
network_type=network_type)
@@ -612,7 +903,8 @@ class CreateNetworkTypeTests(OSComponentTestCase):
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, net_settings.name, True))
+ self.neutron, self.keystone, net_settings.name, True,
+ self.os_creds.project_name))
self.assertEqual(network_type, network.type)
@@ -626,7 +918,7 @@ class CreateNetworkTypeTests(OSComponentTestCase):
# This value must be variable to work on all OpenStack pods
physical_network = 'datacentre'
- net_settings = NetworkSettings(
+ net_settings = NetworkConfig(
name=self.net_config.network_settings.name,
subnet_settings=self.net_config.network_settings.subnet_settings,
network_type=network_type, physical_network=physical_network)
@@ -635,7 +927,8 @@ class CreateNetworkTypeTests(OSComponentTestCase):
# Validate network was created
self.assertTrue(neutron_utils_tests.validate_network(
- self.neutron, net_settings.name, True))
+ self.neutron, self.keystone, net_settings.name, True,
+ self.os_creds.project_name))
self.assertEquals(network_type, network.type)
@@ -646,10 +939,101 @@ class CreateNetworkTypeTests(OSComponentTestCase):
"""
# Create Network
network_type = 'foo'
- net_settings = NetworkSettings(
+ net_settings = NetworkConfig(
name=self.net_config.network_settings.name,
subnet_settings=self.net_config.network_settings.subnet_settings,
network_type=network_type)
self.net_creator = OpenStackNetwork(self.os_creds, net_settings)
with self.assertRaises(Exception):
self.net_creator.create()
+
+
+class CreateMultipleNetworkTests(OSIntegrationTestCase):
+ """
+ Test for the CreateNetwork class and how it interacts with networks
+ groups within other projects with the same name
+ """
+
+ def setUp(self):
+ """
+ Sets up object for test
+ """
+ super(self.__class__, self).__start__()
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
+ net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet')
+
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ # Initialize for cleanup
+ self.admin_net_creator = None
+ self.proj_net_creator = None
+
+ def tearDown(self):
+ """
+ Cleans the network
+ """
+ if self.admin_net_creator:
+ self.admin_net_creator.clean()
+ if self.proj_net_creator:
+ self.proj_net_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_network_same_name_diff_proj(self):
+ """
+ Tests the creation of an OpenStackNetwork with the same name
+ within a different project/tenant when not configured but implied by
+ the OSCreds.
+ """
+ # Create Network
+
+ self.admin_net_creator = OpenStackNetwork(
+ self.admin_os_creds, self.net_config.network_settings)
+ self.admin_net_creator.create()
+
+ self.proj_net_creator = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
+ self.proj_net_creator.create()
+
+ self.assertNotEqual(
+ self.admin_net_creator.get_network().id,
+ self.proj_net_creator.get_network().id)
+
+ admin_creator2 = OpenStackNetwork(
+ self.admin_os_creds, self.net_config.network_settings)
+ admin_creator2.create()
+ self.assertEqual(
+ self.admin_net_creator.get_network(), admin_creator2.get_network())
+
+ proj_creator2 = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
+ proj_creator2.create()
+ self.assertEqual(self.proj_net_creator.get_network(),
+ proj_creator2.get_network())
+
+ def test_network_create_by_admin_to_different_project(self):
+ """
+ Tests the creation of an OpenStackNetwork by the admin user and
+ initialize again with tenant credentials.
+ """
+ # Create Network
+
+ net_settings = self.net_config.network_settings
+
+ net_settings.project_name = self.os_creds.project_name
+
+ self.admin_net_creator = OpenStackNetwork(
+ self.admin_os_creds, net_settings)
+ self.admin_net_creator.create()
+
+ self.proj_net_creator = OpenStackNetwork(
+ self.os_creds, self.net_config.network_settings)
+ self.proj_net_creator.create()
+
+ self.assertEqual(
+ self.admin_net_creator.get_network().id,
+ self.proj_net_creator.get_network().id)
diff --git a/snaps/openstack/tests/create_project_tests.py b/snaps/openstack/tests/create_project_tests.py
index aa9dcfb..1e3a972 100644
--- a/snaps/openstack/tests/create_project_tests.py
+++ b/snaps/openstack/tests/create_project_tests.py
@@ -17,13 +17,14 @@ import uuid
from keystoneclient.exceptions import BadRequest
+from snaps.config.security_group import SecurityGroupConfig
+from snaps.config.user import UserConfig
+from snaps.config.project import ProjectConfigError, ProjectConfig
from snaps.domain.project import ComputeQuotas, NetworkQuotas
from snaps.openstack.create_project import (
- OpenStackProject, ProjectSettings, ProjectSettingsError)
+ OpenStackProject, ProjectSettings)
from snaps.openstack.create_security_group import OpenStackSecurityGroup
-from snaps.openstack.create_security_group import SecurityGroupSettings
from snaps.openstack.create_user import OpenStackUser
-from snaps.openstack.create_user import UserSettings
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
from snaps.openstack.utils import keystone_utils, nova_utils, neutron_utils
@@ -36,11 +37,11 @@ class ProjectSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(ProjectSettingsError):
+ with self.assertRaises(ProjectConfigError):
ProjectSettings()
def test_empty_config(self):
- with self.assertRaises(ProjectSettingsError):
+ with self.assertRaises(ProjectConfigError):
ProjectSettings(**dict())
def test_name_only(self):
@@ -94,11 +95,12 @@ class CreateProjectSuccessTests(OSComponentTestCase):
"""
guid = str(uuid.uuid4())[:-19]
guid = self.__class__.__name__ + '-' + guid
- self.project_settings = ProjectSettings(
+ self.project_settings = ProjectConfig(
name=guid + '-name',
domain=self.os_creds.project_domain_name)
- self.keystone = keystone_utils.keystone_client(self.os_creds)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
# Initialize for cleanup
self.project_creator = None
@@ -110,6 +112,8 @@ class CreateProjectSuccessTests(OSComponentTestCase):
if self.project_creator:
self.project_creator.clean()
+ super(self.__class__, self).__clean__()
+
def test_create_project_bad_domain(self):
"""
Tests the creation of an OpenStack project with an invalid domain
@@ -139,6 +143,38 @@ class CreateProjectSuccessTests(OSComponentTestCase):
self.assertTrue(validate_project(self.keystone, self.project_settings,
created_project))
+ def test_create_project_quota_override(self):
+ """
+ Tests the creation of an OpenStack project with new quotas.
+ """
+ quotas = {
+ 'cores': 4, 'instances': 5, 'injected_files': 6,
+ 'injected_file_content_bytes': 60000, 'ram': 70000, 'fixed_ips': 7,
+ 'key_pairs': 8}
+ self.project_settings.quotas = quotas
+ self.project_creator = OpenStackProject(self.os_creds,
+ self.project_settings)
+ created_project = self.project_creator.create()
+ self.assertIsNotNone(created_project)
+
+ retrieved_project = keystone_utils.get_project(
+ keystone=self.keystone, project_settings=self.project_settings)
+ self.assertIsNotNone(retrieved_project)
+ self.assertEqual(created_project, retrieved_project)
+ self.assertTrue(validate_project(self.keystone, self.project_settings,
+ created_project))
+
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ new_quotas = nova_utils.get_compute_quotas(nova, created_project.id)
+
+ self.assertEqual(4, new_quotas.cores)
+ self.assertEqual(5, new_quotas.instances)
+ self.assertEqual(6, new_quotas.injected_files)
+ self.assertEqual(60000, new_quotas.injected_file_content_bytes)
+ self.assertEqual(70000, new_quotas.ram)
+ self.assertEqual(7, new_quotas.fixed_ips)
+ self.assertEqual(8, new_quotas.key_pairs)
+
def test_create_project_2x(self):
"""
Tests the creation of an OpenStack project twice to ensure it only
@@ -213,12 +249,12 @@ class CreateProjectSuccessTests(OSComponentTestCase):
self.assertEqual(update_network_quotas,
self.project_creator.get_network_quotas())
- nova = nova_utils.nova_client(self.os_creds)
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
new_compute_quotas = nova_utils.get_compute_quotas(
nova, self.project_creator.get_project().id)
self.assertEqual(update_compute_quotas, new_compute_quotas)
- neutron = neutron_utils.neutron_client(self.os_creds)
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
new_network_quotas = neutron_utils.get_network_quotas(
neutron, self.project_creator.get_project().id)
self.assertEqual(update_network_quotas, new_network_quotas)
@@ -236,11 +272,12 @@ class CreateProjectUserTests(OSComponentTestCase):
"""
guid = str(uuid.uuid4())[:-19]
self.guid = self.__class__.__name__ + '-' + guid
- self.project_settings = ProjectSettings(
+ self.project_settings = ProjectConfig(
name=self.guid + '-name',
domain=self.os_creds.project_domain_name)
- self.keystone = keystone_utils.keystone_client(self.os_creds)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
# Initialize for cleanup
self.project_creator = None
@@ -261,6 +298,8 @@ class CreateProjectUserTests(OSComponentTestCase):
if self.project_creator:
self.project_creator.clean()
+ super(self.__class__, self).__clean__()
+
def test_create_project_sec_grp_one_user(self):
"""
Tests the creation of an OpenStack object to a project with a new users
@@ -272,9 +311,10 @@ class CreateProjectUserTests(OSComponentTestCase):
self.assertIsNotNone(created_project)
user_creator = OpenStackUser(
- self.os_creds, UserSettings(
+ self.os_creds, UserConfig(
name=self.guid + '-user',
- password=self.guid, roles={'admin': self.project_settings.name},
+ password=self.guid,
+ roles={'admin': self.project_settings.name},
domain_name=self.os_creds.user_domain_name))
self.project_creator.assoc_user(user_creator.create())
self.user_creators.append(user_creator)
@@ -282,8 +322,8 @@ class CreateProjectUserTests(OSComponentTestCase):
sec_grp_os_creds = user_creator.get_os_creds(
self.project_creator.get_project().name)
sec_grp_creator = OpenStackSecurityGroup(
- sec_grp_os_creds, SecurityGroupSettings(name=self.guid + '-name',
- description='hello group'))
+ sec_grp_os_creds, SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group'))
sec_grp = sec_grp_creator.create()
self.assertIsNotNone(sec_grp)
self.sec_grp_creators.append(sec_grp_creator)
@@ -302,7 +342,7 @@ class CreateProjectUserTests(OSComponentTestCase):
self.assertIsNotNone(created_project)
user_creator_1 = OpenStackUser(
- self.os_creds, UserSettings(
+ self.os_creds, UserConfig(
name=self.guid + '-user1', password=self.guid,
roles={'admin': self.project_settings.name},
domain_name=self.os_creds.user_domain_name))
@@ -310,7 +350,7 @@ class CreateProjectUserTests(OSComponentTestCase):
self.user_creators.append(user_creator_1)
user_creator_2 = OpenStackUser(
- self.os_creds, UserSettings(
+ self.os_creds, UserConfig(
name=self.guid + '-user2', password=self.guid,
roles={'admin': self.project_settings.name},
domain_name=self.os_creds.user_domain_name))
@@ -325,8 +365,8 @@ class CreateProjectUserTests(OSComponentTestCase):
sec_grp_creator = OpenStackSecurityGroup(
sec_grp_os_creds,
- SecurityGroupSettings(name=self.guid + '-name',
- description='hello group'))
+ SecurityGroupConfig(
+ name=self.guid + '-name', description='hello group'))
sec_grp = sec_grp_creator.create()
self.assertIsNotNone(sec_grp)
self.sec_grp_creators.append(sec_grp_creator)
diff --git a/snaps/openstack/tests/create_qos_tests.py b/snaps/openstack/tests/create_qos_tests.py
index 6c0a056..b3d7979 100644
--- a/snaps/openstack/tests/create_qos_tests.py
+++ b/snaps/openstack/tests/create_qos_tests.py
@@ -12,6 +12,8 @@
# 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 snaps.config.qos import Consumer, QoSConfigError, QoSConfig
+from snaps.openstack.create_qos import Consumer as Consumer_old
try:
from urllib.request import URLError
@@ -23,8 +25,7 @@ import unittest
import uuid
from snaps.openstack import create_qos
-from snaps.openstack.create_qos import (QoSSettings, QoSSettingsError,
- Consumer)
+from snaps.openstack.create_qos import QoSSettings
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
from snaps.openstack.utils import cinder_utils
@@ -39,64 +40,64 @@ class QoSSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(QoSSettingsError):
+ with self.assertRaises(QoSConfigError):
QoSSettings()
def test_empty_config(self):
- with self.assertRaises(QoSSettingsError):
+ with self.assertRaises(QoSConfigError):
QoSSettings(**dict())
def test_name_only(self):
- with self.assertRaises(QoSSettingsError):
+ with self.assertRaises(QoSConfigError):
QoSSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(QoSSettingsError):
+ with self.assertRaises(QoSConfigError):
QoSSettings(**{'name': 'foo'})
def test_invalid_consumer(self):
- with self.assertRaises(QoSSettingsError):
+ with self.assertRaises(QoSConfigError):
QoSSettings(name='foo', consumer='bar')
def test_config_with_invalid_consumer(self):
- with self.assertRaises(QoSSettingsError):
+ with self.assertRaises(QoSConfigError):
QoSSettings(**{'name': 'foo', 'consumer': 'bar'})
def test_name_consumer(self):
- settings = QoSSettings(name='foo', consumer=Consumer.front_end)
+ settings = QoSSettings(name='foo', consumer=Consumer_old.front_end)
self.assertEqual('foo', settings.name)
- self.assertEqual(Consumer.front_end, settings.consumer)
+ self.assertEqual(Consumer_old.front_end.value, settings.consumer.value)
self.assertEqual(dict(), settings.specs)
def test_name_consumer_front_end_strings(self):
settings = QoSSettings(name='foo', consumer='front-end')
self.assertEqual('foo', settings.name)
- self.assertEqual(Consumer.front_end, settings.consumer)
+ self.assertEqual(Consumer_old.front_end.value, settings.consumer.value)
self.assertEqual(dict(), settings.specs)
def test_name_consumer_back_end_strings(self):
settings = QoSSettings(name='foo', consumer='back-end')
self.assertEqual('foo', settings.name)
- self.assertEqual(Consumer.back_end, settings.consumer)
+ self.assertEqual(Consumer_old.back_end.value, settings.consumer.value)
self.assertEqual(dict(), settings.specs)
def test_name_consumer_both_strings(self):
settings = QoSSettings(name='foo', consumer='both')
self.assertEqual('foo', settings.name)
- self.assertEqual(Consumer.both, settings.consumer)
+ self.assertEqual(Consumer_old.both.value, settings.consumer.value)
self.assertEqual(dict(), settings.specs)
def test_all(self):
specs = {'spec1': 'val1', 'spec2': 'val2'}
- settings = QoSSettings(name='foo', consumer=Consumer.both,
+ settings = QoSSettings(name='foo', consumer=Consumer_old.both,
specs=specs)
self.assertEqual('foo', settings.name)
- self.assertEqual(Consumer.both, settings.consumer)
+ self.assertEqual(Consumer_old.both.value, settings.consumer.value)
self.assertEqual(specs, settings.specs)
def test_config_all(self):
@@ -121,12 +122,14 @@ class CreateQoSTests(OSIntegrationTestCase):
super(self.__class__, self).__start__()
guid = uuid.uuid4()
- self.qos_settings = QoSSettings(
+ qos_settings = QoSConfig(
name=self.__class__.__name__ + '-' + str(guid),
consumer=Consumer.both)
- self.cinder = cinder_utils.cinder_client(self.os_creds)
- self.qos_creator = None
+ self.cinder = cinder_utils.cinder_client(
+ self.admin_os_creds, self.admin_os_session)
+ self.qos_creator = create_qos.OpenStackQoS(
+ self.admin_os_creds, qos_settings)
def tearDown(self):
"""
@@ -142,13 +145,11 @@ class CreateQoSTests(OSIntegrationTestCase):
Tests the creation of an OpenStack qos.
"""
# Create QoS
- self.qos_creator = create_qos.OpenStackQoS(
- self.os_creds, self.qos_settings)
created_qos = self.qos_creator.create()
self.assertIsNotNone(created_qos)
retrieved_qos = cinder_utils.get_qos(
- self.cinder, qos_settings=self.qos_settings)
+ self.cinder, qos_settings=self.qos_creator.qos_settings)
self.assertIsNotNone(retrieved_qos)
self.assertEqual(created_qos, retrieved_qos)
@@ -159,13 +160,11 @@ class CreateQoSTests(OSIntegrationTestCase):
clean() does not raise an Exception.
"""
# Create QoS
- self.qos_creator = create_qos.OpenStackQoS(
- self.os_creds, self.qos_settings)
created_qos = self.qos_creator.create()
self.assertIsNotNone(created_qos)
retrieved_qos = cinder_utils.get_qos(
- self.cinder, qos_settings=self.qos_settings)
+ self.cinder, qos_settings=self.qos_creator.qos_settings)
self.assertIsNotNone(retrieved_qos)
self.assertEqual(created_qos, retrieved_qos)
@@ -173,7 +172,7 @@ class CreateQoSTests(OSIntegrationTestCase):
cinder_utils.delete_qos(self.cinder, created_qos)
self.assertIsNone(cinder_utils.get_qos(
- self.cinder, qos_settings=self.qos_settings))
+ self.cinder, qos_settings=self.qos_creator.qos_settings))
# Must not raise an exception when attempting to cleanup non-existent
# qos
@@ -185,16 +184,14 @@ class CreateQoSTests(OSIntegrationTestCase):
Tests the creation of an OpenStack qos when one already exists.
"""
# Create QoS
- self.qos_creator = create_qos.OpenStackQoS(
- self.os_creds, self.qos_settings)
qos1 = self.qos_creator.create()
retrieved_qos = cinder_utils.get_qos(
- self.cinder, qos_settings=self.qos_settings)
+ self.cinder, qos_settings=self.qos_creator.qos_settings)
self.assertEqual(qos1, retrieved_qos)
# Should be retrieving the instance data
os_qos_2 = create_qos.OpenStackQoS(
- self.os_creds, self.qos_settings)
+ self.admin_os_creds, self.qos_creator.qos_settings)
qos2 = os_qos_2.create()
self.assertEqual(qos1, qos2)
diff --git a/snaps/openstack/tests/create_router_tests.py b/snaps/openstack/tests/create_router_tests.py
index db3170e..a305cf8 100644
--- a/snaps/openstack/tests/create_router_tests.py
+++ b/snaps/openstack/tests/create_router_tests.py
@@ -15,20 +15,23 @@
import unittest
import uuid
+from snaps.config.network import PortConfig, NetworkConfig, PortConfigError
+from snaps.config.router import RouterConfigError, RouterConfig
+from snaps.config.security_group import SecurityGroupConfig
from snaps.openstack import create_network
from snaps.openstack import create_router
-from snaps.openstack.create_network import (
- NetworkSettings, PortSettings)
from snaps.openstack.create_network import OpenStackNetwork
from snaps.openstack.create_router import (
- RouterSettings, RouterSettingsError)
+ RouterSettings, OpenStackRouter, RouterCreationError)
+from snaps.openstack.create_security_group import OpenStackSecurityGroup
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
-from snaps.openstack.utils import neutron_utils
+from snaps.openstack.utils import neutron_utils, settings_utils, keystone_utils
__author__ = 'mmakati'
cidr1 = '10.200.201.0/24'
cidr2 = '10.200.202.0/24'
+cidr3 = '10.200.203.0/24'
static_gateway_ip1 = '10.200.201.1'
static_gateway_ip2 = '10.200.202.1'
@@ -39,11 +42,11 @@ class RouterSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(RouterSettingsError):
+ with self.assertRaises(RouterConfigError):
RouterSettings()
def test_empty_config(self):
- with self.assertRaises(RouterSettingsError):
+ with self.assertRaises(RouterConfigError):
RouterSettings(**dict())
def test_name_only(self):
@@ -51,9 +54,8 @@ class RouterSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo', settings.name)
self.assertIsNone(settings.project_name)
self.assertIsNone(settings.external_gateway)
- self.assertIsNone(settings.admin_state_up)
+ self.assertTrue(settings.admin_state_up)
self.assertIsNone(settings.enable_snat)
- self.assertIsNone(settings.external_fixed_ips)
self.assertIsNotNone(settings.internal_subnets)
self.assertTrue(isinstance(settings.internal_subnets, list))
self.assertEqual(0, len(settings.internal_subnets))
@@ -66,9 +68,8 @@ class RouterSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo', settings.name)
self.assertIsNone(settings.project_name)
self.assertIsNone(settings.external_gateway)
- self.assertIsNone(settings.admin_state_up)
+ self.assertTrue(settings.admin_state_up)
self.assertIsNone(settings.enable_snat)
- self.assertIsNone(settings.external_fixed_ips)
self.assertIsNotNone(settings.internal_subnets)
self.assertTrue(isinstance(settings.internal_subnets, list))
self.assertEqual(0, len(settings.internal_subnets))
@@ -77,17 +78,16 @@ class RouterSettingsUnitTests(unittest.TestCase):
self.assertEqual(0, len(settings.port_settings))
def test_all(self):
- port_settings = PortSettings(name='foo', network_name='bar')
+ port_settings = PortConfig(name='foo', network_name='bar')
settings = RouterSettings(
name='foo', project_name='bar', external_gateway='foo_gateway',
- admin_state_up=True, enable_snat=False, external_fixed_ips=['ip1'],
+ admin_state_up=True, enable_snat=False,
internal_subnets=['10.0.0.1/24'], interfaces=[port_settings])
self.assertEqual('foo', settings.name)
self.assertEqual('bar', settings.project_name)
self.assertEqual('foo_gateway', settings.external_gateway)
self.assertTrue(settings.admin_state_up)
self.assertFalse(settings.enable_snat)
- self.assertEqual(['ip1'], settings.external_fixed_ips)
self.assertIsNotNone(settings.internal_subnets)
self.assertTrue(isinstance(settings.internal_subnets, list))
self.assertEqual(1, len(settings.internal_subnets))
@@ -98,8 +98,7 @@ class RouterSettingsUnitTests(unittest.TestCase):
settings = RouterSettings(
**{'name': 'foo', 'project_name': 'bar',
'external_gateway': 'foo_gateway', 'admin_state_up': True,
- 'enable_snat': False, 'external_fixed_ips': ['ip1'],
- 'internal_subnets': ['10.0.0.1/24'],
+ 'enable_snat': False, 'internal_subnets': ['10.0.0.1/24'],
'interfaces':
[{'port': {'name': 'foo-port',
'network_name': 'bar-net'}}]})
@@ -108,14 +107,13 @@ class RouterSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo_gateway', settings.external_gateway)
self.assertTrue(settings.admin_state_up)
self.assertFalse(settings.enable_snat)
- self.assertEqual(['ip1'], settings.external_fixed_ips)
self.assertIsNotNone(settings.internal_subnets)
self.assertTrue(isinstance(settings.internal_subnets, list))
self.assertEqual(1, len(settings.internal_subnets))
self.assertEqual(['10.0.0.1/24'], settings.internal_subnets)
- self.assertEqual([PortSettings(**{'name': 'foo-port',
- 'network_name': 'bar-net'})],
- settings.port_settings)
+ self.assertEqual(
+ [PortConfig(**{'name': 'foo-port', 'network_name': 'bar-net'})],
+ settings.port_settings)
class CreateRouterSuccessTests(OSIntegrationTestCase):
@@ -134,7 +132,8 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
self.router_creator = None
self.network_creator1 = None
self.network_creator2 = None
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
@@ -155,26 +154,28 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
"""
Test creation of a most basic router with minimal options.
"""
- router_settings = RouterSettings(name=self.guid + '-pub-router',
- external_gateway=self.ext_net_name)
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', external_gateway=self.ext_net_name)
- self.router_creator = create_router.OpenStackRouter(self.os_creds,
- router_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNotNone(router)
- self.assertTrue(verify_router_attributes(
- router, self.router_creator, ext_gateway=self.ext_net_name))
+ self.assertEqual(self.router_creator.get_router(), router)
+
+ self.check_router_recreation(router, router_settings)
def test_create_router_admin_user_to_new_project(self):
"""
Test creation of a most basic router with the admin user pointing
to the new project.
"""
- router_settings = RouterSettings(
+ router_settings = RouterConfig(
name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
project_name=self.os_creds.project_name)
@@ -182,39 +183,43 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
self.admin_os_creds, router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNotNone(router)
- self.assertTrue(verify_router_attributes(
- router, self.router_creator, ext_gateway=self.ext_net_name))
+ self.assertEqual(self.router_creator.get_router().id, router.id)
- def test_create_router_new_user_to_admin_project(self):
+ self.check_router_recreation(router, router_settings)
+
+ def test_create_router_new_user_as_admin_project(self):
"""
Test creation of a most basic router with the new user pointing
to the admin project.
"""
- router_settings = RouterSettings(
+ router_settings = RouterConfig(
name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
- project_name=self.admin_os_creds.project_name)
+ project_name=self.os_creds.project_name)
self.router_creator = create_router.OpenStackRouter(
- self.os_creds, router_settings)
+ self.admin_os_creds, router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNotNone(router)
- self.assertTrue(verify_router_attributes(
- router, self.router_creator, ext_gateway=self.ext_net_name))
+ self.assertEqual(self.router_creator.get_router().id, router.id)
+
+ self.check_router_recreation(router, router_settings)
def test_create_delete_router(self):
"""
Test that clean() will not raise an exception if the router is deleted
by another process.
"""
- self.router_settings = RouterSettings(
+ self.router_settings = RouterConfig(
name=self.guid + '-pub-router', external_gateway=self.ext_net_name)
self.router_creator = create_router.OpenStackRouter(
@@ -222,69 +227,122 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
created_router = self.router_creator.create()
self.assertIsNotNone(created_router)
retrieved_router = neutron_utils.get_router(
- self.neutron, router_settings=self.router_settings)
+ self.neutron, self.keystone, router_settings=self.router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNotNone(retrieved_router)
neutron_utils.delete_router(self.neutron, created_router)
retrieved_router = neutron_utils.get_router(
- self.neutron, router_settings=self.router_settings)
+ self.neutron, self.keystone, router_settings=self.router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNone(retrieved_router)
# Should not raise an exception
self.router_creator.clean()
+ def test_create_with_internal_sub(self):
+ """
+ Test internal_subnets works.
+ """
+ network_settings1 = NetworkConfig(
+ name=self.guid + '-pub-net1',
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr1, name=self.guid + '-pub-subnet1',
+ gateway_ip=static_gateway_ip1)])
+ self.network_creator1 = OpenStackNetwork(self.os_creds,
+ network_settings1)
+
+ self.network_creator1.create()
+ self.router_settings = RouterConfig(
+ name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
+ internal_subnets=[network_settings1.subnet_settings[0].name])
+
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, self.router_settings)
+ created_router = self.router_creator.create()
+ self.assertIsNotNone(created_router)
+
+ def test_create_with_invalid_internal_sub(self):
+ """
+ Test adding an internal subnet owned by admin which should fail.
+ """
+ network_settings1 = NetworkConfig(
+ name=self.guid + '-pub-net1',
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr1, name=self.guid + '-pub-subnet1',
+ gateway_ip=static_gateway_ip1)])
+ self.network_creator1 = OpenStackNetwork(self.admin_os_creds,
+ network_settings1)
+
+ self.network_creator1.create()
+ self.router_settings = RouterConfig(
+ name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
+ internal_subnets=[network_settings1.subnet_settings[0].name])
+
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, self.router_settings)
+
+ with self.assertRaises(RouterCreationError):
+ self.router_creator.create()
+
def test_create_router_admin_state_false(self):
"""
Test creation of a basic router with admin state down.
"""
- router_settings = RouterSettings(name=self.guid + '-pub-router',
- admin_state_up=False)
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', admin_state_up=False)
self.router_creator = create_router.OpenStackRouter(self.os_creds,
router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNotNone(router)
- self.assertTrue(verify_router_attributes(router, self.router_creator,
- admin_state=False))
+ self.assertEqual(self.router_creator.get_router(), router)
+
+ self.check_router_recreation(router, router_settings)
def test_create_router_admin_state_True(self):
"""
Test creation of a basic router with admin state Up.
"""
- router_settings = RouterSettings(name=self.guid + '-pub-router',
- admin_state_up=True)
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', admin_state_up=True)
- self.router_creator = create_router.OpenStackRouter(self.os_creds,
- router_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
self.assertIsNotNone(router)
- self.assertTrue(verify_router_attributes(router, self.router_creator,
- admin_state=True))
+ self.assertEqual(self.router_creator.get_router(), router)
+
+ self.check_router_recreation(router, router_settings)
def test_create_router_private_network(self):
"""
Test creation of a router connected with two private networks and no
external gateway
"""
- network_settings1 = NetworkSettings(
+ network_settings1 = NetworkConfig(
name=self.guid + '-pub-net1',
subnet_settings=[
- create_network.SubnetSettings(
+ create_network.SubnetConfig(
cidr=cidr1, name=self.guid + '-pub-subnet1',
gateway_ip=static_gateway_ip1)])
- network_settings2 = NetworkSettings(
+ network_settings2 = NetworkConfig(
name=self.guid + '-pub-net2',
subnet_settings=[
- create_network.SubnetSettings(
+ create_network.SubnetConfig(
cidr=cidr2, name=self.guid + '-pub-subnet2',
gateway_ip=static_gateway_ip2)])
@@ -297,34 +355,33 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
self.network_creator2.create()
port_settings = [
- create_network.PortSettings(
+ create_network.PortConfig(
name=self.guid + '-port1',
ip_addrs=[{
'subnet_name':
network_settings1.subnet_settings[0].name,
'ip': static_gateway_ip1
}],
- network_name=network_settings1.name,
- project_name=self.os_creds.project_name),
- create_network.PortSettings(
+ network_name=network_settings1.name),
+ create_network.PortConfig(
name=self.guid + '-port2',
ip_addrs=[{
'subnet_name': network_settings2.subnet_settings[0].name,
'ip': static_gateway_ip2
}],
- network_name=network_settings2.name,
- project_name=self.os_creds.project_name)]
+ network_name=network_settings2.name)]
- router_settings = RouterSettings(name=self.guid + '-pub-router',
- port_settings=port_settings)
- self.router_creator = create_router.OpenStackRouter(self.os_creds,
- router_settings)
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', port_settings=port_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
- self.assertTrue(verify_router_attributes(router, self.router_creator))
+ self.assertEqual(router, self.router_creator.get_router())
# Instantiate second identical creator to ensure a second router
# has not been created
@@ -333,15 +390,17 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
router2 = router_creator2.create()
self.assertIsNotNone(self.router_creator.get_router(), router2)
+ self.check_router_recreation(router2, router_settings)
+
def test_create_router_external_network(self):
"""
Test creation of a router connected to an external network and a
private network.
"""
- network_settings = NetworkSettings(
+ network_settings = NetworkConfig(
name=self.guid + '-pub-net1',
subnet_settings=[
- create_network.SubnetSettings(
+ create_network.SubnetConfig(
cidr=cidr1, name=self.guid + '-pub-subnet1',
gateway_ip=static_gateway_ip1)])
self.network_creator1 = OpenStackNetwork(self.os_creds,
@@ -349,26 +408,108 @@ class CreateRouterSuccessTests(OSIntegrationTestCase):
self.network_creator1.create()
port_settings = [
- create_network.PortSettings(
+ create_network.PortConfig(
name=self.guid + '-port1',
ip_addrs=[{
'subnet_name': network_settings.subnet_settings[0].name,
'ip': static_gateway_ip1}],
- network_name=network_settings.name,
- project_name=self.os_creds.project_name)]
+ network_name=network_settings.name)]
- router_settings = RouterSettings(
+ router_settings = RouterConfig(
name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
port_settings=port_settings)
- self.router_creator = create_router.OpenStackRouter(self.os_creds,
- router_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
+
+ router = neutron_utils.get_router(
+ self.neutron, self.keystone, router_settings=router_settings,
+ project_name=self.os_creds.project_name)
+
+ self.assertEquals(router, self.router_creator.get_router())
+
+ self.check_router_recreation(router, router_settings)
+
+ def test_create_router_with_ext_port(self):
+ """
+ Test creation of a router with a port to an external network as an
+ 'admin' user.
+ """
+ port_settings = [
+ create_network.PortConfig(
+ name=self.guid + '-port1',
+ network_name=self.ext_net_name)]
+
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', port_settings=port_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.admin_os_creds, router_settings)
self.router_creator.create()
- router = neutron_utils.get_router(self.neutron,
- router_settings=router_settings)
+ admin_neutron = neutron_utils.neutron_client(
+ self.admin_os_creds, self.admin_os_session)
+ admin_keystone = keystone_utils.keystone_client(
+ self.admin_os_creds, self.admin_os_session)
+ router = neutron_utils.get_router(
+ admin_neutron, admin_keystone, router_settings=router_settings,
+ project_name=self.admin_os_creds.project_name)
+
+ self.assertIsNotNone(router)
+ self.assertEquals(router, self.router_creator.get_router())
+
+ ext_net = neutron_utils.get_network(
+ admin_neutron, admin_keystone, network_name=self.ext_net_name)
+
+ self.assertIsNotNone(ext_net)
+ self.assertIsNotNone(router.port_subnets)
+
+ id_found = False
+ for port, subnets in router.port_subnets:
+ self.assertIsNotNone(subnets)
+ self.assertIsNotNone(port)
+
+ if ext_net.id == port.network_id:
+ id_found = True
+ for subnet in subnets:
+ self.assertIsNotNone(subnet)
+ self.assertEqual(ext_net.id, subnet.network_id)
+ self.assertTrue(id_found)
- self.assertTrue(verify_router_attributes(
- router, self.router_creator, ext_gateway=self.ext_net_name))
+ def check_router_recreation(self, router, orig_settings):
+ """
+ Validates the derived RouterConfig with the original
+ :param router: the Router domain object to test
+ :param orig_settings: the original RouterConfig object that was
+ responsible for creating the router
+ :return: the derived RouterConfig object
+ """
+ derived_settings = settings_utils.create_router_config(
+ self.neutron, router)
+ self.assertIsNotNone(derived_settings)
+ self.assertEqual(
+ orig_settings.enable_snat, derived_settings.enable_snat)
+ self.assertEqual(orig_settings.external_gateway,
+ derived_settings.external_gateway)
+ self.assertEqual(orig_settings.name, derived_settings.name)
+ self.assertEqual(orig_settings.internal_subnets,
+ derived_settings.internal_subnets)
+
+ if orig_settings.external_gateway:
+ self.assertEqual(len(orig_settings.port_settings),
+ len(derived_settings.port_settings))
+ else:
+ self.assertEqual(len(orig_settings.port_settings),
+ len(derived_settings.port_settings))
+
+ if len(orig_settings.port_settings) > 0:
+ self.assertEqual(orig_settings.port_settings[0].name,
+ derived_settings.port_settings[0].name)
+
+ if len(orig_settings.port_settings) > 1:
+ self.assertEqual(orig_settings.port_settings[1].name,
+ derived_settings.port_settings[1].name)
+
+ return derived_settings
class CreateRouterNegativeTests(OSIntegrationTestCase):
@@ -383,6 +524,7 @@ class CreateRouterNegativeTests(OSIntegrationTestCase):
super(self.__class__, self).__start__()
self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.network_creator = None
self.router_creator = None
def tearDown(self):
@@ -392,14 +534,17 @@ class CreateRouterNegativeTests(OSIntegrationTestCase):
if self.router_creator:
self.router_creator.clean()
+ if self.network_creator:
+ self.network_creator.clean()
+
super(self.__class__, self).__clean__()
def test_create_router_noname(self):
"""
Test creating a router without a name.
"""
- with self.assertRaises(RouterSettingsError):
- router_settings = RouterSettings(
+ with self.assertRaises(RouterConfigError):
+ router_settings = RouterConfig(
name=None, external_gateway=self.ext_net_name)
self.router_creator = create_router.OpenStackRouter(
self.os_creds, router_settings)
@@ -409,48 +554,320 @@ class CreateRouterNegativeTests(OSIntegrationTestCase):
"""
Test creating a router without a valid network gateway name.
"""
- with self.assertRaises(RouterSettingsError):
- router_settings = RouterSettings(name=self.guid + '-pub-router',
- external_gateway="Invalid_name")
+ with self.assertRaises(RouterConfigError):
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router',
+ external_gateway="Invalid_name")
self.router_creator = create_router.OpenStackRouter(
self.os_creds, router_settings)
self.router_creator.create()
+ def test_create_router_admin_ports(self):
+ """
+ Test creation of a router with ports to subnets owned by the admin
+ project
+ """
+ network_settings = NetworkConfig(
+ name=self.guid + '-pub-net1',
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr1, name=self.guid + '-pub-subnet1',
+ gateway_ip=static_gateway_ip1)])
+ self.network_creator = OpenStackNetwork(
+ self.admin_os_creds, network_settings)
+ self.network_creator.create()
+
+ port_settings = [
+ create_network.PortConfig(
+ name=self.guid + '-port1',
+ ip_addrs=[{
+ 'subnet_name': network_settings.subnet_settings[0].name,
+ 'ip': static_gateway_ip1}],
+ network_name=network_settings.name)]
+
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
+ port_settings=port_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
+
+ with self.assertRaises(PortConfigError):
+ self.router_creator.create()
+
+
+class CreateMultipleRouterTests(OSIntegrationTestCase):
+ """
+ Test for the OpenStackRouter class and how it interacts with routers
+ groups within other projects with the same name
+ """
+
+ def setUp(self):
+ """
+ Initializes objects used for router testing
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.admin_router_creator = None
+ self.proj_router_creator = None
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ network_settings = NetworkConfig(
+ name=self.guid + '-pub-net', shared=True,
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr1, name=self.guid + '-pub-subnet',
+ gateway_ip=static_gateway_ip1)])
+
+ self.network_creator = OpenStackNetwork(
+ self.admin_os_creds, network_settings)
+ self.network_creator.create()
-def verify_router_attributes(router_operational, router_creator,
- admin_state=True, ext_gateway=None):
+ def tearDown(self):
+ """
+ Cleans the remote OpenStack objects used for router testing
+ """
+ if self.admin_router_creator:
+ self.admin_router_creator.clean()
+
+ if self.proj_router_creator:
+ self.proj_router_creator.clean()
+
+ if self.network_creator:
+ self.network_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_router_same_name_diff_proj(self):
+ """
+ Tests the creation of an OpenStackNetwork with the same name
+ within a different project/tenant when not configured but implied by
+ the OSCreds.
+ """
+ # Create Router
+
+ router_config = RouterConfig(name=self.guid + '-router')
+ self.admin_router_creator = OpenStackRouter(
+ self.admin_os_creds, router_config)
+ self.admin_router_creator.create()
+
+ self.proj_router_creator = OpenStackRouter(
+ self.os_creds, router_config)
+ self.proj_router_creator.create()
+
+ self.assertNotEqual(
+ self.admin_router_creator.get_router().id,
+ self.proj_router_creator.get_router().id)
+
+ admin_creator2 = OpenStackRouter(
+ self.admin_os_creds, router_config)
+ admin_creator2.create()
+ self.assertEqual(
+ self.admin_router_creator.get_router(),
+ admin_creator2.get_router())
+
+ proj_creator2 = OpenStackRouter(self.os_creds, router_config)
+ proj_creator2.create()
+ self.assertEqual(self.proj_router_creator.get_router(),
+ proj_creator2.get_router())
+
+ def test_router_create_by_admin_to_different_project(self):
+ """
+ Tests the creation of an OpenStackRouter by the admin user and
+ initialize again with tenant credentials.
+ """
+ # Create Network
+
+ admin_router_config = RouterConfig(
+ name=self.guid + '-router',
+ project_name=self.os_creds.project_name)
+
+ self.admin_router_creator = OpenStackRouter(
+ self.admin_os_creds, admin_router_config)
+ self.admin_router_creator.create()
+
+ proj_router_config = RouterConfig(
+ name=self.guid + '-router',
+ project_name=self.os_creds.project_name)
+
+ self.proj_router_creator = OpenStackRouter(
+ self.os_creds, proj_router_config)
+ self.proj_router_creator.create()
+
+ self.assertEqual(
+ self.admin_router_creator.get_router().id,
+ self.proj_router_creator.get_router().id)
+
+
+class CreateRouterSecurityGroupTests(OSIntegrationTestCase):
"""
- Helper function to validate the attributes of router created with the one
- operational
- :param router_operational: Operational Router object returned from neutron
- utils of type snaps.domain.Router
- :param router_creator: router_creator object returned from creating a
- router in the router test functions
- :param admin_state: True if router is expected to be Up, else False
- :param ext_gateway: None if router is not connected to external gateway
- :return:
+ Class for testing routers with ports containing security groups
"""
- router = router_creator.get_router()
-
- if not router_operational:
- return False
- elif not router_creator:
- return False
- elif not (router_operational.name == router_creator.router_settings.name):
- return False
- elif not (router_operational.id == router.id):
- return False
- elif not (router_operational.status == router.status):
- return False
- elif not (router_operational.tenant_id == router.tenant_id):
- return False
- elif not (admin_state == router_operational.admin_state_up):
- return False
- elif (ext_gateway is None) and \
- (router_operational.external_gateway_info is not None):
- return False
- elif ext_gateway is not None:
- if router_operational.external_gateway_info is None:
- return False
- return True
+ def setUp(self):
+ """
+ Initializes objects used for router testing
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.router_creator = None
+ self.network_creator = None
+
+ self.sec_grp_creator = OpenStackSecurityGroup(
+ self.os_creds, SecurityGroupConfig(name=self.guid + '-sec_grp'))
+ self.sec_grp_creator.create()
+
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the remote OpenStack objects used for router testing
+ """
+ if self.router_creator:
+ self.router_creator.clean()
+
+ if self.network_creator:
+ self.network_creator.clean()
+
+ if self.sec_grp_creator:
+ self.sec_grp_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_router_secure_port(self):
+ """
+ Test creation of a router with a port that has a security group.
+ """
+ network_settings = NetworkConfig(
+ name=self.guid + '-pub-net1',
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr1, name=self.guid + '-pub-subnet1')])
+ self.network_creator = OpenStackNetwork(
+ self.os_creds, network_settings)
+ self.network_creator.create()
+
+ port_settings = [
+ create_network.PortConfig(
+ name=self.guid + '-port1',
+ ip_addrs=[{
+ 'subnet_name': network_settings.subnet_settings[0].name,
+ 'ip': static_gateway_ip1}],
+ network_name=network_settings.name,
+ security_groups=[self.sec_grp_creator.sec_grp_settings.name])]
+
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router', external_gateway=self.ext_net_name,
+ port_settings=port_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
+
+
+class CreateRouterSharedNetworksTests(OSIntegrationTestCase):
+ """
+ Class for testing routers external and/or shared networks
+ """
+
+ def setUp(self):
+ """
+ Initializes objects used for router testing
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.router_creator = None
+
+ ext_network_settings = NetworkConfig(
+ name=self.guid + '-ext-net',
+ external=True,
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr1, name=self.guid + '-ext-subnet1')])
+ self.ext_network_creator = OpenStackNetwork(
+ self.admin_os_creds, ext_network_settings)
+ self.ext_network_creator.create()
+
+ shared_network_settings = NetworkConfig(
+ name=self.guid + '-shared-net',
+ shared=True,
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr2, name=self.guid + '-shared-subnet1')])
+ self.shared_network_creator = OpenStackNetwork(
+ self.admin_os_creds, shared_network_settings)
+ self.shared_network_creator.create()
+
+ overlay_network_settings = NetworkConfig(
+ name=self.guid + '-overlay-net',
+ subnet_settings=[
+ create_network.SubnetConfig(
+ cidr=cidr3, name=self.guid + '-overlay-subnet1')])
+ self.overlay_network_creator = OpenStackNetwork(
+ self.os_creds, overlay_network_settings)
+ self.overlay_network_creator.create()
+
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the remote OpenStack objects used for router testing
+ """
+ if self.router_creator:
+ self.router_creator.clean()
+
+ if self.overlay_network_creator:
+ self.overlay_network_creator.clean()
+
+ if self.shared_network_creator:
+ self.shared_network_creator.clean()
+
+ if self.ext_network_creator:
+ self.ext_network_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_router_external(self):
+ """
+ Test creation of a router with a custom external network created by
+ admin.
+ """
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router',
+ external_gateway=self.ext_network_creator.get_network().name)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
+
+ def test_create_router_port_external(self):
+ """
+ Test creation of a router with a port to an custom external network
+ created by admin.
+ """
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router',
+ network_name=self.ext_network_creator.get_network().name)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
+
+ def test_create_router_port_shared(self):
+ """
+ Test creation of a router with a port to an custom shared network
+ created by admin.
+ """
+ port_settings = [
+ create_network.PortConfig(
+ name=self.guid + '-port1',
+ network_name=self.shared_network_creator.get_network().name)]
+
+ router_settings = RouterConfig(
+ name=self.guid + '-pub-router',
+ port_settings=port_settings)
+ self.router_creator = create_router.OpenStackRouter(
+ self.os_creds, router_settings)
+ self.router_creator.create()
diff --git a/snaps/openstack/tests/create_security_group_tests.py b/snaps/openstack/tests/create_security_group_tests.py
index 99ea53a..dc632b7 100644
--- a/snaps/openstack/tests/create_security_group_tests.py
+++ b/snaps/openstack/tests/create_security_group_tests.py
@@ -15,10 +15,13 @@
import unittest
import uuid
+from snaps.config.security_group import (
+ SecurityGroupConfig, SecurityGroupRuleConfig,
+ SecurityGroupRuleConfigError, SecurityGroupConfigError)
from snaps.openstack import create_security_group
from snaps.openstack.create_security_group import (
SecurityGroupSettings, SecurityGroupRuleSettings, Direction, Ethertype,
- Protocol, SecurityGroupRuleSettingsError, SecurityGroupSettingsError)
+ Protocol, OpenStackSecurityGroup)
from snaps.openstack.tests import validation_utils
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
from snaps.openstack.utils import neutron_utils
@@ -32,32 +35,64 @@ class SecurityGroupRuleSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(SecurityGroupRuleSettingsError):
+ with self.assertRaises(SecurityGroupRuleConfigError):
SecurityGroupRuleSettings()
def test_empty_config(self):
- with self.assertRaises(SecurityGroupRuleSettingsError):
+ with self.assertRaises(SecurityGroupRuleConfigError):
SecurityGroupRuleSettings(**dict())
def test_name_only(self):
- with self.assertRaises(SecurityGroupRuleSettingsError):
+ with self.assertRaises(SecurityGroupRuleConfigError):
SecurityGroupRuleSettings(sec_grp_name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(SecurityGroupRuleSettingsError):
+ with self.assertRaises(SecurityGroupRuleConfigError):
SecurityGroupRuleSettings(**{'sec_grp_name': 'foo'})
def test_name_and_direction(self):
settings = SecurityGroupRuleSettings(sec_grp_name='foo',
direction=Direction.ingress)
self.assertEqual('foo', settings.sec_grp_name)
- self.assertEqual(Direction.ingress, settings.direction)
+ self.assertEqual(Direction.ingress.value, settings.direction.value)
def test_config_name_and_direction(self):
settings = SecurityGroupRuleSettings(
**{'sec_grp_name': 'foo', 'direction': 'ingress'})
self.assertEqual('foo', settings.sec_grp_name)
- self.assertEqual(Direction.ingress, settings.direction)
+ self.assertEqual(Direction.ingress.value, settings.direction.value)
+
+ def test_proto_ah_str(self):
+ settings = SecurityGroupRuleSettings(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 'ah'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress.value, settings.direction.value)
+ self.assertEqual(Protocol.ah.value, settings.protocol.value)
+
+ def test_proto_ah_value(self):
+ settings = SecurityGroupRuleSettings(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 51})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress.value, settings.direction.value)
+ self.assertEqual(Protocol.ah.value, settings.protocol.value)
+
+ def test_proto_any(self):
+ settings = SecurityGroupRuleSettings(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 'any'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress.value, settings.direction.value)
+ self.assertEqual(Protocol.null.value, settings.protocol.value)
+
+ def test_proto_null(self):
+ settings = SecurityGroupRuleSettings(
+ **{'sec_grp_name': 'foo', 'direction': 'ingress',
+ 'protocol': 'null'})
+ self.assertEqual('foo', settings.sec_grp_name)
+ self.assertEqual(Direction.ingress.value, settings.direction.value)
+ self.assertEqual(Protocol.null.value, settings.protocol.value)
def test_all(self):
settings = SecurityGroupRuleSettings(
@@ -68,10 +103,10 @@ class SecurityGroupRuleSettingsUnitTests(unittest.TestCase):
remote_ip_prefix='prfx')
self.assertEqual('foo', settings.sec_grp_name)
self.assertEqual('fubar', settings.description)
- self.assertEqual(Direction.egress, settings.direction)
+ self.assertEqual(Direction.egress.value, settings.direction.value)
self.assertEqual('rgi', settings.remote_group_id)
- self.assertEqual(Protocol.icmp, settings.protocol)
- self.assertEqual(Ethertype.IPv6, settings.ethertype)
+ self.assertEqual(Protocol.icmp.value, settings.protocol.value)
+ self.assertEqual(Ethertype.IPv6.value, settings.ethertype.value)
self.assertEqual(1, settings.port_range_min)
self.assertEqual(2, settings.port_range_max)
self.assertEqual('prfx', settings.remote_ip_prefix)
@@ -89,10 +124,10 @@ class SecurityGroupRuleSettingsUnitTests(unittest.TestCase):
'remote_ip_prefix': 'prfx'})
self.assertEqual('foo', settings.sec_grp_name)
self.assertEqual('fubar', settings.description)
- self.assertEqual(Direction.egress, settings.direction)
+ self.assertEqual(Direction.egress.value, settings.direction.value)
self.assertEqual('rgi', settings.remote_group_id)
- self.assertEqual(Protocol.tcp, settings.protocol)
- self.assertEqual(Ethertype.IPv6, settings.ethertype)
+ self.assertEqual(Protocol.tcp.value, settings.protocol.value)
+ self.assertEqual(Ethertype.IPv6.value, settings.ethertype.value)
self.assertEqual(1, settings.port_range_min)
self.assertEqual(2, settings.port_range_max)
self.assertEqual('prfx', settings.remote_ip_prefix)
@@ -104,11 +139,11 @@ class SecurityGroupSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(SecurityGroupSettingsError):
+ with self.assertRaises(SecurityGroupConfigError):
SecurityGroupSettings()
def test_empty_config(self):
- with self.assertRaises(SecurityGroupSettingsError):
+ with self.assertRaises(SecurityGroupConfigError):
SecurityGroupSettings(**dict())
def test_name_only(self):
@@ -123,7 +158,7 @@ class SecurityGroupSettingsUnitTests(unittest.TestCase):
rule_setting = SecurityGroupRuleSettings(
sec_grp_name='bar', direction=Direction.ingress,
description='test_rule_1')
- with self.assertRaises(SecurityGroupSettingsError):
+ with self.assertRaises(SecurityGroupConfigError):
SecurityGroupSettings(name='foo', rule_settings=[rule_setting])
def test_all(self):
@@ -157,8 +192,8 @@ class SecurityGroupSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo', settings.project_name)
self.assertEqual(1, len(settings.rule_settings))
self.assertEqual('bar', settings.rule_settings[0].sec_grp_name)
- self.assertEqual(Direction.ingress,
- settings.rule_settings[0].direction)
+ self.assertEqual(Direction.ingress.value,
+ settings.rule_settings[0].direction.value)
class CreateSecurityGroupTests(OSIntegrationTestCase):
@@ -175,7 +210,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.sec_grp_name = guid + 'name'
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
# Initialize for cleanup
self.sec_grp_creator = None
@@ -193,15 +229,15 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
"""
Tests the creation of an OpenStack Security Group without custom rules.
"""
- # Create Image
- sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name,
- description='hello group')
+ # Create Security Group
+ sec_grp_settings = SecurityGroupConfig(name=self.sec_grp_name,
+ description='hello group')
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
self.os_creds, sec_grp_settings)
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNotNone(sec_grp)
validation_utils.objects_equivalent(
@@ -214,23 +250,24 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group()))
def test_create_group_admin_user_to_new_project(self):
"""
Tests the creation of an OpenStack Security Group without custom rules.
"""
- # Create Image
- sec_grp_settings = SecurityGroupSettings(
+ # Create Security Group
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
- project_name=self.admin_os_creds.project_name)
- self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
- self.os_creds, sec_grp_settings)
+ project_name=self.os_creds.project_name)
+ self.sec_grp_creator = OpenStackSecurityGroup(
+ self.admin_os_creds, sec_grp_settings)
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNotNone(sec_grp)
validation_utils.objects_equivalent(
@@ -243,15 +280,26 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
+ self.assertEqual(self.sec_grp_creator.get_security_group().id,
+ sec_grp.id)
+
+ proj_creator = OpenStackSecurityGroup(
+ self.os_creds, SecurityGroupConfig(name=self.sec_grp_name))
+ proj_creator.create()
+
+ self.assertEqual(self.sec_grp_creator.get_security_group().id,
+ proj_creator.get_security_group().id)
+
def test_create_group_new_user_to_admin_project(self):
"""
Tests the creation of an OpenStack Security Group without custom rules.
"""
- # Create Image
- sec_grp_settings = SecurityGroupSettings(
+ # Create Security Group
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
project_name=self.os_creds.project_name)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -259,7 +307,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNotNone(sec_grp)
validation_utils.objects_equivalent(
@@ -272,16 +320,17 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
def test_create_delete_group(self):
"""
Tests the creation of an OpenStack Security Group without custom rules.
"""
- # Create Image
- sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name,
- description='hello group')
+ # Create Security Group
+ sec_grp_settings = SecurityGroupConfig(name=self.sec_grp_name,
+ description='hello group')
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
self.os_creds, sec_grp_settings)
created_sec_grp = self.sec_grp_creator.create()
@@ -289,12 +338,13 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group()))
neutron_utils.delete_security_group(self.neutron, created_sec_grp)
self.assertIsNone(neutron_utils.get_security_group(
- self.neutron,
+ self.neutron, self.keystone,
sec_grp_settings=self.sec_grp_creator.sec_grp_settings))
self.sec_grp_creator.clean()
@@ -304,13 +354,13 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
Tests the creation of an OpenStack Security Group with one simple
custom rule.
"""
- # Create Image
+ # Create Security Group
sec_grp_rule_settings = list()
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.ingress,
description='test_rule_1'))
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=sec_grp_rule_settings)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -318,7 +368,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
validation_utils.objects_equivalent(
self.sec_grp_creator.get_security_group(), sec_grp)
rules = neutron_utils.get_rules_by_security_group(
@@ -329,7 +379,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
def test_create_group_with_one_complex_rule(self):
@@ -337,15 +388,15 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
Tests the creation of an OpenStack Security Group with one simple
custom rule.
"""
- # Create Image
+ # Create Security Group
sec_grp_rule_settings = list()
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv4,
port_range_min=10, port_range_max=20,
description='test_rule_1'))
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=sec_grp_rule_settings)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -353,7 +404,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
validation_utils.objects_equivalent(
self.sec_grp_creator.get_security_group(), sec_grp)
rules = neutron_utils.get_rules_by_security_group(
@@ -364,7 +415,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
def test_create_group_with_several_rules(self):
@@ -372,24 +424,24 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
Tests the creation of an OpenStack Security Group with one simple
custom rule.
"""
- # Create Image
+ # Create Security Group
sec_grp_rule_settings = list()
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.ingress,
description='test_rule_1'))
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv6,
description='test_rule_2'))
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv4,
port_range_min=10, port_range_max=20,
description='test_rule_3'))
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=sec_grp_rule_settings)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -397,7 +449,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
validation_utils.objects_equivalent(
self.sec_grp_creator.get_security_group(), sec_grp)
rules = neutron_utils.get_rules_by_security_group(
@@ -408,7 +460,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
def test_add_rule(self):
@@ -416,13 +469,13 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
Tests the creation of an OpenStack Security Group with one simple
custom rule then adds one after creation.
"""
- # Create Image
+ # Create Security Group
sec_grp_rule_settings = list()
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.ingress,
description='test_rule_1'))
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=sec_grp_rule_settings)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -430,7 +483,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
validation_utils.objects_equivalent(
self.sec_grp_creator.get_security_group(), sec_grp)
@@ -439,7 +492,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
rules = neutron_utils.get_rules_by_security_group(
@@ -448,7 +502,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
validation_utils.objects_equivalent(self.sec_grp_creator.get_rules(),
rules)
- self.sec_grp_creator.add_rule(SecurityGroupRuleSettings(
+ self.sec_grp_creator.add_rule(SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_creator.sec_grp_settings.name,
direction=Direction.egress, protocol=Protocol.icmp,
description='test_rule_2'))
@@ -461,24 +515,24 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
Tests the creation of an OpenStack Security Group with two simple
custom rules then removes one by the rule ID.
"""
- # Create Image
+ # Create Security Group
sec_grp_rule_settings = list()
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.ingress,
description='test_rule_1'))
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv6,
description='test_rule_2'))
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv4,
port_range_min=10, port_range_max=20,
description='test_rule_3'))
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=sec_grp_rule_settings)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -486,7 +540,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
validation_utils.objects_equivalent(
self.sec_grp_creator.get_security_group(), sec_grp)
rules = neutron_utils.get_rules_by_security_group(
@@ -497,7 +551,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
self.sec_grp_creator.remove_rule(
@@ -512,24 +567,24 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
Tests the creation of an OpenStack Security Group with two simple
custom rules then removes one by the rule setting object
"""
- # Create Image
+ # Create Security Group
sec_grp_rule_settings = list()
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.ingress,
description='test_rule_1'))
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv6,
description='test_rule_2'))
sec_grp_rule_settings.append(
- SecurityGroupRuleSettings(
+ SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.egress,
protocol=Protocol.udp, ethertype=Ethertype.IPv4,
port_range_min=10, port_range_max=20,
description='test_rule_3'))
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=sec_grp_rule_settings)
self.sec_grp_creator = create_security_group.OpenStackSecurityGroup(
@@ -537,7 +592,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.sec_grp_creator.create()
sec_grp = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
validation_utils.objects_equivalent(
self.sec_grp_creator.get_security_group(), sec_grp)
@@ -549,7 +604,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertTrue(
validate_sec_grp(
- self.neutron, self.sec_grp_creator.sec_grp_settings,
+ self.neutron, self.keystone,
+ self.sec_grp_creator.sec_grp_settings,
self.sec_grp_creator.get_security_group(), rules))
self.sec_grp_creator.remove_rule(rule_setting=sec_grp_rule_settings[0])
@@ -559,11 +615,13 @@ class CreateSecurityGroupTests(OSIntegrationTestCase):
self.assertEqual(len(rules) - 1, len(rules_after_del))
-def validate_sec_grp(neutron, sec_grp_settings, sec_grp, rules=list()):
+def validate_sec_grp(neutron, keystone, sec_grp_settings, sec_grp,
+ rules=list()):
"""
Returns True is the settings on a security group are properly contained
on the SNAPS SecurityGroup domain object
:param neutron: the neutron client
+ :param keystone: the keystone client
:param sec_grp_settings: the security group configuration
:param sec_grp: the SNAPS-OO security group object
:param rules: collection of SNAPS-OO security group rule objects
@@ -572,10 +630,10 @@ def validate_sec_grp(neutron, sec_grp_settings, sec_grp, rules=list()):
return (sec_grp.description == sec_grp_settings.description and
sec_grp.name == sec_grp_settings.name and
validate_sec_grp_rules(
- neutron, sec_grp_settings.rule_settings, rules))
+ neutron, keystone, sec_grp_settings.rule_settings, rules))
-def validate_sec_grp_rules(neutron, rule_settings, rules):
+def validate_sec_grp_rules(neutron, keystone, rule_settings, rules):
"""
Returns True is the settings on a security group rule are properly
contained on the SNAPS SecurityGroupRule domain object.
@@ -583,7 +641,8 @@ def validate_sec_grp_rules(neutron, rule_settings, rules):
this is the only means to tell if the rule is custom or defaulted by
OpenStack
:param neutron: the neutron client
- :param rule_settings: collection of SecurityGroupRuleSettings objects
+ :param keystone: the keystone client
+ :param rule_settings: collection of SecurityGroupRuleConfig objects
:param rules: a collection of SecurityGroupRule domain objects
:return: T/F
"""
@@ -592,13 +651,8 @@ def validate_sec_grp_rules(neutron, rule_settings, rules):
if rule_setting.description:
match = False
for rule in rules:
- if rule_setting.protocol == Protocol.null:
- setting_proto = None
- else:
- setting_proto = rule_setting.protocol.name
-
sec_grp = neutron_utils.get_security_group(
- neutron, sec_grp_name=rule_setting.sec_grp_name)
+ neutron, keystone, sec_grp_name=rule_setting.sec_grp_name)
setting_eth_type = create_security_group.Ethertype.IPv4
if rule_setting.ethertype:
@@ -607,15 +661,19 @@ def validate_sec_grp_rules(neutron, rule_settings, rules):
if not sec_grp:
return False
+ proto_str = 'null'
+ if rule.protocol:
+ proto_str = rule.protocol
+
if (rule.description == rule_setting.description and
rule.direction == rule_setting.direction.name and
rule.ethertype == setting_eth_type.name and
rule.port_range_max == rule_setting.port_range_max and
rule.port_range_min == rule_setting.port_range_min and
- rule.protocol == setting_proto and
+ proto_str == str(rule_setting.protocol.value) and
rule.remote_group_id == rule_setting.remote_group_id and
rule.remote_ip_prefix == rule_setting.remote_ip_prefix and
- rule.security_group_id == sec_grp.id):
+ rule.security_group_id == sec_grp.id):
match = True
break
@@ -623,3 +681,69 @@ def validate_sec_grp_rules(neutron, rule_settings, rules):
return False
return True
+
+
+class CreateMultipleSecurityGroupTests(OSIntegrationTestCase):
+ """
+ Test for the CreateSecurityGroup class and how it interacts with security
+ groups within other projects with the same name
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateSecurityGroup object that is responsible for
+ downloading and creating an OS image file within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.sec_grp_name = guid + 'name'
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ # Initialize for cleanup
+ self.admin_sec_grp_config = SecurityGroupConfig(
+ name=self.sec_grp_name, description='hello group')
+ self.sec_grp_creator_admin = OpenStackSecurityGroup(
+ self.admin_os_creds, self.admin_sec_grp_config)
+ self.sec_grp_creator_admin.create()
+ self.sec_grp_creator_proj = None
+
+ def tearDown(self):
+ """
+ Cleans the image and downloaded image file
+ """
+ if self.sec_grp_creator_admin:
+ self.sec_grp_creator_admin.clean()
+ if self.sec_grp_creator_proj:
+ self.sec_grp_creator_proj.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_sec_grp_same_name_diff_proj(self):
+ """
+ Tests the creation of an OpenStack Security Group with the same name
+ within a different project/tenant.
+ """
+ # Create Security Group
+ sec_grp_config = SecurityGroupConfig(
+ name=self.sec_grp_name, description='hello group')
+ self.sec_grp_creator_proj = OpenStackSecurityGroup(
+ self.os_creds, sec_grp_config)
+ self.sec_grp_creator_proj.create()
+
+ self.assertNotEqual(
+ self.sec_grp_creator_admin.get_security_group().id,
+ self.sec_grp_creator_proj.get_security_group().id)
+
+ admin_sec_grp_creator = OpenStackSecurityGroup(
+ self.admin_os_creds, self.admin_sec_grp_config)
+ admin_sec_grp_creator.create()
+ self.assertEqual(self.sec_grp_creator_admin.get_security_group().id,
+ admin_sec_grp_creator.get_security_group().id)
+
+ proj_sec_grp_creator = OpenStackSecurityGroup(
+ self.os_creds, sec_grp_config)
+ proj_sec_grp_creator.create()
+ self.assertEqual(self.sec_grp_creator_proj.get_security_group().id,
+ proj_sec_grp_creator.get_security_group().id)
diff --git a/snaps/openstack/tests/create_stack_tests.py b/snaps/openstack/tests/create_stack_tests.py
index f4eb1eb..7da5bc7 100644
--- a/snaps/openstack/tests/create_stack_tests.py
+++ b/snaps/openstack/tests/create_stack_tests.py
@@ -12,12 +12,19 @@
# 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.
+import os
import time
import pkg_resources
from heatclient.exc import HTTPBadRequest
+
+import snaps
from snaps import file_utils
-from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
+from snaps.config.flavor import FlavorConfig
+from snaps.config.image import ImageConfig
+from snaps.config.stack import (StackConfigError, StackConfig,
+ STATUS_UPDATE_COMPLETE)
+from snaps.openstack.create_flavor import OpenStackFlavor
from snaps.openstack.create_image import OpenStackImage
try:
@@ -31,10 +38,11 @@ import uuid
from snaps.openstack import create_stack
from snaps.openstack.create_stack import (
- StackSettings, StackSettingsError, StackCreationError)
+ StackSettings, StackCreationError, StackError, OpenStackHeatStack)
from snaps.openstack.tests import openstack_tests, create_instance_tests
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
-from snaps.openstack.utils import heat_utils, neutron_utils, nova_utils
+from snaps.openstack.utils import (
+ heat_utils, neutron_utils, nova_utils, keystone_utils)
__author__ = 'spisarski'
@@ -47,19 +55,19 @@ class StackSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(StackSettingsError):
+ with self.assertRaises(StackConfigError):
StackSettings()
def test_empty_config(self):
- with self.assertRaises(StackSettingsError):
+ with self.assertRaises(StackConfigError):
StackSettings(**dict())
def test_name_only(self):
- with self.assertRaises(StackSettingsError):
+ with self.assertRaises(StackConfigError):
StackSettings(name='foo')
def test_config_with_name_only(self):
- with self.assertRaises(StackSettingsError):
+ with self.assertRaises(StackConfigError):
StackSettings(**{'name': 'foo'})
def test_config_minimum_template(self):
@@ -68,7 +76,7 @@ class StackSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo', settings.template)
self.assertIsNone(settings.template_path)
self.assertIsNone(settings.env_values)
- self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
settings.stack_create_timeout)
def test_config_minimum_template_path(self):
@@ -77,7 +85,7 @@ class StackSettingsUnitTests(unittest.TestCase):
self.assertIsNone(settings.template)
self.assertEqual('foo', settings.template_path)
self.assertIsNone(settings.env_values)
- self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
settings.stack_create_timeout)
def test_minimum_template(self):
@@ -86,7 +94,7 @@ class StackSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo', settings.template)
self.assertIsNone(settings.template_path)
self.assertIsNone(settings.env_values)
- self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
settings.stack_create_timeout)
def test_minimum_template_path(self):
@@ -95,7 +103,7 @@ class StackSettingsUnitTests(unittest.TestCase):
self.assertEqual('foo', settings.template_path)
self.assertIsNone(settings.template)
self.assertIsNone(settings.env_values)
- self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
+ self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT,
settings.stack_create_timeout)
def test_all(self):
@@ -123,35 +131,31 @@ class StackSettingsUnitTests(unittest.TestCase):
class CreateStackSuccessTests(OSIntegrationTestCase):
"""
- Tests for the CreateStack class defined in create_stack.py
+ Tests for the OpenStackHeatStack class defined in create_stack.py
"""
def setUp(self):
- """
- Instantiates the CreateStack object that is responsible for downloading
- and creating an OS stack file within OpenStack
- """
+ self.user_roles = ['heat_stack_owner']
+
super(self.__class__, self).__start__()
self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.heat_creds = self.admin_os_creds
- self.heat_creds.project_name = self.admin_os_creds.project_name
-
- self.heat_cli = heat_utils.heat_client(self.heat_creds)
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
self.stack_creator = None
self.image_creator = OpenStackImage(
- self.heat_creds, openstack_tests.cirros_image_settings(
+ self.os_creds, openstack_tests.cirros_image_settings(
name=self.guid + '-image',
image_metadata=self.image_metadata))
self.image_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=1, metadata=self.flavor_metadata)
self.flavor_creator = OpenStackFlavor(
- self.admin_os_creds,
- FlavorSettings(name=self.guid + '-flavor-name', ram=256, disk=10,
- vcpus=1))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
self.network_name = self.guid + '-net'
@@ -199,13 +203,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
# Create Stack
# Set the default stack settings, then set any custom parameters sent
# from the app
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template_path=self.heat_tmplt_path,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- created_stack = self.stack_creator.create()
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ created_stack = self.stack_creator.create(block=True)
self.assertIsNotNone(created_stack)
retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
@@ -215,6 +219,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
self.assertEqual(created_stack.id, retrieved_stack.id)
self.assertEqual(0, len(self.stack_creator.get_outputs()))
+ derived_creator = create_stack.generate_creator(
+ self.os_creds, retrieved_stack,
+ [self.image_creator.image_settings])
+ derived_stack = derived_creator.get_stack()
+ self.assertEqual(retrieved_stack, derived_stack)
+
def test_create_stack_short_timeout(self):
"""
Tests the creation of an OpenStack stack from Heat template file.
@@ -222,15 +232,15 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
# Create Stack
# Set the default stack settings, then set any custom parameters sent
# from the app
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template_path=self.heat_tmplt_path,
env_values=self.env_values, stack_create_timeout=0)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
with self.assertRaises(StackCreationError):
- self.stack_creator.create()
+ self.stack_creator.create(block=True)
def test_create_stack_template_dict(self):
"""
@@ -241,13 +251,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
# from the app
template_dict = heat_utils.parse_heat_template_str(
file_utils.read_file(self.heat_tmplt_path))
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template=template_dict,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- created_stack = self.stack_creator.create()
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ created_stack = self.stack_creator.create(block=True)
self.assertIsNotNone(created_stack)
retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
@@ -265,13 +275,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
# Create Stack
template_dict = heat_utils.parse_heat_template_str(
file_utils.read_file(self.heat_tmplt_path))
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template=template_dict,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- created_stack = self.stack_creator.create()
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ created_stack = self.stack_creator.create(block=True)
self.assertIsNotNone(created_stack)
retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
@@ -280,7 +290,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
self.assertEqual(created_stack.name, retrieved_stack.name)
self.assertEqual(created_stack.id, retrieved_stack.id)
self.assertEqual(0, len(self.stack_creator.get_outputs()))
- self.assertEqual(create_stack.STATUS_CREATE_COMPLETE,
+ self.assertEqual(snaps.config.stack.STATUS_CREATE_COMPLETE,
self.stack_creator.get_status())
# Delete Stack manually
@@ -291,7 +301,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
while time.time() < end_time:
status = heat_utils.get_stack_status(self.heat_cli,
retrieved_stack.id)
- if status == create_stack.STATUS_DELETE_COMPLETE:
+ if status == snaps.config.stack.STATUS_DELETE_COMPLETE:
deleted = True
break
@@ -309,13 +319,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
# Create Stack
template_dict = heat_utils.parse_heat_template_str(
file_utils.read_file(self.heat_tmplt_path))
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template=template_dict,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- created_stack1 = self.stack_creator.create()
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ created_stack1 = self.stack_creator.create(block=True)
retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
created_stack1.id)
@@ -325,9 +335,8 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
self.assertEqual(0, len(self.stack_creator.get_outputs()))
# Should be retrieving the instance data
- stack_creator2 = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- stack2 = stack_creator2.create()
+ stack_creator2 = OpenStackHeatStack(self.os_creds, stack_settings)
+ stack2 = stack_creator2.create(block=True)
self.assertEqual(created_stack1.id, stack2.id)
def test_retrieve_network_creators(self):
@@ -335,13 +344,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
Tests the creation of an OpenStack stack from Heat template file and
the retrieval of the network creator.
"""
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template_path=self.heat_tmplt_path,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- created_stack = self.stack_creator.create()
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ created_stack = self.stack_creator.create(block=True)
self.assertIsNotNone(created_stack)
net_creators = self.stack_creator.get_network_creators()
@@ -349,17 +358,22 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
self.assertEqual(1, len(net_creators))
self.assertEqual(self.network_name, net_creators[0].get_network().name)
- neutron = neutron_utils.neutron_client(self.os_creds)
+ # Need to use 'admin' creds as heat creates objects under it's own
+ # project/tenant
+ neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
net_by_name = neutron_utils.get_network(
- neutron, network_name=net_creators[0].get_network().name)
+ neutron, keystone, network_name=net_creators[0].get_network().name)
self.assertEqual(net_creators[0].get_network(), net_by_name)
self.assertIsNotNone(neutron_utils.get_network_by_id(
neutron, net_creators[0].get_network().id))
- self.assertEqual(1, len(net_creators[0].get_subnets()))
- subnet = net_creators[0].get_subnets()[0]
+ self.assertEqual(1, len(net_creators[0].get_network().subnets))
+ subnet = net_creators[0].get_network().subnets[0]
subnet_by_name = neutron_utils.get_subnet(
- neutron, subnet_name=subnet.name)
+ neutron, net_creators[0].get_network(), subnet_name=subnet.name)
self.assertEqual(subnet, subnet_by_name)
subnet_by_id = neutron_utils.get_subnet_by_id(neutron, subnet.id)
@@ -371,13 +385,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
Tests the creation of an OpenStack stack from Heat template file and
the retrieval of the network creator.
"""
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template_path=self.heat_tmplt_path,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
- created_stack = self.stack_creator.create()
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ created_stack = self.stack_creator.create(block=True)
self.assertIsNotNone(created_stack)
vm_inst_creators = self.stack_creator.get_vm_inst_creators()
@@ -386,36 +400,36 @@ class CreateStackSuccessTests(OSIntegrationTestCase):
self.assertEqual(self.vm_inst_name,
vm_inst_creators[0].get_vm_inst().name)
- nova = nova_utils.nova_client(self.admin_os_creds)
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(self.os_creds, self.os_session)
vm_inst_by_name = nova_utils.get_server(
- nova, server_name=vm_inst_creators[0].get_vm_inst().name)
+ nova, neutron, keystone,
+ server_name=vm_inst_creators[0].get_vm_inst().name)
+
self.assertEqual(vm_inst_creators[0].get_vm_inst(), vm_inst_by_name)
self.assertIsNotNone(nova_utils.get_server_object_by_id(
- nova, vm_inst_creators[0].get_vm_inst().id))
+ nova, neutron, keystone, vm_inst_creators[0].get_vm_inst().id))
-class CreateComplexStackTests(OSIntegrationTestCase):
+class CreateStackFloatingIpTests(OSIntegrationTestCase):
"""
- Tests for the CreateStack class defined in create_stack.py
+ Tests to ensure that floating IPs can be accessed via an
+ OpenStackVmInstance object obtained from the OpenStackHeatStack instance
"""
def setUp(self):
- """
- Instantiates the CreateStack object that is responsible for downloading
- and creating an OS stack file within OpenStack
- """
+ self.user_roles = ['heat_stack_owner', 'admin']
+
super(self.__class__, self).__start__()
self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.heat_creds = self.admin_os_creds
- self.heat_creds.project_name = self.admin_os_creds.project_name
-
- self.heat_cli = heat_utils.heat_client(self.heat_creds)
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
self.stack_creator = None
self.image_creator = OpenStackImage(
- self.heat_creds, openstack_tests.cirros_image_settings(
+ self.os_creds, openstack_tests.cirros_image_settings(
name=self.guid + '-image',
image_metadata=self.image_metadata))
self.image_creator.create()
@@ -424,6 +438,7 @@ class CreateComplexStackTests(OSIntegrationTestCase):
self.subnet_name = self.guid + '-subnet'
self.flavor1_name = self.guid + '-flavor1'
self.flavor2_name = self.guid + '-flavor2'
+ self.sec_grp_name = self.guid + '-sec_grp'
self.vm_inst1_name = self.guid + '-inst1'
self.vm_inst2_name = self.guid + '-inst2'
self.keypair_name = self.guid + '-kp'
@@ -433,15 +448,20 @@ class CreateComplexStackTests(OSIntegrationTestCase):
'image2_name': self.image_creator.image_settings.name,
'flavor1_name': self.flavor1_name,
'flavor2_name': self.flavor2_name,
+ 'flavor_extra_specs': self.flavor_metadata,
'net_name': self.network_name,
'subnet_name': self.subnet_name,
'inst1_name': self.vm_inst1_name,
'inst2_name': self.vm_inst2_name,
- 'keypair_name': self.keypair_name}
+ 'keypair_name': self.keypair_name,
+ 'external_net_name': self.ext_net_name,
+ 'security_group_name': self.sec_grp_name}
self.heat_tmplt_path = pkg_resources.resource_filename(
'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
+ self.vm_inst_creators = list()
+
def tearDown(self):
"""
Cleans the stack and downloaded stack file
@@ -458,6 +478,17 @@ class CreateComplexStackTests(OSIntegrationTestCase):
except:
pass
+ for vm_inst_creator in self.vm_inst_creators:
+ try:
+ keypair_settings = vm_inst_creator.keypair_settings
+ if keypair_settings and keypair_settings.private_filepath:
+ expanded_path = os.path.expanduser(
+ keypair_settings.private_filepath)
+ os.chmod(expanded_path, 0o755)
+ os.remove(expanded_path)
+ except:
+ pass
+
super(self.__class__, self).__clean__()
def test_connect_via_ssh_heat_vm(self):
@@ -466,22 +497,55 @@ class CreateComplexStackTests(OSIntegrationTestCase):
the retrieval of two VM instance creators and attempt to connect via
SSH to the first one with a floating IP.
"""
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
template_path=self.heat_tmplt_path,
env_values=self.env_values)
- self.stack_creator = create_stack.OpenStackHeatStack(
- self.heat_creds, stack_settings,
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings,
[self.image_creator.image_settings])
- created_stack = self.stack_creator.create()
+ created_stack = self.stack_creator.create(block=True)
self.assertIsNotNone(created_stack)
- vm_inst_creators = self.stack_creator.get_vm_inst_creators(
+ self.vm_inst_creators = self.stack_creator.get_vm_inst_creators(
heat_keypair_option='private_key')
- self.assertIsNotNone(vm_inst_creators)
- self.assertEqual(2, len(vm_inst_creators))
+ self.assertIsNotNone(self.vm_inst_creators)
+ self.assertEqual(2, len(self.vm_inst_creators))
+
+ for vm_inst_creator in self.vm_inst_creators:
+ if vm_inst_creator.get_vm_inst().name == self.vm_inst1_name:
+ self.assertTrue(
+ create_instance_tests.validate_ssh_client(vm_inst_creator))
+ else:
+ vm_settings = vm_inst_creator.instance_settings
+ self.assertEqual(0, len(vm_settings.floating_ip_settings))
+
+ def test_connect_via_ssh_heat_vm_derived(self):
+ """
+ Tests the the retrieval of two VM instance creators from a derived
+ OpenStackHeatStack object and attempt to connect via
+ SSH to the first one with a floating IP.
+ """
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=self.heat_tmplt_path,
+ env_values=self.env_values)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings,
+ [self.image_creator.image_settings])
+ created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(created_stack)
+
+ derived_stack = create_stack.generate_creator(
+ self.os_creds, created_stack,
+ [self.image_creator.image_settings])
+
+ self.vm_inst_creators = derived_stack.get_vm_inst_creators(
+ heat_keypair_option='private_key')
+ self.assertIsNotNone(self.vm_inst_creators)
+ self.assertEqual(2, len(self.vm_inst_creators))
- for vm_inst_creator in vm_inst_creators:
+ for vm_inst_creator in self.vm_inst_creators:
if vm_inst_creator.get_vm_inst().name == self.vm_inst1_name:
self.assertTrue(
create_instance_tests.validate_ssh_client(vm_inst_creator))
@@ -490,16 +554,654 @@ class CreateComplexStackTests(OSIntegrationTestCase):
self.assertEqual(0, len(vm_settings.floating_ip_settings))
-class CreateStackNegativeTests(OSIntegrationTestCase):
+class CreateStackNestedResourceTests(OSIntegrationTestCase):
+ """
+ Tests to ensure that nested heat templates work
+ """
+
+ def setUp(self):
+ self.user_roles = ['heat_stack_owner']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.image_creator = OpenStackImage(
+ self.os_creds, openstack_tests.cirros_image_settings(
+ name="{}-{}".format(self.guid, 'image'),
+ image_metadata=self.image_metadata))
+ self.image_creator.create()
+
+ flavor_config = openstack_tests.get_flavor_config(
+ name="{}-{}".format(self.guid, 'flavor-name'), ram=256, disk=10,
+ vcpus=1, metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(
+ self.admin_os_creds, flavor_config)
+ self.flavor_creator.create()
+
+ env_values = {
+ 'network_name': self.guid + '-network',
+ 'public_network': self.ext_net_name,
+ 'agent_image': self.image_creator.image_settings.name,
+ 'agent_flavor': self.flavor_creator.flavor_settings.name,
+ 'key_name': self.guid + '-key',
+ }
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'agent-group.yaml')
+ heat_resource_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'agent.yaml')
+
+ stack_settings = StackConfig(
+ name="{}-{}".format(
+ self.__class__.__name__, str(self.guid) + '-stack'),
+ template_path=heat_tmplt_path,
+ resource_files=[heat_resource_path],
+ env_values=env_values)
+
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings,
+ [self.image_creator.image_settings])
+
+ self.vm_inst_creators = list()
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except:
+ pass
+
+ for vm_inst_creator in self.vm_inst_creators:
+ try:
+ keypair_settings = vm_inst_creator.keypair_settings
+ if keypair_settings and keypair_settings.private_filepath:
+ expanded_path = os.path.expanduser(
+ keypair_settings.private_filepath)
+ os.chmod(expanded_path, 0o755)
+ os.remove(expanded_path)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_nested(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of two VM instance creators and attempt to connect via
+ SSH to the first one with a floating IP.
+ """
+ created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(created_stack)
+
+ self.vm_inst_creators = self.stack_creator.get_vm_inst_creators(
+ heat_keypair_option='private_key')
+ self.assertIsNotNone(self.vm_inst_creators)
+ self.assertEqual(1, len(self.vm_inst_creators))
+
+ for vm_inst_creator in self.vm_inst_creators:
+ self.assertTrue(
+ create_instance_tests.validate_ssh_client(vm_inst_creator))
+
+
+class CreateStackUpdateTests(OSIntegrationTestCase):
+ """
+ Tests to ensure that stack update commands work
+ """
+
+ def setUp(self):
+ self.user_roles = ['heat_stack_owner']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.image_creator = OpenStackImage(
+ self.os_creds, openstack_tests.cirros_image_settings(
+ name=self.guid + '-image',
+ image_metadata=self.image_metadata))
+ self.image_creator.create()
+
+ self.flavor_creator = OpenStackFlavor(
+ self.admin_os_creds,
+ FlavorConfig(
+ name=self.guid + '-flavor-name', ram=256, disk=10, vcpus=1))
+ self.flavor_creator.create()
+
+ env_values = {
+ 'network_name': self.guid + '-network',
+ 'public_network': self.ext_net_name,
+ 'agent_image': self.image_creator.image_settings.name,
+ 'agent_flavor': self.flavor_creator.flavor_settings.name,
+ 'key_name': self.guid + '-key',
+ }
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'agent-group.yaml')
+ heat_resource_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'agent.yaml')
+
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=heat_tmplt_path,
+ resource_files=[heat_resource_path],
+ env_values=env_values)
+
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings,
+ [self.image_creator.image_settings])
+
+ self.vm_inst_creators = list()
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except:
+ pass
+
+ for vm_inst_creator in self.vm_inst_creators:
+ try:
+ keypair_settings = vm_inst_creator.keypair_settings
+ if keypair_settings and keypair_settings.private_filepath:
+ expanded_path = os.path.expanduser(
+ keypair_settings.private_filepath)
+ os.chmod(expanded_path, 0o755)
+ os.remove(expanded_path)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_update(self):
+ """
+ Tests the update of an OpenStack stack from Heat template file
+ by changing the number of VM instances from 1 to 2, and
+ the retrieval of two VM instance creators and attempt to connect via
+ SSH to the first one with a floating IP.
+ """
+ created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(created_stack)
+
+ self.vm_inst_creators = self.stack_creator.get_vm_inst_creators(
+ heat_keypair_option='private_key')
+ self.assertIsNotNone(self.vm_inst_creators)
+ self.assertEqual(1, len(self.vm_inst_creators))
+
+ for vm_inst_creator in self.vm_inst_creators:
+ self.assertTrue(
+ create_instance_tests.validate_ssh_client(vm_inst_creator))
+
+ env_values = {
+ 'network_name': self.guid + '-network',
+ 'public_network': self.ext_net_name,
+ 'agent_count': 2,
+ 'agent_image': self.image_creator.image_settings.name,
+ 'agent_flavor': self.flavor_creator.flavor_settings.name,
+ 'key_name': self.guid + '-key',
+ }
+
+ updated_stack = self.stack_creator.update(env_values, block=True)
+ self.assertIsNotNone(updated_stack)
+ self.assertEqual(STATUS_UPDATE_COMPLETE, updated_stack.status)
+
+ self.vm_inst_creators = self.stack_creator.get_vm_inst_creators(
+ heat_keypair_option='private_key')
+ self.assertIsNotNone(self.vm_inst_creators)
+ self.assertEqual(2, len(self.vm_inst_creators))
+
+ for vm_inst_creator in self.vm_inst_creators:
+ self.assertTrue(
+ create_instance_tests.validate_ssh_client(vm_inst_creator))
+
+
+class CreateStackRouterTests(OSIntegrationTestCase):
"""
- Negative test cases for the CreateStack class
+ Tests for the CreateStack class defined in create_stack.py where the
+ target is a Network, Subnet, and Router
"""
def setUp(self):
+ """
+ Instantiates the CreateStack object that is responsible for downloading
+ and creating an OS stack file within OpenStack
+ """
+ self.user_roles = ['heat_stack_owner']
+
super(self.__class__, self).__start__()
- self.heat_creds = self.admin_os_creds
- self.heat_creds.project_name = self.admin_os_creds.project_name
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.net_name = self.guid + '-net'
+ self.subnet_name = self.guid + '-subnet'
+ self.router_name = self.guid + '-router'
+
+ self.env_values = {
+ 'net_name': self.net_name,
+ 'subnet_name': self.subnet_name,
+ 'router_name': self.router_name,
+ 'external_net_name': self.ext_net_name}
+
+ self.heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'router_heat_template.yaml')
+
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=self.heat_tmplt_path,
+ env_values=self.env_values)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ self.created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(self.created_stack)
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_retrieve_router_creator(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of an OpenStackRouter creator/state machine instance
+ """
+ router_creators = self.stack_creator.get_router_creators()
+ self.assertEqual(1, len(router_creators))
+
+ creator = router_creators[0]
+ self.assertEqual(self.router_name, creator.router_settings.name)
+
+ router = creator.get_router()
+
+ ext_net = neutron_utils.get_network(
+ self.neutron, self.keystone, network_name=self.ext_net_name)
+ self.assertEqual(ext_net.id, router.external_network_id)
+
+
+class CreateStackVolumeTests(OSIntegrationTestCase):
+ """
+ Tests to ensure that floating IPs can be accessed via an
+ OpenStackVolume object obtained from the OpenStackHeatStack instance
+ """
+
+ def setUp(self):
+
+ self.user_roles = ['heat_stack_owner', 'admin']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.volume_name = self.guid + '-volume'
+ self.volume_type_name = self.guid + '-volume-type'
+
+ self.env_values = {
+ 'volume_name': self.volume_name,
+ 'volume_type_name': self.volume_type_name}
+
+ self.heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
+
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=self.heat_tmplt_path,
+ env_values=self.env_values)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ self.created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(self.created_stack)
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_retrieve_volume_creator(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of an OpenStackVolume creator/state machine instance
+ """
+ volume_creators = self.stack_creator.get_volume_creators()
+ self.assertEqual(1, len(volume_creators))
+
+ creator = volume_creators[0]
+ self.assertEqual(self.volume_name, creator.volume_settings.name)
+ self.assertEqual(self.volume_name, creator.get_volume().name)
+ self.assertEqual(self.volume_type_name,
+ creator.volume_settings.type_name)
+ self.assertEqual(self.volume_type_name, creator.get_volume().type)
+ self.assertEqual(1, creator.volume_settings.size)
+ self.assertEqual(1, creator.get_volume().size)
+
+ def test_retrieve_volume_type_creator(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of an OpenStackVolume creator/state machine instance
+ """
+ volume_type_creators = self.stack_creator.get_volume_type_creators()
+ self.assertEqual(1, len(volume_type_creators))
+
+ creator = volume_type_creators[0]
+ self.assertIsNotNone(creator)
+
+ volume_type = creator.get_volume_type()
+ self.assertIsNotNone(volume_type)
+
+ self.assertEqual(self.volume_type_name, volume_type.name)
+ self.assertTrue(volume_type.public)
+ self.assertIsNone(volume_type.qos_spec)
+
+ # TODO - Add encryption back and find out why it broke in Pike
+ # encryption = volume_type.encryption
+ # self.assertIsNotNone(encryption)
+ # self.assertIsNone(encryption.cipher)
+ # self.assertEqual('front-end', encryption.control_location)
+ # self.assertIsNone(encryption.key_size)
+ # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
+ # encryption.provider)
+ # self.assertEqual(volume_type.id, encryption.volume_type_id)
+
+
+class CreateStackFlavorTests(OSIntegrationTestCase):
+ """
+ Tests to ensure that floating IPs can be accessed via an
+ OpenStackFlavor object obtained from the OpenStackHeatStack instance
+ """
+
+ def setUp(self):
+
+ self.user_roles = ['heat_stack_owner', 'admin']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml')
+
+ stack_settings = StackConfig(
+ name=self.guid + '-stack',
+ template_path=self.heat_tmplt_path)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ self.created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(self.created_stack)
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_retrieve_flavor_creator(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of an OpenStackVolume creator/state machine instance
+ """
+ flavor_creators = self.stack_creator.get_flavor_creators()
+ self.assertEqual(1, len(flavor_creators))
+
+ creator = flavor_creators[0]
+ self.assertTrue(creator.get_flavor().name.startswith(self.guid))
+ self.assertEqual(1024, creator.get_flavor().ram)
+ self.assertEqual(200, creator.get_flavor().disk)
+ self.assertEqual(8, creator.get_flavor().vcpus)
+ self.assertEqual(0, creator.get_flavor().ephemeral)
+ self.assertIsNone(creator.get_flavor().swap)
+ self.assertEqual(1.0, creator.get_flavor().rxtx_factor)
+ self.assertTrue(creator.get_flavor().is_public)
+
+
+class CreateStackKeypairTests(OSIntegrationTestCase):
+ """
+ Tests to ensure that floating IPs can be accessed via an
+ OpenStackKeypair object obtained from the OpenStackHeatStack instance
+ """
+
+ def setUp(self):
+
+ self.user_roles = ['heat_stack_owner']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.keypair_name = self.guid + '-kp'
+
+ self.env_values = {
+ 'keypair_name': self.keypair_name}
+
+ self.heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
+
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=self.heat_tmplt_path,
+ env_values=self.env_values)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ self.created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(self.created_stack)
+
+ self.keypair_creators = list()
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+ for keypair_creator in self.keypair_creators:
+ try:
+ keypair_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_retrieve_keypair_creator(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of an OpenStackKeypair creator/state machine instance
+ """
+ self.kp_creators = self.stack_creator.get_keypair_creators(
+ 'private_key')
+ self.assertEqual(1, len(self.kp_creators))
+
+ self.keypair_creator = self.kp_creators[0]
+
+ self.assertEqual(self.keypair_name,
+ self.keypair_creator.get_keypair().name)
+ self.assertIsNotNone(
+ self.keypair_creator.keypair_settings.private_filepath)
+
+ private_file_contents = file_utils.read_file(
+ self.keypair_creator.keypair_settings.private_filepath)
+ self.assertTrue(private_file_contents.startswith(
+ '-----BEGIN RSA PRIVATE KEY-----'))
+
+ keypair = nova_utils.get_keypair_by_id(
+ self.nova, self.keypair_creator.get_keypair().id)
+ self.assertIsNotNone(keypair)
+ self.assertEqual(self.keypair_creator.get_keypair(), keypair)
+
+
+class CreateStackSecurityGroupTests(OSIntegrationTestCase):
+ """
+ Tests for the OpenStackHeatStack class to ensure it returns an
+ OpenStackSecurityGroup object
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateStack object that is responsible for downloading
+ and creating an OS stack file within OpenStack
+ """
+ self.user_roles = ['heat_stack_owner']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.security_group_name = self.guid + '-sec-grp'
+
+ self.env_values = {
+ 'security_group_name': self.security_group_name}
+
+ self.heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'security_group_heat_template.yaml')
+
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=self.heat_tmplt_path,
+ env_values=self.env_values)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+ self.created_stack = self.stack_creator.create(block=True)
+ self.assertIsNotNone(self.created_stack)
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_retrieve_security_group_creator(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file and
+ the retrieval of an OpenStackSecurityGroup creator/state machine
+ instance
+ """
+ sec_grp_creators = self.stack_creator.get_security_group_creators()
+ self.assertEqual(1, len(sec_grp_creators))
+
+ creator = sec_grp_creators[0]
+ sec_grp = creator.get_security_group()
+
+ self.assertEqual(self.security_group_name, sec_grp.name)
+ self.assertEqual('Test description', sec_grp.description)
+ self.assertEqual(2, len(sec_grp.rules))
+
+ has_ssh_rule = False
+ has_icmp_rule = False
+
+ for rule in sec_grp.rules:
+ if (rule.security_group_id == sec_grp.id
+ and rule.direction == 'egress'
+ and rule.ethertype == 'IPv4'
+ and rule.port_range_min == 22
+ and rule.port_range_max == 22
+ and rule.protocol == 'tcp'
+ and rule.remote_group_id is None
+ and rule.remote_ip_prefix == '0.0.0.0/0'):
+ has_ssh_rule = True
+ if (rule.security_group_id == sec_grp.id
+ and rule.direction == 'ingress'
+ and rule.ethertype == 'IPv4'
+ and rule.port_range_min is None
+ and rule.port_range_max is None
+ and rule.protocol == 'icmp'
+ and rule.remote_group_id is None
+ and rule.remote_ip_prefix == '0.0.0.0/0'):
+ has_icmp_rule = True
+
+ self.assertTrue(has_ssh_rule)
+ self.assertTrue(has_icmp_rule)
+
+
+class CreateStackNegativeTests(OSIntegrationTestCase):
+ """
+ Negative test cases for the OpenStackHeatStack class with poor
+ configuration
+ """
+
+ def setUp(self):
+ self.user_roles = ['heat_stack_owner']
+
+ super(self.__class__, self).__start__()
self.stack_name = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.stack_creator = None
@@ -509,26 +1211,135 @@ class CreateStackNegativeTests(OSIntegrationTestCase):
def tearDown(self):
if self.stack_creator:
self.stack_creator.clean()
+
super(self.__class__, self).__clean__()
def test_missing_dependencies(self):
"""
Expect an StackCreationError when the stack file does not exist
"""
- stack_settings = StackSettings(name=self.stack_name,
- template_path=self.heat_tmplt_path)
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
+ stack_settings = StackConfig(name=self.stack_name,
+ template_path=self.heat_tmplt_path)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
with self.assertRaises(HTTPBadRequest):
- self.stack_creator.create()
+ self.stack_creator.create(block=True)
def test_bad_stack_file(self):
"""
Expect an StackCreationError when the stack file does not exist
"""
- stack_settings = StackSettings(name=self.stack_name,
- template_path='foo')
- self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
- stack_settings)
+ stack_settings = StackConfig(
+ name=self.stack_name, template_path='foo')
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
with self.assertRaises(IOError):
- self.stack_creator.create()
+ self.stack_creator.create(block=True)
+
+
+class CreateStackFailureTests(OSIntegrationTestCase):
+ """
+ Tests for the OpenStackHeatStack class defined in create_stack.py for
+ when failures occur. Failures are being triggered by allocating 1 million
+ CPUs.
+ """
+
+ def setUp(self):
+ self.user_roles = ['heat_stack_owner']
+
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.heat_cli = heat_utils.heat_client(self.os_creds, self.os_session)
+ self.stack_creator = None
+
+ self.tmp_file = file_utils.save_string_to_file(
+ ' ', str(uuid.uuid4()) + '-bad-image')
+ self.image_creator = OpenStackImage(
+ self.os_creds, ImageConfig(
+ name=self.guid + 'image', image_file=self.tmp_file.name,
+ image_user='foo', img_format='qcow2'))
+ self.image_creator.create()
+
+ # Create Flavor
+ self.flavor_creator = OpenStackFlavor(
+ self.admin_os_creds,
+ FlavorConfig(
+ name=self.guid + '-flavor-name', ram=256, disk=10,
+ vcpus=1000000))
+ self.flavor_creator.create()
+
+ self.network_name = self.guid + '-net'
+ self.subnet_name = self.guid + '-subnet'
+ self.vm_inst_name = self.guid + '-inst'
+
+ self.env_values = {
+ 'image_name': self.image_creator.image_settings.name,
+ 'flavor_name': self.flavor_creator.flavor_settings.name,
+ 'net_name': self.network_name,
+ 'subnet_name': self.subnet_name,
+ 'inst_name': self.vm_inst_name}
+
+ self.heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'test_heat_template.yaml')
+
+ def tearDown(self):
+ """
+ Cleans the stack and downloaded stack file
+ """
+ if self.stack_creator:
+ try:
+ self.stack_creator.clean()
+ except:
+ pass
+
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except:
+ pass
+
+ if self.tmp_file:
+ try:
+ os.remove(self.tmp_file.name)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_stack_failure(self):
+ """
+ Tests the creation of an OpenStack stack from Heat template file that
+ should always fail due to too many CPU cores
+ """
+ # Create Stack
+ # Set the default stack settings, then set any custom parameters sent
+ # from the app
+ stack_settings = StackConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
+ template_path=self.heat_tmplt_path,
+ env_values=self.env_values)
+ self.stack_creator = OpenStackHeatStack(
+ self.os_creds, stack_settings)
+
+ with self.assertRaises(StackError):
+ try:
+ self.stack_creator.create(block=True)
+ except StackError:
+ resources = heat_utils.get_resources(
+ self.heat_cli, self.stack_creator.get_stack().id)
+
+ found = False
+ for resource in resources:
+ if (resource.status ==
+ snaps.config.stack.STATUS_CREATE_COMPLETE):
+ found = True
+ self.assertTrue(found)
+ raise
diff --git a/snaps/openstack/tests/create_user_tests.py b/snaps/openstack/tests/create_user_tests.py
index ffae596..c15a71f 100644
--- a/snaps/openstack/tests/create_user_tests.py
+++ b/snaps/openstack/tests/create_user_tests.py
@@ -15,6 +15,7 @@
import unittest
import uuid
+from snaps.config.user import UserConfig
from snaps.openstack.create_user import OpenStackUser, UserSettings
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
from snaps.openstack.utils import keystone_utils
@@ -102,13 +103,14 @@ class CreateUserSuccessTests(OSComponentTestCase):
"""
guid = str(uuid.uuid4())[:-19]
guid = self.__class__.__name__ + '-' + guid
- self.user_settings = UserSettings(
+ self.user_settings = UserConfig(
name=guid + '-name',
password=guid + '-password',
- roles={'admin': self.os_creds.project_name},
+ roles={'admin': self.os_creds.project_name,
+ 'Admin': self.os_creds.project_name},
domain_name=self.os_creds.user_domain_name)
- self.keystone = keystone_utils.keystone_client(self.os_creds)
+ self.keystone = keystone_utils.keystone_client(self.os_creds, self.os_session)
# Initialize for cleanup
self.user_creator = None
@@ -120,6 +122,8 @@ class CreateUserSuccessTests(OSComponentTestCase):
if self.user_creator:
self.user_creator.clean()
+ super(self.__class__, self).__clean__()
+
def test_create_user(self):
"""
Tests the creation of an OpenStack user.
@@ -180,6 +184,8 @@ class CreateUserSuccessTests(OSComponentTestCase):
self.assertEqual(created_user, retrieved_user)
role = keystone_utils.get_role_by_name(self.keystone, 'admin')
+ if not role:
+ role = keystone_utils.get_role_by_name(self.keystone, 'Admin')
self.assertIsNotNone(role)
os_proj = keystone_utils.get_project(
diff --git a/snaps/openstack/tests/create_volume_tests.py b/snaps/openstack/tests/create_volume_tests.py
new file mode 100644
index 0000000..3c9a346
--- /dev/null
+++ b/snaps/openstack/tests/create_volume_tests.py
@@ -0,0 +1,487 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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 cinderclient.exceptions import NotFound, BadRequest
+
+from snaps.config.volume import VolumeConfig, VolumeConfigError
+from snaps.config.volume_type import VolumeTypeConfig
+from snaps.openstack.create_image import OpenStackImage
+from snaps.openstack.create_volume_type import OpenStackVolumeType
+from snaps.openstack.tests import openstack_tests
+
+try:
+ from urllib.request import URLError
+except ImportError:
+ from urllib2 import URLError
+
+import logging
+import unittest
+import uuid
+
+from snaps.openstack.create_volume import (
+ VolumeSettings, OpenStackVolume)
+from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
+from snaps.openstack.utils import cinder_utils, keystone_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('create_volume_tests')
+
+
+class VolumeSettingsUnitTests(unittest.TestCase):
+ """
+ Tests the construction of the VolumeSettings class
+ """
+
+ def test_no_params(self):
+ with self.assertRaises(VolumeConfigError):
+ VolumeSettings()
+
+ def test_empty_config(self):
+ with self.assertRaises(VolumeConfigError):
+ VolumeSettings(**dict())
+
+ def test_name_only(self):
+ settings = VolumeSettings(name='foo')
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.description)
+ self.assertEquals(1, settings.size)
+ self.assertIsNone(settings.image_name)
+ self.assertIsNone(settings.type_name)
+ self.assertIsNone(settings.availability_zone)
+ self.assertFalse(settings.multi_attach)
+
+ def test_config_with_name_only(self):
+ settings = VolumeSettings(**{'name': 'foo'})
+ self.assertEqual('foo', settings.name)
+ self.assertIsNone(settings.description)
+ self.assertEquals(1, settings.size)
+ self.assertIsNone(settings.image_name)
+ self.assertIsNone(settings.type_name)
+ self.assertIsNone(settings.availability_zone)
+ self.assertFalse(settings.multi_attach)
+
+ def test_all_strings(self):
+ settings = VolumeSettings(
+ name='foo', description='desc', size='2', image_name='image',
+ type_name='type', availability_zone='zone1', multi_attach='true')
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual(2, settings.size)
+ self.assertEqual('image', settings.image_name)
+ self.assertEqual('type', settings.type_name)
+ self.assertEqual('zone1', settings.availability_zone)
+ self.assertTrue(settings.multi_attach)
+
+ def test_all_correct_type(self):
+ settings = VolumeSettings(
+ name='foo', description='desc', size=2, image_name='image',
+ type_name='bar', availability_zone='zone1', multi_attach=True)
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual(2, settings.size)
+ self.assertEqual('image', settings.image_name)
+ self.assertEqual('bar', settings.type_name)
+ self.assertEqual('zone1', settings.availability_zone)
+ self.assertTrue(settings.multi_attach)
+
+ def test_config_all(self):
+ settings = VolumeSettings(
+ **{'name': 'foo', 'description': 'desc', 'size': '2',
+ 'image_name': 'foo', 'type_name': 'bar',
+ 'availability_zone': 'zone1', 'multi_attach': 'true'})
+
+ self.assertEqual('foo', settings.name)
+ self.assertEqual('desc', settings.description)
+ self.assertEqual(2, settings.size)
+ self.assertEqual('foo', settings.image_name)
+ self.assertEqual('bar', settings.type_name)
+ self.assertEqual('zone1', settings.availability_zone)
+ self.assertTrue(settings.multi_attach)
+
+
+class CreateSimpleVolumeSuccessTests(OSIntegrationTestCase):
+ """
+ Test for the CreateVolume class defined in create_volume.py
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateVolume object that is responsible for
+ downloading and creating an OS volume file within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ guid = uuid.uuid4()
+ self.volume_settings = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(guid))
+
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ self.volume_creator = None
+
+ def tearDown(self):
+ """
+ Cleans the volume and downloaded volume file
+ """
+ if self.volume_creator:
+ self.volume_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_volume_simple(self):
+ """
+ Tests the creation of a simple OpenStack volume.
+ """
+ # Create Volume
+ self.volume_creator = OpenStackVolume(
+ self.os_creds, self.volume_settings)
+ created_volume = self.volume_creator.create(block=True)
+ self.assertIsNotNone(created_volume)
+
+ retrieved_volume = cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings=self.volume_settings,
+ project_name=self.os_creds.project_name)
+
+ self.assertIsNotNone(retrieved_volume)
+ self.assertEqual(created_volume.id, retrieved_volume.id)
+ self.assertTrue(created_volume == retrieved_volume)
+
+ def test_create_delete_volume(self):
+ """
+ Tests the creation then deletion of an OpenStack volume to ensure
+ clean() does not raise an Exception.
+ """
+ # Create Volume
+ self.volume_creator = OpenStackVolume(
+ self.os_creds, self.volume_settings)
+ created_volume = self.volume_creator.create(block=True)
+ self.assertIsNotNone(created_volume)
+
+ retrieved_volume = cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings=self.volume_settings,
+ project_name=self.os_creds.project_name)
+ self.assertIsNotNone(retrieved_volume)
+ self.assertEqual(created_volume, retrieved_volume)
+
+ # Delete Volume manually
+ self.volume_creator.clean()
+
+ self.assertIsNone(cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings=self.volume_settings,
+ project_name=self.os_creds.project_name))
+
+ # Must not throw an exception when attempting to cleanup non-existent
+ # volume
+ self.volume_creator.clean()
+ self.assertIsNone(self.volume_creator.get_volume())
+
+ def test_create_same_volume(self):
+ """
+ Tests the creation of an OpenStack volume when one already exists.
+ """
+ # Create Volume
+ self.volume_creator = OpenStackVolume(
+ self.os_creds, self.volume_settings)
+ volume1 = self.volume_creator.create(block=True)
+
+ retrieved_volume = cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings=self.volume_settings,
+ project_name=self.os_creds.project_name)
+ self.assertEqual(volume1, retrieved_volume)
+
+ # Should be retrieving the instance data
+ os_volume_2 = OpenStackVolume(
+ self.os_creds, self.volume_settings)
+ volume2 = os_volume_2.create(block=True)
+ self.assertEqual(volume1, volume2)
+
+
+class CreateSimpleVolumeFailureTests(OSIntegrationTestCase):
+ """
+ Test for the CreateVolume class defined in create_volume.py
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateVolume object that is responsible for
+ downloading and creating an OS volume file within OpenStack
+ """
+ super(self.__class__, self).__start__()
+
+ self.guid = uuid.uuid4()
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+ self.volume_creator = None
+
+ def tearDown(self):
+ """
+ Cleans the volume and downloaded volume file
+ """
+ if self.volume_creator:
+ self.volume_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_volume_bad_size(self):
+ """
+ Tests the creation of an OpenStack volume with a negative size to
+ ensure it raises a BadRequest exception.
+ """
+ volume_settings = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid), size=-1)
+
+ # Create Volume
+ self.volume_creator = OpenStackVolume(self.os_creds, volume_settings)
+
+ with self.assertRaises(BadRequest):
+ self.volume_creator.create(block=True)
+
+ def test_create_volume_bad_type(self):
+ """
+ Tests the creation of an OpenStack volume with a type that does not
+ exist to ensure it raises a NotFound exception.
+ """
+ volume_settings = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid),
+ type_name='foo')
+
+ # Create Volume
+ self.volume_creator = OpenStackVolume(self.os_creds, volume_settings)
+
+ with self.assertRaises(NotFound):
+ self.volume_creator.create(block=True)
+
+ def test_create_volume_bad_image(self):
+ """
+ Tests the creation of an OpenStack volume with an image that does not
+ exist to ensure it raises a BadRequest exception.
+ """
+ volume_settings = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(self.guid),
+ image_name='foo')
+
+ # Create Volume
+ self.volume_creator = OpenStackVolume(self.os_creds, volume_settings)
+
+ with self.assertRaises(BadRequest):
+ self.volume_creator.create(block=True)
+
+
+class CreateVolumeWithTypeTests(OSIntegrationTestCase):
+ """
+ Test cases for the CreateVolume when attempting to associate it to a
+ Volume Type
+ """
+
+ def setUp(self):
+ super(self.__class__, self).__start__()
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.volume_name = guid + '-vol'
+ self.volume_type_name = guid + '-vol-type'
+
+ self.volume_type_creator = OpenStackVolumeType(
+ self.admin_os_creds, VolumeTypeConfig(name=self.volume_type_name))
+ self.volume_type_creator.create()
+ self.volume_creator = None
+
+ def tearDown(self):
+ if self.volume_creator:
+ self.volume_creator.clean()
+ if self.volume_type_creator:
+ self.volume_type_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ def test_bad_volume_type(self):
+ """
+ Expect a NotFound to be raised when the volume type does not exist
+ """
+ self.volume_creator = OpenStackVolume(
+ self.os_creds,
+ VolumeConfig(name=self.volume_name, type_name='foo'))
+
+ with self.assertRaises(NotFound):
+ self.volume_creator.create()
+
+ def test_valid_volume_type(self):
+ """
+ Expect a NotFound to be raised when the volume type does not exist
+ """
+ self.volume_creator = OpenStackVolume(
+ self.admin_os_creds, VolumeConfig(
+ name=self.volume_name, type_name=self.volume_type_name))
+
+ created_volume = self.volume_creator.create(block=True)
+ self.assertIsNotNone(created_volume)
+ self.assertEqual(self.volume_type_name, created_volume.type)
+
+
+class CreateVolumeWithImageTests(OSIntegrationTestCase):
+ """
+ Test cases for the CreateVolume when attempting to associate it to an Image
+ """
+
+ def setUp(self):
+ super(self.__class__, self).__start__()
+
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.volume_name = guid + '-vol'
+ self.image_name = guid + '-image'
+
+ os_image_settings = openstack_tests.cirros_image_settings(
+ name=self.image_name, image_metadata=self.image_metadata)
+ # Create Image
+ self.image_creator = OpenStackImage(self.os_creds,
+ os_image_settings)
+ self.image_creator.create()
+ self.volume_creator = None
+
+ def tearDown(self):
+ if self.volume_creator:
+ try:
+ self.volume_creator.clean()
+ except:
+ pass
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_bad_image_name(self):
+ """
+ Tests OpenStackVolume#create() method to ensure a volume is NOT created
+ when associating it to an invalid image name
+ """
+ self.volume_creator = OpenStackVolume(
+ self.os_creds,
+ VolumeConfig(name=self.volume_name, image_name='foo'))
+
+ with self.assertRaises(BadRequest):
+ self.volume_creator.create(block=True)
+
+ def test_valid_volume_image(self):
+ """
+ Tests OpenStackVolume#create() method to ensure a volume is NOT created
+ when associating it to an invalid image name
+ """
+ self.volume_creator = OpenStackVolume(
+ self.os_creds,
+ VolumeConfig(name=self.volume_name, image_name=self.image_name))
+
+ created_volume = self.volume_creator.create(block=True)
+ self.assertIsNotNone(created_volume)
+ self.assertEqual(
+ self.volume_creator.volume_settings.name, created_volume.name)
+ self.assertTrue(self.volume_creator.volume_active())
+
+ retrieved_volume = cinder_utils.get_volume_by_id(
+ self.cinder, created_volume.id)
+
+ self.assertEqual(created_volume, retrieved_volume)
+
+
+class CreateVolMultipleCredsTests(OSIntegrationTestCase):
+ """
+ Test for the OpenStackVolume class and how it interacts with volumes
+ created with differenct credentials and to other projects with the same
+ name
+ """
+ def setUp(self):
+ super(self.__class__, self).__start__()
+
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.volume_creators = list()
+
+ def tearDown(self):
+ for volume_creator in self.volume_creators:
+ volume_creator.clean()
+
+ super(self.__class__, self).__clean__()
+
+ # TODO - activate after cinder API bug has been fixed
+ # see https://bugs.launchpad.net/cinder/+bug/1641982 as to why this test
+ # is not activated
+ # def test_create_by_admin_to_other_proj(self):
+ # """
+ # Creates a volume as admin to the project of os_creds then instantiates
+ # a creator object with the os_creds project to ensure it initializes
+ # without creation
+ # """
+ # self.volume_creators.append(OpenStackVolume(
+ # self.admin_os_creds, VolumeConfig(
+ # name=self.guid + '-vol',
+ # project_name=self.os_creds.project_name)))
+ # admin_vol = self.volume_creators[0].create(block=True)
+ #
+ # self.volume_creators.append(OpenStackVolume(
+ # self.os_creds, VolumeConfig(name=self.guid + '-vol')))
+ # proj_vol = self.volume_creators[1].create(block=True)
+ #
+ # self.assertEqual(admin_vol, proj_vol)
+
+ def test_create_two_vol_same_name_diff_proj(self):
+ """
+ Creates a volume as admin to the project of os_creds then instantiates
+ a creator object with the os_creds project to ensure it initializes
+ without creation
+ """
+ vol_name = self.guid + '-vol'
+ self.volume_creators.append(OpenStackVolume(
+ self.admin_os_creds, VolumeConfig(name=vol_name)))
+ admin_vol = self.volume_creators[0].create(block=True)
+ self.assertIsNotNone(admin_vol)
+
+ admin_key = keystone_utils.keystone_client(
+ self.admin_os_creds, self.admin_os_session)
+ admin_proj = keystone_utils.get_project(
+ admin_key, project_name=self.admin_os_creds.project_name)
+ self.assertEqual(admin_vol.project_id, admin_proj.id)
+
+ admin_cinder = cinder_utils.cinder_client(
+ self.admin_os_creds, self.admin_os_session)
+ admin_vol_get = cinder_utils.get_volume(
+ admin_cinder, admin_key, volume_name=vol_name,
+ project_name=self.admin_os_creds.project_name)
+ self.assertIsNotNone(admin_vol_get)
+ self.assertEqual(admin_vol, admin_vol_get)
+
+ self.volume_creators.append(OpenStackVolume(
+ self.os_creds, VolumeConfig(name=vol_name)))
+ proj_vol = self.volume_creators[1].create(block=True)
+ self.assertIsNotNone(proj_vol)
+
+ self.assertNotEqual(admin_vol, proj_vol)
+
+ proj_key = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ proj_cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+ proj_vol_get = cinder_utils.get_volume(
+ proj_cinder, proj_key, volume_name=vol_name,
+ project_name=self.os_creds.project_name)
+
+ self.assertIsNotNone(proj_vol_get)
+ self.assertEqual(proj_vol, proj_vol_get)
diff --git a/snaps/openstack/tests/create_volume_type_tests.py b/snaps/openstack/tests/create_volume_type_tests.py
index 93e9351..b797274 100644
--- a/snaps/openstack/tests/create_volume_type_tests.py
+++ b/snaps/openstack/tests/create_volume_type_tests.py
@@ -12,7 +12,11 @@
# 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 snaps.openstack.create_qos import QoSSettings, Consumer, OpenStackQoS
+from snaps.config.volume_type import (
+ VolumeTypeConfig, VolumeTypeEncryptionConfig, VolumeTypeConfigError,
+ ControlLocation)
+from snaps.config.qos import QoSConfig, Consumer
+from snaps.openstack.create_qos import OpenStackQoS
try:
from urllib.request import URLError
@@ -25,8 +29,7 @@ import uuid
from snaps.openstack import create_volume_type
from snaps.openstack.create_volume_type import (
- VolumeTypeSettings, VolumeTypeSettingsError, VolumeTypeEncryptionSettings,
- ControlLocation)
+ VolumeTypeSettings, VolumeTypeEncryptionSettings, OpenStackVolumeType)
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
from snaps.openstack.utils import cinder_utils
@@ -41,11 +44,11 @@ class VolumeTypeSettingsUnitTests(unittest.TestCase):
"""
def test_no_params(self):
- with self.assertRaises(VolumeTypeSettingsError):
+ with self.assertRaises(VolumeTypeConfigError):
VolumeTypeSettings()
def test_empty_config(self):
- with self.assertRaises(VolumeTypeSettingsError):
+ with self.assertRaises(VolumeTypeConfigError):
VolumeTypeSettings(**dict())
def test_name_only(self):
@@ -109,7 +112,7 @@ class VolumeTypeSettingsUnitTests(unittest.TestCase):
class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase):
"""
- Test for the OpenStackVolumeType class defined in create_volume_type.py
+ Test for the OpenStackVolumeType class defined in py
without any QoS Specs or Encryption
"""
@@ -121,11 +124,13 @@ class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase):
super(self.__class__, self).__start__()
guid = uuid.uuid4()
- self.volume_type_settings = VolumeTypeSettings(
+ self.volume_type_settings = VolumeTypeConfig(
name=self.__class__.__name__ + '-' + str(guid))
- self.cinder = cinder_utils.cinder_client(self.os_creds)
- self.volume_type_creator = None
+ self.cinder = cinder_utils.cinder_client(
+ self.admin_os_creds, self.admin_os_session)
+ self.volume_type_creator = OpenStackVolumeType(
+ self.admin_os_creds, self.volume_type_settings)
def tearDown(self):
"""
@@ -141,8 +146,6 @@ class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase):
Tests the creation of an OpenStack volume.
"""
# Create VolumeType
- self.volume_type_creator = create_volume_type.OpenStackVolumeType(
- self.os_creds, self.volume_type_settings)
created_volume_type = self.volume_type_creator.create()
self.assertIsNotNone(created_volume_type)
self.assertEqual(self.volume_type_settings.name,
@@ -163,8 +166,6 @@ class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase):
clean() does not raise an Exception.
"""
# Create VolumeType
- self.volume_type_creator = create_volume_type.OpenStackVolumeType(
- self.os_creds, self.volume_type_settings)
created_volume_type = self.volume_type_creator.create()
self.assertIsNotNone(created_volume_type)
@@ -189,8 +190,6 @@ class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase):
Tests the creation of an OpenStack volume_type when one already exists.
"""
# Create VolumeType
- self.volume_type_creator = create_volume_type.OpenStackVolumeType(
- self.os_creds, self.volume_type_settings)
volume_type1 = self.volume_type_creator.create()
retrieved_volume_type = cinder_utils.get_volume_type(
@@ -199,7 +198,7 @@ class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase):
# Should be retrieving the instance data
os_volume_type_2 = create_volume_type.OpenStackVolumeType(
- self.os_creds, self.volume_type_settings)
+ self.admin_os_creds, self.volume_type_settings)
volume_type2 = os_volume_type_2.create()
self.assertEqual(volume_type2, volume_type2)
@@ -213,16 +212,17 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase):
def setUp(self):
super(self.__class__, self).__start__()
- self.cinder = cinder_utils.cinder_client(self.os_creds)
+ self.cinder = cinder_utils.cinder_client(
+ self.admin_os_creds, self.admin_os_session)
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.volume_type_name = guid + '-vol_type'
self.volume_type_creator = None
- qos_settings = QoSSettings(
+ qos_settings = QoSConfig(
name=guid + '-qos-spec', consumer=Consumer.both)
- self.qos_creator = OpenStackQoS(self.os_creds, qos_settings)
+ self.qos_creator = OpenStackQoS(self.admin_os_creds, qos_settings)
self.qos_creator.create()
def tearDown(self):
@@ -238,9 +238,9 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase):
"""
Creates a Volume Type object with an associated QoS Spec
"""
- self.volume_type_creator = create_volume_type.OpenStackVolumeType(
- self.os_creds,
- VolumeTypeSettings(
+ self.volume_type_creator = OpenStackVolumeType(
+ self.admin_os_creds,
+ VolumeTypeConfig(
name=self.volume_type_name,
qos_spec_name=self.qos_creator.qos_settings.name))
@@ -264,12 +264,12 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase):
"""
Creates a Volume Type object with encryption
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name='foo', provider_class='bar',
control_location=ControlLocation.back_end)
- self.volume_type_creator = create_volume_type.OpenStackVolumeType(
- self.os_creds,
- VolumeTypeSettings(
+ self.volume_type_creator = OpenStackVolumeType(
+ self.admin_os_creds,
+ VolumeTypeConfig(
name=self.volume_type_name,
encryption=encryption_settings))
@@ -293,12 +293,12 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase):
"""
Creates a Volume Type object with encryption and an associated QoS Spec
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name='foo', provider_class='bar',
control_location=ControlLocation.back_end)
- self.volume_type_creator = create_volume_type.OpenStackVolumeType(
- self.os_creds,
- VolumeTypeSettings(
+ self.volume_type_creator = OpenStackVolumeType(
+ self.admin_os_creds,
+ VolumeTypeConfig(
name=self.volume_type_name,
encryption=encryption_settings,
qos_spec_name=self.qos_creator.qos_settings.name))
diff --git a/snaps/openstack/tests/heat/agent-group.yaml b/snaps/openstack/tests/heat/agent-group.yaml
new file mode 100644
index 0000000..494da0a
--- /dev/null
+++ b/snaps/openstack/tests/heat/agent-group.yaml
@@ -0,0 +1,119 @@
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+##############################################################################
+heat_template_version: 2013-05-23
+
+parameters:
+ public_network:
+ type: string
+ constraints:
+ - custom_constraint: neutron.network
+ agent_flavor:
+ type: string
+ agent_image:
+ type: string
+ volume_size:
+ type: number
+ description: Size of the volume to be created.
+ default: 1
+ constraints:
+ - range: { min: 1, max: 1024 }
+ description: must be between 1 and 1024 Gb.
+ agent_count:
+ type: number
+ default: 1
+ constraints:
+ - range: { min: 1, max: 512 }
+ description: must be between 1 and 512 agents.
+ availability_zone:
+ type: string
+ default: nova
+ network_name:
+ type: string
+ key_name:
+ type: string
+
+resources:
+ slaves:
+ type: OS::Heat::ResourceGroup
+ depends_on: [subnet, network_router_interface,
+ open_security_group, key_pair]
+ properties:
+ count: {get_param: agent_count}
+ resource_def: {
+ type: "agent.yaml",
+ properties: {
+ public_network: {get_param: public_network},
+ agent_network: {get_resource: network},
+ flavor: {get_param: agent_flavor},
+ image: {get_param: agent_image},
+ availability_zone: {get_param: availability_zone},
+ open_security_group: {get_resource: open_security_group},
+ key_name: {get_resource: key_pair},
+ volume_size: {get_param: volume_size}
+ }
+ }
+
+ network:
+ type: OS::Neutron::Net
+ properties:
+ name: { get_param: network_name }
+
+ subnet:
+ type: OS::Neutron::Subnet
+ properties:
+ network_id: { get_resource: network }
+ cidr: 172.16.0.0/16
+ gateway_ip: 172.16.0.1
+
+ network_router:
+ type: OS::Neutron::Router
+ properties:
+ external_gateway_info:
+ network: { get_param: public_network }
+
+ network_router_interface:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router_id: { get_resource: network_router }
+ subnet_id: { get_resource: subnet }
+
+ key_pair:
+ type: OS::Nova::KeyPair
+ properties:
+ save_private_key: true
+ name: { get_param: key_name }
+
+ open_security_group:
+ type: OS::Neutron::SecurityGroup
+ properties:
+ description: An open security group to allow all access to the slaves
+ rules:
+ - remote_ip_prefix: 0.0.0.0/0
+ protocol: tcp
+ port_range_min: 22
+ port_range_max: 22
+ - remote_ip_prefix: 0.0.0.0/0
+ protocol: icmp
+
+outputs:
+ slave_ips: {
+ description: "Slave addresses",
+ value: { get_attr: [ slaves, agent_ip] }
+ }
+ private_key:
+ description: "SSH Private Key"
+ value: { get_attr: [ key_pair, private_key ]}
diff --git a/snaps/openstack/tests/heat/agent.yaml b/snaps/openstack/tests/heat/agent.yaml
new file mode 100644
index 0000000..8ac0660
--- /dev/null
+++ b/snaps/openstack/tests/heat/agent.yaml
@@ -0,0 +1,109 @@
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+##############################################################################
+heat_template_version: 2013-05-23
+
+parameters:
+ flavor:
+ type: string
+ default: test
+ image:
+ type: string
+ default: 'Ubuntu 16.04'
+ key_name:
+ type: string
+ username:
+ type: string
+ default: test_user
+ open_security_group:
+ type: string
+ volume_size:
+ type: number
+ description: Size of the volume to be created.
+ default: 1
+ constraints:
+ - range: { min: 1, max: 1024 }
+ description: must be between 1 and 1024 Gb.
+ agent_network:
+ type: string
+ constraints:
+ - custom_constraint: neutron.network
+ public_network:
+ type: string
+ constraints:
+ - custom_constraint: neutron.network
+ availability_zone:
+ type: string
+ default: nova
+
+resources:
+ agent:
+ type: "OS::Nova::Server"
+ properties:
+ name: agent
+ image: { get_param: image }
+ flavor: { get_param: flavor }
+ key_name: { get_param: key_name }
+ networks:
+ - port: { get_resource: agent_port }
+ user_data: { get_resource: agent_config }
+ user_data_format: RAW
+ availability_zone: { get_param: availability_zone}
+
+ agent_config:
+ type: "OS::Heat::CloudConfig"
+ properties:
+ cloud_config:
+ users:
+ - name: { get_param: username }
+ groups: users
+ shell: /bin/bash
+ sudo: "ALL=(ALL) NOPASSWD:ALL"
+ ssh_authorized_keys:
+ - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEbnDiqZ8RjQJJzJPf074J41XlYED+zYBzaUZ5UkkUquXzymyUmoWaFBXJP+XPu4Ns44U/S8614+JxGk96tjUdJlIjL0Ag8HP6KLtTNCabucKcEASpgJIVWqJvE3E9upZLIEiTGsF8I8S67T2qq1J1uvtxyeZmyjm7NMamjyFXE53dhR2EHqSutyKK1CK74NkRY9wr3qWUIt35kLdKSVSfrr4gOOicDALbIRu77skHIvrjt+wK1VWphBdMg6ytuq5mIE6pjWAU3Gwl4aTxOU0z43ARzCLq8HVf8s/dKjYMj8plNqaIfceMbaEUqpNHv/xbvtGNG7N0aB/a4pkUQL07
+ - default
+ package_update: false
+ package_upgrade: false
+ manage_etc_hosts: localhost
+
+ agent_port:
+ type: "OS::Neutron::Port"
+ properties:
+ network_id: { get_param: agent_network }
+ security_groups:
+ - { get_param: open_security_group }
+
+ floating_ip:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: { get_param: public_network }
+ port_id: { get_resource: agent_port }
+
+ agent_volume:
+ type: OS::Cinder::Volume
+ properties:
+ size: { get_param: volume_size }
+
+ agent_volume_att:
+ type: OS::Cinder::VolumeAttachment
+ properties:
+ instance_uuid: { get_resource: agent }
+ volume_id: { get_resource: agent_volume}
+
+outputs:
+ agent_ip:
+ description: The floating IP address of the agent on the public network
+ value: { get_attr: [ floating_ip, floating_ip_address ] }
diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/configure_host.yml b/snaps/openstack/tests/heat/flavor_heat_template.yaml
index 8df03cb..060c85c 100644
--- a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/configure_host.yml
+++ b/snaps/openstack/tests/heat/flavor_heat_template.yaml
@@ -1,4 +1,5 @@
-# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
# and others. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +13,15 @@
# 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.
----
-- name: Configure NIC
- hosts: all
- become: yes
- become_method: sudo
- become_user: root
+##############################################################################
+heat_template_version: 2015-04-30
- tasks:
- - name: Setup /etc/sysconfig/network-scripts/ifcfg-eth1 file
- action: template owner=root group=root mode=644 src=../templates/ifcfg-interface dest=/etc/sysconfig/network-scripts/ifcfg-{{nic_name}}
- - name : Restart Network
- command: systemctl restart network \ No newline at end of file
+description: Simple template to deploy a single volume with encryption
+
+resources:
+ flavor:
+ type: OS::Nova::Flavor
+ properties:
+ ram: 1024
+ vcpus: 8
+ disk: 200
diff --git a/snaps/openstack/tests/heat/floating_ip_heat_template.yaml b/snaps/openstack/tests/heat/floating_ip_heat_template.yaml
index e09515e..e64c7fc 100644
--- a/snaps/openstack/tests/heat/floating_ip_heat_template.yaml
+++ b/snaps/openstack/tests/heat/floating_ip_heat_template.yaml
@@ -41,6 +41,10 @@ parameters:
label: Instance Flavor for second VM
description: Flavor name for the second instance
default: m1.med
+ flavor_extra_specs:
+ type: json
+ description: Instance Flavor extra specs
+ default: {}
net_name:
type: string
label: Test network name
@@ -61,6 +65,11 @@ parameters:
label: Keypair name
description: The name of the stack's keypair
default: keypair_name
+ security_group_name:
+ type: string
+ label: Security Group name
+ description: The name of the stack's security group
+ default: security_group_name
inst1_name:
type: string
label: First VM name
@@ -83,12 +92,14 @@ resources:
ram: 1024
vcpus: 2
disk: 2
+ extra_specs: { get_param: flavor_extra_specs }
flavor2:
type: OS::Nova::Flavor
properties:
ram: 1024
vcpus: 2
disk: 2
+ extra_specs: { get_param: flavor_extra_specs }
network:
type: OS::Neutron::Net
@@ -116,16 +127,29 @@ resources:
router: { get_resource: management_router }
subnet: { get_resource: subnet }
+ server_security_group:
+ type: OS::Neutron::SecurityGroup
+ properties:
+ description: Add security group rules for server
+ name: { get_param: security_group_name }
+ rules:
+ - remote_ip_prefix: 0.0.0.0/0
+ protocol: tcp
+ port_range_min: 22
+ port_range_max: 22
+ - remote_ip_prefix: 0.0.0.0/0
+ protocol: icmp
+
floating_ip:
type: OS::Neutron::FloatingIP
properties:
floating_network: { get_param: external_net_name }
floating_ip_association:
- type: OS::Nova::FloatingIPAssociation
+ type: OS::Neutron::FloatingIPAssociation
properties:
- floating_ip: { get_resource: floating_ip }
- server_id: {get_resource: vm1}
+ floatingip_id: { get_resource: floating_ip }
+ port_id: {get_resource: port1}
keypair:
type: OS::Nova::KeyPair
@@ -133,20 +157,28 @@ resources:
name: { get_param: keypair_name }
save_private_key: True
+ port1:
+ type: OS::Neutron::Port
+ properties:
+ network: { get_resource: network }
+ security_groups: [{ get_resource: server_security_group }]
+ fixed_ips:
+ - subnet_id: { get_resource: subnet }
+
vm1:
type: OS::Nova::Server
- depends_on: [subnet, keypair, flavor1]
+ depends_on: [subnet, keypair, flavor1, port1]
properties:
name: { get_param: inst1_name }
image: { get_param: image1_name }
flavor: { get_resource: flavor1 }
key_name: {get_resource: keypair}
networks:
- - network: { get_resource: network }
+ - port: { get_resource: port1 }
vm2:
type: OS::Nova::Server
- depends_on: [subnet, flavor2]
+ depends_on: [subnet, keypair, flavor2]
properties:
name: { get_param: inst2_name }
image: { get_param: image2_name }
diff --git a/snaps/openstack/tests/heat/keypair_heat_template.yaml b/snaps/openstack/tests/heat/keypair_heat_template.yaml
new file mode 100644
index 0000000..ffb8892
--- /dev/null
+++ b/snaps/openstack/tests/heat/keypair_heat_template.yaml
@@ -0,0 +1,39 @@
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+##############################################################################
+heat_template_version: 2015-04-30
+
+description: >
+ Test template that simply deploys a keypair with a generated key
+
+parameters:
+ keypair_name:
+ type: string
+ label: Keypair name
+ description: The name of the stack's keypair
+ default: keypair_name
+
+resources:
+ keypair:
+ type: OS::Nova::KeyPair
+ properties:
+ name: { get_param: keypair_name }
+ save_private_key: True
+
+outputs:
+ private_key:
+ description: "SSH Private Key"
+ value: { get_attr: [ keypair, private_key ]}
diff --git a/snaps/openstack/tests/heat/router_heat_template.yaml b/snaps/openstack/tests/heat/router_heat_template.yaml
new file mode 100644
index 0000000..ee7f60c
--- /dev/null
+++ b/snaps/openstack/tests/heat/router_heat_template.yaml
@@ -0,0 +1,69 @@
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+##############################################################################
+heat_template_version: 2015-04-30
+
+description: >
+ Sample template with two VMs instantiated against different images and
+ flavors on the same network and the first one has a floating IP
+
+parameters:
+ net_name:
+ type: string
+ label: Test network name
+ description: The name of the stack's network
+ default: test_net
+ subnet_name:
+ type: string
+ label: Test subnet name
+ description: The name of the stack's subnet
+ default: test_subnet
+ router_name:
+ type: string
+ label: Test router name
+ description: The name of the stack's router
+ default: mgmt_router
+ external_net_name:
+ type: string
+ description: Name of the external network which management network will connect to
+ default: external
+
+resources:
+ network:
+ type: OS::Neutron::Net
+ properties:
+ name: { get_param: net_name }
+
+ subnet:
+ type: OS::Neutron::Subnet
+ properties:
+ name: { get_param: subnet_name }
+ ip_version: 4
+ cidr: 10.1.2.0/24
+ network: { get_resource: network }
+
+ management_router:
+ type: OS::Neutron::Router
+ properties:
+ name: { get_param: router_name }
+ external_gateway_info:
+ network: { get_param: external_net_name }
+
+ management_router_interface:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router: { get_resource: management_router }
+ subnet: { get_resource: subnet }
diff --git a/snaps/openstack/tests/heat/security_group_heat_template.yaml b/snaps/openstack/tests/heat/security_group_heat_template.yaml
new file mode 100644
index 0000000..0c4f07b
--- /dev/null
+++ b/snaps/openstack/tests/heat/security_group_heat_template.yaml
@@ -0,0 +1,45 @@
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+##############################################################################
+heat_template_version: 2015-04-30
+
+description: >
+ Sample template for creating a single SecurityGroup
+
+parameters:
+ security_group_name:
+ type: string
+ label: Security Group name
+ description: The name of the stack's security group
+ default: security_group_name
+
+resources:
+ server_security_group:
+ type: OS::Neutron::SecurityGroup
+ properties:
+ description: Test description
+ name: { get_param: security_group_name }
+ rules:
+ - direction: egress
+ ethertype: IPv4
+ port_range_min: 22
+ port_range_max: 22
+ protocol: tcp
+ remote_ip_prefix: 0.0.0.0/0
+ - direction: ingress
+ ethertype: IPv4
+ protocol: icmp
+ remote_ip_prefix: 0.0.0.0/0
diff --git a/snaps/openstack/tests/heat/volume_heat_template.yaml b/snaps/openstack/tests/heat/volume_heat_template.yaml
new file mode 100644
index 0000000..1200476
--- /dev/null
+++ b/snaps/openstack/tests/heat/volume_heat_template.yaml
@@ -0,0 +1,52 @@
+##############################################################################
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+##############################################################################
+heat_template_version: 2015-04-30
+
+description: Simple template to deploy a single volume with encryption
+
+parameters:
+ volume_name:
+ type: string
+ label: Volume name
+ description: The name of the volume
+ default: test-vol-name
+ volume_type_name:
+ type: string
+ label: Volume Type name
+ description: The name of the volume type
+ default: test-vol-type-name
+
+resources:
+ volume_type:
+ type: OS::Cinder::VolumeType
+ properties:
+ name: { get_param: volume_type_name }
+
+# encryption_vol_type:
+# type: OS::Cinder::EncryptedVolumeType
+# properties:
+# provider: nova.volume.encryptors.luks.LuksEncryptor
+# control_location: front-end
+# volume_type: { get_resource: volume_type }
+
+ volume:
+ type: OS::Cinder::Volume
+ properties:
+ name: { get_param: volume_name }
+ size: 1
+# volume_type: { get_resource: encryption_vol_type }
+ volume_type: { get_resource: volume_type }
diff --git a/snaps/openstack/tests/openstack_tests.py b/snaps/openstack/tests/openstack_tests.py
index 16fb0b5..f3a1df7 100644
--- a/snaps/openstack/tests/openstack_tests.py
+++ b/snaps/openstack/tests/openstack_tests.py
@@ -15,11 +15,11 @@
import logging
import re
-import pkg_resources
from snaps import file_utils
-from snaps.openstack.create_image import ImageSettings
-from snaps.openstack.create_network import NetworkSettings, SubnetSettings
-from snaps.openstack.create_router import RouterSettings
+from snaps.config.flavor import FlavorConfig
+from snaps.config.image import ImageConfig
+from snaps.config.network import NetworkConfig, SubnetConfig
+from snaps.config.router import RouterConfig
from snaps.openstack.os_credentials import OSCreds, ProxySettings
__author__ = 'spisarski'
@@ -81,7 +81,7 @@ def get_credentials(os_env_file=None, proxy_settings_str=None,
elif config.get('OS_INSECURE'):
https_cacert = False
- interface = 'admin'
+ interface = 'public'
if config.get('OS_INTERFACE'):
interface = config.get('OS_INTERFACE')
@@ -139,6 +139,10 @@ def get_credentials(os_env_file=None, proxy_settings_str=None,
if overrides and isinstance(overrides, dict):
creds_dict.update(overrides)
+ for key, value in creds_dict.items():
+ if value is not None and isinstance(value, str):
+ creds_dict[key] = value.replace('"', '').replace('\'', '')
+
os_creds = OSCreds(**creds_dict)
logger.info('OS Credentials = %s', os_creds.__str__)
return os_creds
@@ -171,32 +175,29 @@ def create_image_settings(image_name, image_user, image_format, metadata,
logger.debug('Image metadata - ' + str(metadata))
if metadata and 'config' in metadata:
- return ImageSettings(**metadata['config'])
+ return ImageConfig(**metadata['config'])
disk_file = None
- if metadata:
+ if metadata and ('disk_url' in metadata or 'disk_file' in metadata):
disk_url = metadata.get('disk_url')
disk_file = metadata.get('disk_file')
elif not disk_url:
disk_url = default_url
- else:
- disk_url = disk_url
- if metadata and \
- ('kernel_file' in metadata or 'kernel_url' in metadata) and \
- kernel_settings is None:
- kernel_image_settings = ImageSettings(
+ if (metadata
+ and ('kernel_file' in metadata or 'kernel_url' in metadata)
+ and kernel_settings is None):
+ kernel_image_settings = ImageConfig(
name=image_name + '-kernel', image_user=image_user,
- img_format=image_format,
- image_file=metadata.get('kernel_file'),
+ img_format=image_format, image_file=metadata.get('kernel_file'),
url=metadata.get('kernel_url'), public=public)
else:
kernel_image_settings = kernel_settings
- if metadata and \
- ('ramdisk_file' in metadata or 'ramdisk_url' in metadata) and \
- ramdisk_settings is None:
- ramdisk_image_settings = ImageSettings(
+ if (metadata
+ and ('ramdisk_file' in metadata or 'ramdisk_url' in metadata)
+ and ramdisk_settings is None):
+ ramdisk_image_settings = ImageConfig(
name=image_name + '-ramdisk', image_user=image_user,
img_format=image_format,
image_file=metadata.get('ramdisk_file'),
@@ -208,13 +209,13 @@ def create_image_settings(image_name, image_user, image_format, metadata,
if metadata and 'extra_properties' in metadata:
extra_properties = metadata['extra_properties']
- return ImageSettings(name=image_name, image_user=image_user,
- img_format=image_format, image_file=disk_file,
- url=disk_url, extra_properties=extra_properties,
- kernel_image_settings=kernel_image_settings,
- ramdisk_image_settings=ramdisk_image_settings,
- public=public,
- nic_config_pb_loc=nic_config_pb_loc)
+ return ImageConfig(name=image_name, image_user=image_user,
+ img_format=image_format, image_file=disk_file,
+ url=disk_url, extra_properties=extra_properties,
+ kernel_image_settings=kernel_image_settings,
+ ramdisk_image_settings=ramdisk_image_settings,
+ public=public,
+ nic_config_pb_loc=nic_config_pb_loc)
def cirros_image_settings(name=None, url=None, image_metadata=None,
@@ -248,8 +249,8 @@ def cirros_image_settings(name=None, url=None, image_metadata=None,
def file_image_test_settings(name, file_path, image_user=CIRROS_USER):
- return ImageSettings(name=name, image_user=image_user,
- img_format=DEFAULT_IMAGE_FORMAT, image_file=file_path)
+ return ImageConfig(name=name, image_user=image_user,
+ img_format=DEFAULT_IMAGE_FORMAT, image_file=file_path)
def centos_image_settings(name, url=None, image_metadata=None,
@@ -274,15 +275,12 @@ def centos_image_settings(name, url=None, image_metadata=None,
else:
metadata = image_metadata
- pb_path = pkg_resources.resource_filename(
- 'snaps.provisioning.ansible_pb.centos-network-setup.playbooks',
- 'configure_host.yml')
return create_image_settings(
image_name=name, image_user=CENTOS_USER,
image_format=DEFAULT_IMAGE_FORMAT, metadata=metadata, disk_url=url,
default_url=CENTOS_DEFAULT_IMAGE_URL,
kernel_settings=kernel_settings, ramdisk_settings=ramdisk_settings,
- public=public, nic_config_pb_loc=pb_path)
+ public=public)
def ubuntu_image_settings(name, url=None, image_metadata=None,
@@ -307,49 +305,126 @@ def ubuntu_image_settings(name, url=None, image_metadata=None,
else:
metadata = image_metadata
- pb_path = pkg_resources.resource_filename(
- 'snaps.provisioning.ansible_pb.ubuntu-network-setup.playbooks',
- 'configure_host.yml')
return create_image_settings(
image_name=name, image_user=UBUNTU_USER,
image_format=DEFAULT_IMAGE_FORMAT, metadata=metadata, disk_url=url,
default_url=UBUNTU_DEFAULT_IMAGE_URL,
kernel_settings=kernel_settings, ramdisk_settings=ramdisk_settings,
- public=public, nic_config_pb_loc=pb_path)
-
+ public=public)
-def get_priv_net_config(net_name, subnet_name, router_name=None,
- cidr='10.55.0.0/24', external_net=None):
- return OSNetworkConfig(net_name, subnet_name, cidr, router_name,
- external_gateway=external_net)
+def get_priv_net_config(project_name, net_name, mtu=None, subnet_name=None,
+ router_name=None, cidr='10.55.0.0/24',
+ external_net=None, netconf_override=None):
+ return OSNetworkConfig(
+ project_name, net_name, mtu, subnet_name, cidr, router_name,
+ external_gateway=external_net, netconf_override=netconf_override)
+
+
+def get_pub_net_config(
+ project_name, net_name, mtu=None, subnet_name=None, router_name=None,
+ cidr='10.55.1.0/24', external_net=None, netconf_override=None):
+ return OSNetworkConfig(project_name, net_name, mtu, subnet_name, cidr,
+ router_name, external_gateway=external_net,
+ netconf_override=netconf_override)
+
+
+def get_flavor_config(name, ram, disk, vcpus, ephemeral=None, swap=None,
+ rxtx_factor=None, is_public=None, metadata=None):
+ """This method replaces the hard coded basic element (e.g. ram, vcpu, disk
+ etc) with those are included in the new freeform dict() of metadata
+ parameter.
+
+ :param name: the flavor name (required)
+ :param ram: memory in MB to allocate to VM (required)
+ :param disk: disk storage in GB (required)
+ :param vcpus: the number of CPUs to allocate to VM (required)
+ :param ephemeral: the size of the ephemeral disk in GB (default=0)
+ :param swap: the size of the swap disk in GB (default=0)
+ :param rxtx_factor: the receive/transmit factor to be set on ports
+ if backend supports QoS extension (default=1.0)
+ :param is_public: flag that denotes whether or not other projects
+ can access image (default=True)
+ :param metadata: - freeform dict() for special metadata (optional)
+ - freeform dict() for values of basic elements
+ (e.g. ram, vcpu, disk, etc) could be added.
+ As result the hard coded values of those elements will be
+ overwritten by the new ones (optional)
+ :return: The FlavorConfig replacing the hard coded basic element values
+ (e.g. ram, vcpu, disk etc) with those are included in the metadata
+ dict [optional]. The metadata parameter in the FlavorConfig
+ consist of the metadata data only.
+ """
-def get_pub_net_config(net_name, subnet_name=None, router_name=None,
- cidr='10.55.1.0/24', external_net=None):
- return OSNetworkConfig(net_name, subnet_name, cidr, router_name,
- external_gateway=external_net)
+ metadata_excl = metadata
+ if metadata:
+ if 'ram' in metadata:
+ ram = metadata['ram']
+ del metadata_excl['ram']
+ if 'disk' in metadata:
+ disk = metadata['disk']
+ del metadata_excl['disk']
+ if 'vcpus' in metadata:
+ vcpus = metadata['vcpus']
+ del metadata_excl['vcpus']
+ if 'ephemeral' in metadata:
+ ephemeral = metadata['ephemeral']
+ del metadata_excl['ephemeral']
+ if 'swap' in metadata:
+ swap = metadata['swap']
+ del metadata_excl['swap']
+ if 'rxtx_factor' in metadata:
+ rxtx_factor = metadata['rxtx_factor']
+ del metadata_excl['rxtx_factor']
+ if 'is_public' in metadata:
+ is_public = metadata['is_public']
+ del metadata_excl['is_public']
+ if 'metadata' in metadata:
+ metadata_excl = metadata['metadata']
+
+ return FlavorConfig(
+ name=name, ram=ram, disk=disk, vcpus=vcpus, ephemeral=ephemeral,
+ swap=swap, rxtx_factor=rxtx_factor, is_public=is_public,
+ metadata=metadata_excl)
class OSNetworkConfig:
"""
Represents the settings required for the creation of a network in OpenStack
+ where netconf_override is used to reconfigure the network_type,
+ physical_network and segmentation_id
"""
- def __init__(self, net_name, subnet_name=None, subnet_cidr=None,
- router_name=None, external_gateway=None):
-
+ def __init__(self, project_name, net_name, mtu=None, subnet_name=None,
+ subnet_cidr=None, router_name=None, external_gateway=None,
+ netconf_override=None):
+ """
+ :param netconf_override: dict() containing the reconfigured
+ network_type, physical_network and
+ segmentation_id
+ """
if subnet_name and subnet_cidr:
- self.network_settings = NetworkSettings(
- name=net_name, subnet_settings=[
- SubnetSettings(cidr=subnet_cidr, name=subnet_name)])
+ network_conf = NetworkConfig(
+ name=net_name, mtu=mtu, subnet_settings=[
+ SubnetConfig(cidr=subnet_cidr, name=subnet_name)])
else:
- self.network_settings = NetworkSettings(name=net_name)
+ network_conf = NetworkConfig(name=net_name)
+ if netconf_override:
+ network_conf.network_type = netconf_override.get('network_type')
+ network_conf.physical_network = netconf_override.get(
+ 'physical_network')
+ network_conf.segmentation_id = netconf_override.get(
+ 'segmentation_id')
+ self.network_settings = network_conf
if router_name:
if subnet_name:
- self.router_settings = RouterSettings(
+ self.router_settings = RouterConfig(
name=router_name, external_gateway=external_gateway,
- internal_subnets=[subnet_name])
+ internal_subnets=[{'subnet': {
+ 'project_name': project_name,
+ 'network_name': net_name,
+ 'subnet_name': subnet_name}}])
else:
- self.router_settings = RouterSettings(
+ self.router_settings = RouterConfig(
name=router_name, external_gateway=external_gateway)
diff --git a/snaps/openstack/tests/os_source_file_test.py b/snaps/openstack/tests/os_source_file_test.py
index 1617f91..8b0a7b4 100644
--- a/snaps/openstack/tests/os_source_file_test.py
+++ b/snaps/openstack/tests/os_source_file_test.py
@@ -14,14 +14,12 @@
# limitations under the License.
import logging
import pkg_resources
-import requests
-from requests.packages.urllib3.exceptions import InsecureRequestWarning
import uuid
import unittest
from snaps import file_utils
-from snaps.openstack.create_project import ProjectSettings
-from snaps.openstack.create_user import UserSettings
+from snaps.config.project import ProjectConfig
+from snaps.config.user import UserConfig
from snaps.openstack.tests import openstack_tests
from snaps.openstack.utils import deploy_utils, keystone_utils
@@ -29,13 +27,12 @@ from snaps.openstack.utils import deploy_utils, keystone_utils
dev_os_env_file = pkg_resources.resource_filename(
'snaps.openstack.tests.conf', 'os_env.yaml')
-requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
-
class OSComponentTestCase(unittest.TestCase):
def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None,
- image_metadata=None, log_level=logging.DEBUG):
+ flavor_metadata=None, image_metadata=None,
+ log_level=logging.DEBUG):
"""
Super for test classes requiring a connection to OpenStack
:param method_name: default 'runTest'
@@ -43,6 +40,8 @@ class OSComponentTestCase(unittest.TestCase):
in the package snaps.openstack.tests.conf.os_env.yaml
:param ext_net_name: the name of the external network that is used for
creating routers for floating IPs
+ :param flavor_metadata: dict() to be sent directly into the Nova client
+ generally used for page sizes
:param image_metadata: ability to override images being used in the
tests (see examples/image-metadata)
:param log_level: the logging level of your test run (default DEBUG)
@@ -51,23 +50,36 @@ class OSComponentTestCase(unittest.TestCase):
logging.basicConfig(level=log_level)
+ self.ext_net_name = None
+ self.flavor_metadata = None
+
if os_creds:
self.os_creds = os_creds
else:
- self.os_creds = openstack_tests.get_credentials(
- dev_os_env_file=dev_os_env_file)
-
- self.ext_net_name = ext_net_name
-
- if not self.ext_net_name and file_utils.file_exists(dev_os_env_file):
- test_conf = file_utils.read_yaml(dev_os_env_file)
- self.ext_net_name = test_conf.get('ext_net')
+ if file_utils.file_exists(dev_os_env_file):
+ self.os_creds = openstack_tests.get_credentials(
+ dev_os_env_file=dev_os_env_file)
+ test_conf = file_utils.read_yaml(dev_os_env_file)
+ self.ext_net_name = test_conf.get('ext_net')
+ os_env_dict = file_utils.read_yaml(dev_os_env_file)
+ flavor_metadata = os_env_dict.get('flavor_metadata')
+ if flavor_metadata:
+ self.flavor_metadata = {'metadata': flavor_metadata}
+ else:
+ raise Exception('Unable to obtain OSCreds')
+
+ self.os_session = keystone_utils.keystone_session(self.os_creds)
self.image_metadata = image_metadata
+ if not self.ext_net_name:
+ self.ext_net_name = ext_net_name
+ if not self.flavor_metadata:
+ self.flavor_metadata = flavor_metadata
@staticmethod
def parameterize(testcase_klass, os_creds, ext_net_name,
- image_metadata=None, log_level=logging.DEBUG):
+ flavor_metadata=None, image_metadata=None,
+ log_level=logging.DEBUG):
""" Create a suite containing all tests taken from the given
subclass, passing them the parameter 'param'.
"""
@@ -75,16 +87,24 @@ class OSComponentTestCase(unittest.TestCase):
test_names = test_loader.getTestCaseNames(testcase_klass)
suite = unittest.TestSuite()
for name in test_names:
- suite.addTest(testcase_klass(name, os_creds, ext_net_name,
- image_metadata, log_level))
+ suite.addTest(testcase_klass(
+ name, os_creds, ext_net_name, flavor_metadata, image_metadata,
+ log_level))
return suite
+ def __clean__(self):
+ """
+ Cleans up keystone session.
+ """
+ if self.os_session:
+ keystone_utils.close_session(self.os_session)
+
class OSIntegrationTestCase(OSComponentTestCase):
def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None,
use_keystone=True, flavor_metadata=None, image_metadata=None,
- log_level=logging.DEBUG):
+ netconf_override=None, log_level=logging.DEBUG):
"""
Super for integration tests requiring a connection to OpenStack
:param method_name: default 'runTest'
@@ -102,20 +122,25 @@ class OSIntegrationTestCase(OSComponentTestCase):
'ramdisk_url': '{URI}/cirros-0.3.4-x86_64-initramfs'})
:param flavor_metadata: dict() to be sent directly into the Nova client
generally used for page sizes
+ :param netconf_override: dict() containing the configured network_type,
+ physical_network and segmentation_id
:param log_level: the logging level of your test run (default DEBUG)
"""
super(OSIntegrationTestCase, self).__init__(
method_name=method_name, os_creds=os_creds,
- ext_net_name=ext_net_name, image_metadata=image_metadata,
- log_level=log_level)
+ ext_net_name=ext_net_name, flavor_metadata=flavor_metadata,
+ image_metadata=image_metadata, log_level=log_level)
+ self.netconf_override = netconf_override
self.use_keystone = use_keystone
self.keystone = None
- self.flavor_metadata = flavor_metadata
+ self.user_roles = None
+ self.proj_users = None
@staticmethod
def parameterize(testcase_klass, os_creds, ext_net_name,
- use_keystone=False, flavor_metadata=None,
- image_metadata=None, log_level=logging.DEBUG):
+ use_keystone=True, flavor_metadata=None,
+ image_metadata=None, netconf_override=None,
+ log_level=logging.DEBUG):
"""
Create a suite containing all tests taken from the given
subclass, passing them the parameter 'param'.
@@ -126,7 +151,8 @@ class OSIntegrationTestCase(OSComponentTestCase):
for name in test_names:
suite.addTest(testcase_klass(name, os_creds, ext_net_name,
use_keystone, flavor_metadata,
- image_metadata, log_level))
+ image_metadata, netconf_override,
+ log_level))
return suite
"""
@@ -142,30 +168,43 @@ class OSIntegrationTestCase(OSComponentTestCase):
self.project_creator = None
self.user_creator = None
self.admin_os_creds = self.os_creds
- self.role = None
+ self.admin_os_session = self.os_session
+ self.keystone = keystone_utils.keystone_client(
+ self.admin_os_creds, self.admin_os_session)
if self.use_keystone:
- self.keystone = keystone_utils.keystone_client(self.admin_os_creds)
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())[:-19]
project_name = guid + '-proj'
self.project_creator = deploy_utils.create_project(
- self.admin_os_creds, ProjectSettings(
+ self.admin_os_creds, ProjectConfig(
name=project_name,
domain=self.admin_os_creds.project_domain_name))
+ # Set by implementing class for setting the user's roles
+ roles = dict()
+ if self.user_roles and isinstance(self.user_roles, list):
+ for user_role in self.user_roles:
+ roles[user_role] = project_name
+
self.user_creator = deploy_utils.create_user(
- self.admin_os_creds, UserSettings(
+ self.admin_os_creds, UserConfig(
name=guid + '-user', password=guid,
- project_name=project_name, roles={
- 'admin': self.project_creator.project_settings.name},
+ project_name=project_name, roles=roles,
domain_name=self.admin_os_creds.user_domain_name))
self.os_creds = self.user_creator.get_os_creds(
self.project_creator.project_settings.name)
+ self.os_session = keystone_utils.keystone_session(self.os_creds)
# add user to project
self.project_creator.assoc_user(self.user_creator.get_user())
+ if self.proj_users and isinstance(self.proj_users, list):
+ for user_name in self.proj_users:
+ user = keystone_utils.get_user(self.keystone, user_name)
+ if user:
+ self.project_creator.assoc_user(user)
+
def __clean__(self):
"""
Cleans up test user and project.
@@ -173,11 +212,13 @@ class OSIntegrationTestCase(OSComponentTestCase):
called during setUp() else these objects will persist after the test is
run
"""
- if self.role:
- keystone_utils.delete_role(self.keystone, self.role)
-
if self.project_creator:
self.project_creator.clean()
if self.user_creator:
self.user_creator.clean()
+
+ if self.admin_os_session:
+ keystone_utils.close_session(self.admin_os_session)
+
+ super(OSIntegrationTestCase, self).__clean__()
diff --git a/snaps/openstack/utils/__init__.py b/snaps/openstack/utils/__init__.py
index 7f92908..5435f8f 100644
--- a/snaps/openstack/utils/__init__.py
+++ b/snaps/openstack/utils/__init__.py
@@ -12,4 +12,5 @@
# 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.
-__author__ = 'spisarski' \ No newline at end of file
+
+__author__ = 'spisarski'
diff --git a/snaps/openstack/utils/cinder_utils.py b/snaps/openstack/utils/cinder_utils.py
index d13277d..61abe22 100644
--- a/snaps/openstack/utils/cinder_utils.py
+++ b/snaps/openstack/utils/cinder_utils.py
@@ -17,7 +17,8 @@ import logging
from cinderclient.client import Client
from cinderclient.exceptions import NotFound
-from snaps.domain.volume import QoSSpec, VolumeType, VolumeTypeEncryption
+from snaps.domain.volume import (
+ QoSSpec, VolumeType, VolumeTypeEncryption, Volume)
from snaps.openstack.utils import keystone_utils
__author__ = 'spisarski'
@@ -32,16 +33,152 @@ Utilities for basic neutron API calls
"""
-def cinder_client(os_creds):
+def cinder_client(os_creds, session=None):
"""
Creates and returns a cinder client object
+ :param os_creds: the credentials for connecting to the OpenStack remote API
+ :param session: the keystone session object (optional)
:return: the cinder client
"""
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
+
return Client(version=os_creds.volume_api_version,
- session=keystone_utils.keystone_session(os_creds),
+ session=session,
region_name=os_creds.region_name)
+def get_volume(cinder, keystone=None, volume_name=None, volume_settings=None,
+ project_name=None):
+ """
+ Returns an OpenStack volume object for a given name
+ :param cinder: the Cinder client
+ :param keystone: the Keystone client (required if project_name or
+ volume_settings.project_name is not None
+ :param volume_name: the volume name to lookup
+ :param volume_settings: the volume settings used for lookups
+ :param project_name: the name of the project associated with the volume
+ :return: the volume object or None
+ """
+ if volume_settings:
+ volume_name = volume_settings.name
+
+ volumes = cinder.volumes.list()
+ for os_volume in volumes:
+ if os_volume.name == volume_name:
+ project_id = None
+ if hasattr(os_volume, 'os-vol-tenant-attr:tenant_id'):
+ project_id = getattr(
+ os_volume, 'os-vol-tenant-attr:tenant_id')
+
+ if volume_settings and volume_settings.project_name:
+ project_name = volume_settings.project_name
+
+ if project_name:
+ project = keystone_utils.get_project_by_id(
+ keystone, project_id)
+
+ if project and project.name == project_name:
+ return __map_os_volume_to_domain(os_volume)
+ else:
+ return __map_os_volume_to_domain(os_volume)
+
+
+def __get_os_volume_by_id(cinder, volume_id):
+ """
+ Returns an OpenStack volume object for a given name
+ :param cinder: the Cinder client
+ :param volume_id: the volume ID to lookup
+ :return: the SNAPS-OO Domain Volume object or None
+ """
+ return cinder.volumes.get(volume_id)
+
+
+def get_volume_by_id(cinder, volume_id):
+ """
+ Returns an OpenStack volume object for a given name
+ :param cinder: the Cinder client
+ :param volume_id: the volume ID to lookup
+ :return: the SNAPS-OO Domain Volume object or None
+ """
+ os_volume = __get_os_volume_by_id(cinder, volume_id)
+ return __map_os_volume_to_domain(os_volume)
+
+
+def __map_os_volume_to_domain(os_volume):
+ """
+ Returns a SNAPS-OO domain Volume object that is created by an OpenStack
+ Volume object
+ :param os_volume: the OpenStack volume object
+ :return: Volume domain object
+ """
+ project_id = None
+ if hasattr(os_volume, 'os-vol-tenant-attr:tenant_id'):
+ project_id = getattr(
+ os_volume, 'os-vol-tenant-attr:tenant_id')
+
+ return Volume(
+ name=os_volume.name, volume_id=os_volume.id,
+ project_id=project_id, description=os_volume.description,
+ size=os_volume.size, vol_type=os_volume.volume_type,
+ availability_zone=os_volume.availability_zone,
+ multi_attach=os_volume.multiattach,
+ attachments=os_volume.attachments)
+
+
+def get_volume_status(cinder, volume):
+ """
+ Returns a new OpenStack Volume object for a given OpenStack volume object
+ :param cinder: the Cinder client
+ :param volume: the domain Volume object
+ :return: the OpenStack Volume object
+ """
+ os_volume = cinder.volumes.get(volume.id)
+ return os_volume.status
+
+
+def create_volume(cinder, keystone, volume_settings):
+ """
+ Creates and returns OpenStack volume object with an external URL
+ :param cinder: the cinder client
+ :param keystone: the keystone client
+ :param volume_settings: the volume settings object
+ :return: the OpenStack volume object
+ :raise Exception if using a file and it cannot be found
+ """
+ project_id = None
+ if volume_settings.project_name:
+ project = keystone_utils.get_project(
+ keystone, project_name=volume_settings.project_name)
+ if project:
+ project_id = project.id
+ else:
+ raise KeystoneUtilsException(
+ 'Project cannot be found with name - '
+ + volume_settings.project_name)
+ os_volume = cinder.volumes.create(
+ name=volume_settings.name,
+ project_id=project_id,
+ description=volume_settings.description,
+ size=volume_settings.size,
+ imageRef=volume_settings.image_name,
+ volume_type=volume_settings.type_name,
+ availability_zone=volume_settings.availability_zone,
+ multiattach=volume_settings.multi_attach)
+
+ return __map_os_volume_to_domain(os_volume)
+
+
+def delete_volume(cinder, volume):
+ """
+ Deletes an volume from OpenStack
+ :param cinder: the cinder client
+ :param volume: the volume to delete
+ """
+ logger.info('Deleting volume named - %s', volume.name)
+ return cinder.volumes.delete(volume.id)
+
+
def get_volume_type(cinder, volume_type_name=None, volume_type_settings=None):
"""
Returns an OpenStack volume type object for a given name
@@ -274,3 +411,9 @@ def delete_qos(cinder, qos):
"""
logger.info('Deleting QoS named - %s', qos.name)
cinder.qos_specs.delete(qos.id)
+
+
+class KeystoneUtilsException(Exception):
+ """
+ Exception when calls to the Keystone client cannot be served properly
+ """
diff --git a/snaps/openstack/utils/deploy_utils.py b/snaps/openstack/utils/deploy_utils.py
index c936c1f..8cd6dd3 100644
--- a/snaps/openstack/utils/deploy_utils.py
+++ b/snaps/openstack/utils/deploy_utils.py
@@ -78,7 +78,7 @@ def create_router(os_creds, router_settings, cleanup=False):
"""
Creates a network on which the CMTSs can attach
:param os_creds: The OpenStack credentials object
- :param router_settings: The RouterSettings instance
+ :param router_settings: The RouterConfig instance
:param cleanup: Denotes whether or not this is being called for cleanup
:return: A reference to the network creator objects for each network from
which network elements such as the subnet, router, interface
@@ -103,7 +103,7 @@ def create_keypair(os_creds, keypair_settings, cleanup=False):
"""
Creates a keypair that can be applied to an instance
:param os_creds: The OpenStack credentials object
- :param keypair_settings: The KeypairSettings object
+ :param keypair_settings: The KeypairConfig object
:param cleanup: Denotes whether or not this is being called for cleanup
:return: A reference to the keypair creator object
"""
@@ -121,7 +121,7 @@ def create_vm_instance(os_creds, instance_settings, image_settings,
"""
Creates a VM instance
:param os_creds: The OpenStack credentials
- :param instance_settings: Instance of VmInstanceSettings
+ :param instance_settings: Instance of VmInstanceConfig
:param image_settings: The object containing image settings
:param keypair_creator: The object responsible for creating the keypair
associated with this VM instance. (optional)
diff --git a/snaps/openstack/utils/glance_utils.py b/snaps/openstack/utils/glance_utils.py
index a127ad3..db20f2f 100644
--- a/snaps/openstack/utils/glance_utils.py
+++ b/snaps/openstack/utils/glance_utils.py
@@ -34,13 +34,18 @@ Utilities for basic neutron API calls
"""
-def glance_client(os_creds):
+def glance_client(os_creds, session=None):
"""
Creates and returns a glance client object
+ :param os_creds: the credentials for connecting to the OpenStack remote API
+ :param session: the keystone session object (optional)
:return: the glance client
"""
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
+
return Client(version=os_creds.image_api_version,
- session=keystone_utils.keystone_session(os_creds),
+ session=session,
region_name=os_creds.region_name)
diff --git a/snaps/openstack/utils/heat_utils.py b/snaps/openstack/utils/heat_utils.py
index 8b9395b..17de020 100644
--- a/snaps/openstack/utils/heat_utils.py
+++ b/snaps/openstack/utils/heat_utils.py
@@ -13,32 +13,37 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
+import os
-import yaml
from heatclient.client import Client
from heatclient.common.template_format import yaml_loader
from novaclient.exceptions import NotFound
from oslo_serialization import jsonutils
+import yaml
from snaps import file_utils
from snaps.domain.stack import Stack, Resource, Output
+from snaps.openstack.utils import (
+ keystone_utils, neutron_utils, nova_utils, cinder_utils)
+from snaps.thread_utils import worker_pool
-from snaps.openstack.utils import keystone_utils, neutron_utils, nova_utils
__author__ = 'spisarski'
logger = logging.getLogger('heat_utils')
-def heat_client(os_creds):
+def heat_client(os_creds, session=None):
"""
Retrieves the Heat client
:param os_creds: the OpenStack credentials
:return: the client
"""
- logger.debug('Retrieving Nova Client')
+ logger.debug('Retrieving Heat Client')
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
return Client(os_creds.heat_api_version,
- session=keystone_utils.keystone_session(os_creds),
+ session=session,
region_name=os_creds.region_name)
@@ -63,7 +68,11 @@ def get_stack(heat_cli, stack_settings=None, stack_name=None):
stacks = heat_cli.stacks.list(**stack_filter)
for stack in stacks:
- return Stack(name=stack.identifier, stack_id=stack.id)
+ return Stack(
+ name=stack.stack_name, stack_id=stack.id,
+ stack_project_id=stack.stack_user_project_id,
+ status=stack.stack_status,
+ status_reason=stack.stack_status_reason)
def get_stack_by_id(heat_cli, stack_id):
@@ -74,7 +83,11 @@ def get_stack_by_id(heat_cli, stack_id):
:return: the Stack domain object else None
"""
stack = heat_cli.stacks.get(stack_id)
- return Stack(name=stack.identifier, stack_id=stack.id)
+ return Stack(
+ name=stack.stack_name, stack_id=stack.id,
+ stack_project_id=stack.stack_user_project_id,
+ status=stack.stack_status,
+ status_reason=stack.stack_status_reason)
def get_stack_status(heat_cli, stack_id):
@@ -116,11 +129,39 @@ def create_stack(heat_cli, stack_settings):
if stack_settings.env_values:
args['parameters'] = stack_settings.env_values
+ if stack_settings.resource_files:
+ resources = dict()
+ for res_file in stack_settings.resource_files:
+ heat_resource_contents = file_utils.read_file(res_file)
+ base_filename = os.path.basename(res_file)
+
+ if heat_resource_contents and base_filename:
+ resources[base_filename] = heat_resource_contents
+ args['files'] = resources
+
stack = heat_cli.stacks.create(**args)
return get_stack_by_id(heat_cli, stack_id=stack['stack']['id'])
+def update_stack(heat_cli, stack, env_vals):
+ """
+ Updates the specified parameters in the stack
+ :param heat_cli: the OpenStack heat client object
+ :param stack_settings: the stack configuration
+ """
+ args = dict()
+
+ args['stack_name'] = stack.name
+ args['existing'] = True
+
+ if env_vals:
+ args['parameters'] = env_vals
+ heat_cli.stacks.update(stack.id, **args)
+ else:
+ logger.warn('Stack not updated, env_vals are None')
+
+
def delete_stack(heat_cli, stack):
"""
Deletes the Heat stack
@@ -130,30 +171,37 @@ def delete_stack(heat_cli, stack):
heat_cli.stacks.delete(stack.id)
-def __get_os_resources(heat_cli, stack):
+def __get_os_resources(heat_cli, res_id):
"""
Returns all of the OpenStack resource objects for a given stack
:param heat_cli: the OpenStack heat client
- :param stack: the SNAPS-OO Stack domain object
+ :param res_id: the resource ID
:return: a list
"""
- return heat_cli.resources.list(stack.id)
+ return heat_cli.resources.list(res_id)
-def get_resources(heat_cli, stack):
+def get_resources(heat_cli, res_id, res_type=None):
"""
Returns all of the OpenStack resource objects for a given stack
:param heat_cli: the OpenStack heat client
- :param stack: the SNAPS-OO Stack domain object
- :return: a list
+ :param res_id: the SNAPS-OO Stack domain object
+ :param res_type: the type name to filter
+ :return: a list of Resource domain objects
"""
- os_resources = __get_os_resources(heat_cli, stack)
+ os_resources = __get_os_resources(heat_cli, res_id)
if os_resources:
out = list()
for os_resource in os_resources:
- out.append(Resource(resource_type=os_resource.resource_type,
- resource_id=os_resource.physical_resource_id))
+ if ((res_type and os_resource.resource_type == res_type)
+ or not res_type):
+ out.append(Resource(
+ name=os_resource.resource_name,
+ resource_type=os_resource.resource_type,
+ resource_id=os_resource.physical_resource_id,
+ status=os_resource.resource_status,
+ status_reason=os_resource.resource_status_reason))
return out
@@ -163,7 +211,7 @@ def get_outputs(heat_cli, stack):
for given stack
:param heat_cli: the OpenStack heat client
:param stack: the SNAPS-OO Stack domain object
- :return: a list
+ :return: a list of Output domain objects
"""
out = list()
@@ -182,46 +230,244 @@ def get_outputs(heat_cli, stack):
def get_stack_networks(heat_cli, neutron, stack):
"""
- Returns an instance of NetworkSettings for each network owned by this stack
+ Returns a list of Network domain objects deployed by this stack
+ :param heat_cli: the OpenStack heat client object
+ :param neutron: the OpenStack neutron client object
+ :param stack: the SNAPS-OO Stack domain object
+ :return: a list of Network objects
+ """
+
+ out = list()
+ resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Net')
+ workers = []
+ for resource in resources:
+ worker = worker_pool().apply_async(neutron_utils.get_network_by_id,
+ (neutron, resource.id))
+ workers.append(worker)
+
+ for worker in workers:
+ network = worker.get()
+ if network:
+ out.append(network)
+
+ return out
+
+
+def get_stack_routers(heat_cli, neutron, stack):
+ """
+ Returns a list of Network domain objects deployed by this stack
:param heat_cli: the OpenStack heat client object
:param neutron: the OpenStack neutron client object
:param stack: the SNAPS-OO Stack domain object
- :return: a list of NetworkSettings
+ :return: a list of Network objects
"""
out = list()
- resources = get_resources(heat_cli, stack)
+ resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Router')
+ workers = []
for resource in resources:
- if resource.type == 'OS::Neutron::Net':
- network = neutron_utils.get_network_by_id(
- neutron, resource.id)
- if network:
- out.append(network)
+ worker = worker_pool().apply_async(neutron_utils.get_router_by_id,
+ (neutron, resource.id))
+ workers.append(worker)
+
+ for worker in workers:
+ router = worker.get()
+ if router:
+ out.append(router)
return out
-def get_stack_servers(heat_cli, nova, stack):
+def get_stack_security_groups(heat_cli, neutron, stack):
"""
- Returns an instance of NetworkSettings for each network owned by this stack
+ Returns a list of SecurityGroup domain objects deployed by this stack
+ :param heat_cli: the OpenStack heat client object
+ :param neutron: the OpenStack neutron client object
+ :param stack: the SNAPS-OO Stack domain object
+ :return: a list of SecurityGroup objects
+ """
+
+ out = list()
+ resources = get_resources(heat_cli, stack.id, 'OS::Neutron::SecurityGroup')
+ workers = []
+ for resource in resources:
+ worker = worker_pool().apply_async(
+ neutron_utils.get_security_group_by_id,
+ (neutron, resource.id))
+ workers.append(worker)
+
+ for worker in workers:
+ security_group = worker.get()
+ if security_group:
+ out.append(security_group)
+
+ return out
+
+
+def get_stack_servers(heat_cli, nova, neutron, keystone, stack, project_name):
+ """
+ Returns a list of VMInst domain objects associated with a Stack
:param heat_cli: the OpenStack heat client object
:param nova: the OpenStack nova client object
+ :param neutron: the OpenStack neutron client object
+ :param keystone: the OpenStack keystone client object
:param stack: the SNAPS-OO Stack domain object
- :return: a list of NetworkSettings
+ :param project_name: the associated project ID
+ :return: a list of VMInst domain objects
"""
out = list()
- resources = get_resources(heat_cli, stack)
+ srvr_res = get_resources(heat_cli, stack.id, 'OS::Nova::Server')
+ workers = []
+ for resource in srvr_res:
+ worker = worker_pool().apply_async(
+ nova_utils.get_server_object_by_id,
+ (nova, neutron, keystone, resource.id, project_name))
+ workers.append((resource.id, worker))
+
+ for worker in workers:
+ resource_id = worker[0]
+ try:
+ server = worker[1].get()
+ if server:
+ out.append(server)
+ except NotFound:
+ logger.warn('VmInst cannot be located with ID %s', resource_id)
+
+ res_grps = get_resources(heat_cli, stack.id, 'OS::Heat::ResourceGroup')
+ for res_grp in res_grps:
+ res_ress = get_resources(heat_cli, res_grp.id)
+ workers = []
+ for res_res in res_ress:
+ res_res_srvrs = get_resources(
+ heat_cli, res_res.id, 'OS::Nova::Server')
+ for res_srvr in res_res_srvrs:
+ worker = worker_pool().apply_async(
+ nova_utils.get_server_object_by_id,
+ (nova, neutron, keystone, res_srvr.id, project_name))
+ workers.append(worker)
+
+ for worker in workers:
+ server = worker.get()
+ if server:
+ out.append(server)
+
+ return out
+
+
+def get_stack_keypairs(heat_cli, nova, stack):
+ """
+ Returns a list of Keypair domain objects associated with a Stack
+ :param heat_cli: the OpenStack heat client object
+ :param nova: the OpenStack nova client object
+ :param stack: the SNAPS-OO Stack domain object
+ :return: a list of VMInst domain objects
+ """
+
+ out = list()
+ resources = get_resources(heat_cli, stack.id, 'OS::Nova::KeyPair')
+ workers = []
for resource in resources:
- if resource.type == 'OS::Nova::Server':
- try:
- server = nova_utils.get_server_object_by_id(
- nova, resource.id)
- if server:
- out.append(server)
- except NotFound:
- logger.warn(
- 'VmInst cannot be located with ID %s', resource.id)
+ worker = worker_pool().apply_async(
+ nova_utils.get_keypair_by_id, (nova, resource.id))
+ workers.append((resource.id, worker))
+
+ for worker in workers:
+ resource_id = worker[0]
+ try:
+ keypair = worker[1].get()
+ if keypair:
+ out.append(keypair)
+ except NotFound:
+ logger.warn('Keypair cannot be located with ID %s', resource_id)
+
+ return out
+
+
+def get_stack_volumes(heat_cli, cinder, stack):
+ """
+ Returns an instance of Volume domain objects created by this stack
+ :param heat_cli: the OpenStack heat client object
+ :param cinder: the OpenStack cinder client object
+ :param stack: the SNAPS-OO Stack domain object
+ :return: a list of Volume domain objects
+ """
+
+ out = list()
+ resources = get_resources(heat_cli, stack.id, 'OS::Cinder::Volume')
+ workers = []
+ for resource in resources:
+ worker = worker_pool().apply_async(
+ cinder_utils.get_volume_by_id, (cinder, resource.id))
+ workers.append((resource.id, worker))
+
+ for worker in workers:
+ resource_id = worker[0]
+ try:
+ server = worker[1].get()
+ if server:
+ out.append(server)
+ except NotFound:
+ logger.warn('Volume cannot be located with ID %s', resource_id)
+
+ return out
+
+
+def get_stack_volume_types(heat_cli, cinder, stack):
+ """
+ Returns an instance of VolumeType domain objects created by this stack
+ :param heat_cli: the OpenStack heat client object
+ :param cinder: the OpenStack cinder client object
+ :param stack: the SNAPS-OO Stack domain object
+ :return: a list of VolumeType domain objects
+ """
+
+ out = list()
+ resources = get_resources(heat_cli, stack.id, 'OS::Cinder::VolumeType')
+ workers = []
+ for resource in resources:
+ worker = worker_pool().apply_async(
+ cinder_utils.get_volume_type_by_id, (cinder, resource.id))
+ workers.append((resource.id, worker))
+
+ for worker in workers:
+ resource_id = worker[0]
+ try:
+ vol_type = worker[1].get()
+ if vol_type:
+ out.append(vol_type)
+ except NotFound:
+ logger.warn('VolumeType cannot be located with ID %s', resource_id)
+
+ return out
+
+
+def get_stack_flavors(heat_cli, nova, stack):
+ """
+ Returns an instance of Flavor SNAPS domain object for each flavor created
+ by this stack
+ :param heat_cli: the OpenStack heat client object
+ :param nova: the OpenStack cinder client object
+ :param stack: the SNAPS-OO Stack domain object
+ :return: a list of Volume domain objects
+ """
+
+ out = list()
+ resources = get_resources(heat_cli, stack.id, 'OS::Nova::Flavor')
+ workers = []
+ for resource in resources:
+ worker = worker_pool().apply_async(
+ nova_utils.get_flavor_by_id, (nova, resource.id))
+ workers.append((resource.id, worker))
+
+ for worker in workers:
+ resource_id = worker[0]
+ try:
+ flavor = worker[1].get()
+ if flavor:
+ out.append(flavor)
+ except NotFound:
+ logger.warn('Flavor cannot be located with ID %s', resource_id)
return out
diff --git a/snaps/openstack/utils/keystone_utils.py b/snaps/openstack/utils/keystone_utils.py
index 46f6fb8..4d3c0d3 100644
--- a/snaps/openstack/utils/keystone_utils.py
+++ b/snaps/openstack/utils/keystone_utils.py
@@ -14,10 +14,12 @@
# limitations under the License.
import logging
+import keystoneauth1
from keystoneclient.client import Client
from keystoneauth1.identity import v3, v2
from keystoneauth1 import session
import requests
+from keystoneclient.exceptions import NotFound
from snaps.domain.project import Project, Domain
from snaps.domain.role import Role
@@ -26,6 +28,7 @@ from snaps.domain.user import User
logger = logging.getLogger('keystone_utils')
V2_VERSION_NUM = 2.0
+V3_VERSION_NUM = 3
V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
@@ -77,15 +80,29 @@ def keystone_session(os_creds):
verify=os_creds.cacert)
-def keystone_client(os_creds):
+def close_session(session):
+ """
+ Closes a keystone session
+ :param session: a session.Session object
+ """
+ if isinstance(session, keystoneauth1.session.Session):
+ session.session.close()
+
+
+def keystone_client(os_creds, session=None):
"""
Returns the keystone client
:param os_creds: the OpenStack credentials (OSCreds) object
+ :param session: the keystone session object (optional)
:return: the client
"""
+
+ if not session:
+ session = keystone_session(os_creds)
+
return Client(
version=os_creds.identity_api_version,
- session=keystone_session(os_creds),
+ session=session,
interface=os_creds.interface,
region_name=os_creds.region_name)
@@ -105,26 +122,16 @@ def get_endpoint(os_creds, service_type, interface='public'):
interface=interface)
-def get_project(keystone=None, os_creds=None, project_settings=None,
- project_name=None):
+def get_project(keystone=None, project_settings=None, project_name=None):
"""
Returns the first project where the project_settings is used for the query
if not None, else the project_name parameter is used for the query. If both
parameters are None, None is returned
:param keystone: the Keystone client
- :param os_creds: the OpenStack credentials used to obtain the Keystone
- client if the keystone parameter is None
- :param project_settings: a ProjectSettings object
+ :param project_settings: a ProjectConfig object
:param project_name: the name to query
:return: the SNAPS-OO Project domain object or None
"""
- if not keystone:
- if os_creds:
- keystone = keystone_client(os_creds)
- else:
- raise KeystoneException(
- 'Cannot lookup project without the proper credentials')
-
proj_filter = dict()
if project_name:
@@ -152,6 +159,26 @@ def get_project(keystone=None, os_creds=None, project_settings=None,
domain_id=domain_id)
+def get_project_by_id(keystone, proj_id):
+ """
+ Returns the first project where the project_settings is used for the query
+ if not None, else the project_name parameter is used for the query. If both
+ parameters are None, None is returned
+ :param keystone: the Keystone client
+ :param proj_id: the project ID
+ """
+ if proj_id and len(proj_id) > 0:
+ try:
+ os_proj = keystone.projects.get(proj_id)
+ if os_proj:
+ return Project(name=os_proj.name, project_id=os_proj.id,
+ domain_id=os_proj)
+ except NotFound:
+ pass
+ except KeyError:
+ pass
+
+
def create_project(keystone, project_settings):
"""
Creates a project
@@ -184,7 +211,7 @@ def create_project(keystone, project_settings):
def delete_project(keystone, project):
"""
Deletes a project
- :param keystone: the Keystone clien
+ :param keystone: the Keystone client
:param project: the SNAPS-OO Project domain object
"""
logger.info('Deleting project with name - %s', project.name)
@@ -237,8 +264,8 @@ def create_user(keystone, user_settings):
"""
project = None
if user_settings.project_name:
- project = get_project(keystone=keystone,
- project_name=user_settings.project_name)
+ project = get_project(
+ keystone=keystone, project_name=user_settings.project_name)
if keystone.version == V2_VERSION_STR:
project_id = None
@@ -367,7 +394,7 @@ def grant_user_role_to_project(keystone, role, user, project):
"""
os_role = get_role_by_id(keystone, role.id)
- logger.info('Granting role %s to project %s', role.name, project)
+ logger.info('Granting role %s to project %s', role.name, project.name)
if keystone.version == V2_VERSION_STR:
keystone.roles.add_user_role(user, os_role, tenant=project)
else:
@@ -381,9 +408,10 @@ def get_domain_by_id(keystone, domain_id):
:param domain_id: the domain ID to retrieve
:return: the SNAPS-OO Domain domain object
"""
- domain = keystone.domains.get(domain_id)
- if domain:
- return Domain(name=domain.name, domain_id=domain.id)
+ if keystone.version != V2_VERSION_STR:
+ domain = keystone.domains.get(domain_id)
+ if domain:
+ return Domain(name=domain.name, domain_id=domain.id)
def __get_os_domain_by_name(keystone, domain_name):
diff --git a/snaps/openstack/utils/launch_utils.py b/snaps/openstack/utils/launch_utils.py
new file mode 100644
index 0000000..ddaad12
--- /dev/null
+++ b/snaps/openstack/utils/launch_utils.py
@@ -0,0 +1,889 @@
+#
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+#
+# This utility makes it easy to create OpenStack objects
+import logging
+import re
+import socket
+import struct
+
+import os
+import time
+from keystoneauth1.exceptions import Unauthorized
+
+from snaps import file_utils
+from snaps.config.flavor import FlavorConfig
+from snaps.config.image import ImageConfig
+from snaps.config.keypair import KeypairConfig
+from snaps.config.network import PortConfig, NetworkConfig
+from snaps.config.project import ProjectConfig
+from snaps.config.qos import QoSConfig
+from snaps.config.router import RouterConfig
+from snaps.config.security_group import SecurityGroupConfig
+from snaps.config.user import UserConfig
+from snaps.config.vm_inst import VmInstanceConfig
+from snaps.config.volume import VolumeConfig
+from snaps.config.volume_type import VolumeTypeConfig
+from snaps.openstack.create_flavor import OpenStackFlavor
+from snaps.openstack.create_image import OpenStackImage
+from snaps.openstack.create_keypairs import OpenStackKeypair
+from snaps.openstack.create_network import OpenStackNetwork
+from snaps.openstack.create_project import OpenStackProject
+from snaps.openstack.create_qos import OpenStackQoS
+from snaps.openstack.create_router import OpenStackRouter
+from snaps.openstack.create_security_group import OpenStackSecurityGroup
+from snaps.openstack.create_user import OpenStackUser
+from snaps.openstack.create_volume import OpenStackVolume
+from snaps.openstack.create_volume_type import OpenStackVolumeType
+from snaps.openstack.os_credentials import OSCreds, ProxySettings
+from snaps.openstack.utils import deploy_utils, neutron_utils, keystone_utils
+from snaps.openstack.utils.nova_utils import RebootType
+from snaps.provisioning import ansible_utils
+
+from warnings import warn
+warn('This utility will be removed in a subsequent release',
+ DeprecationWarning)
+
+logger = logging.getLogger('lanuch_utils')
+DEFAULT_CREDS_KEY = 'admin'
+
+
+def launch_config(config, tmplt_file, deploy, clean, clean_image):
+ """
+ Launches all objects and applies any configured ansible playbooks
+ :param config: the environment configuration dict object
+ :param tmplt_file: the path to the SNAPS-OO template file
+ :param deploy: when True deploy
+ :param clean: when True clean
+ :param clean_image: when True clean the image when clean is True
+ """
+ os_config = config.get('openstack')
+
+ creators = list()
+ vm_dict = dict()
+ images_dict = dict()
+ flavors_dict = dict()
+ networks_dict = dict()
+ routers_dict = dict()
+ os_creds_dict = dict()
+
+ if os_config:
+ os_creds_dict = __get_creds_dict(os_config)
+
+ # Create projects
+ projects_dict = __create_instances(
+ os_creds_dict, OpenStackProject, ProjectConfig,
+ os_config.get('projects'), 'project', clean)
+ creators.append(projects_dict)
+
+ # Create users
+ users_dict = __create_instances(
+ os_creds_dict, OpenStackUser, UserConfig,
+ os_config.get('users'), 'user', clean)
+ creators.append(users_dict)
+
+ # Associate new users to projects
+ if not clean:
+ for project_creator in projects_dict.values():
+ users = project_creator.project_settings.users
+ for user_name in users:
+ user_creator = users_dict.get(user_name)
+ if user_creator:
+ project_creator.assoc_user(
+ user_creator.get_user())
+
+ # Create flavors
+ flavors_dict = __create_instances(
+ os_creds_dict, OpenStackFlavor, FlavorConfig,
+ os_config.get('flavors'), 'flavor', clean, users_dict)
+ creators.append(flavors_dict)
+
+ # Create QoS specs
+ qos_dict = __create_instances(
+ os_creds_dict, OpenStackQoS, QoSConfig,
+ os_config.get('qos_specs'), 'qos_spec', clean, users_dict)
+ creators.append(qos_dict)
+
+ # Create volume types
+ vol_type_dict = __create_instances(
+ os_creds_dict, OpenStackVolumeType, VolumeTypeConfig,
+ os_config.get('volume_types'), 'volume_type', clean,
+ users_dict)
+ creators.append(vol_type_dict)
+
+ # Create volumes
+ vol_dict = __create_instances(
+ os_creds_dict, OpenStackVolume, VolumeConfig,
+ os_config.get('volumes'), 'volume', clean, users_dict)
+ creators.append(vol_dict)
+
+ # Create images
+ images_dict = __create_instances(
+ os_creds_dict, OpenStackImage, ImageConfig,
+ os_config.get('images'), 'image', clean, users_dict)
+ creators.append(images_dict)
+
+ # Create networks
+ networks_dict = __create_instances(
+ os_creds_dict, OpenStackNetwork, NetworkConfig,
+ os_config.get('networks'), 'network', clean, users_dict)
+ creators.append(networks_dict)
+
+ # Create routers
+ routers_dict = __create_instances(
+ os_creds_dict, OpenStackRouter, RouterConfig,
+ os_config.get('routers'), 'router', clean, users_dict)
+ creators.append(routers_dict)
+
+ # Create keypairs
+ keypairs_dict = __create_instances(
+ os_creds_dict, OpenStackKeypair, KeypairConfig,
+ os_config.get('keypairs'), 'keypair', clean, users_dict)
+ creators.append(keypairs_dict)
+
+ # Create security groups
+ creators.append(__create_instances(
+ os_creds_dict, OpenStackSecurityGroup,
+ SecurityGroupConfig,
+ os_config.get('security_groups'), 'security_group', clean,
+ users_dict))
+
+ # Create instance
+ vm_dict = __create_vm_instances(
+ os_creds_dict, users_dict, os_config.get('instances'),
+ images_dict, keypairs_dict, clean)
+ creators.append(vm_dict)
+ logger.info(
+ 'Completed creating/retrieving all configured instances')
+
+ # Must enter either block
+ if clean:
+ # Cleanup Environment
+ __cleanup(creators, clean_image)
+ elif deploy:
+ # Provision VMs
+ ansible_config = config.get('ansible')
+ if ansible_config and vm_dict:
+ if not __apply_ansible_playbooks(
+ ansible_config, os_creds_dict, vm_dict, images_dict,
+ flavors_dict, networks_dict, routers_dict, tmplt_file):
+ logger.error("Problem applying ansible playbooks")
+
+
+def __get_creds_dict(os_conn_config):
+ """
+ Returns a dict of OSCreds where the key is the creds name.
+ For backwards compatibility, credentials not contained in a list (only
+ one) will be returned with the key of None
+ :param os_conn_config: the credential configuration
+ :return: a dict of OSCreds objects
+ """
+ if 'connection' in os_conn_config:
+ return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)}
+ elif 'connections' in os_conn_config:
+ out = dict()
+ for os_conn_dict in os_conn_config['connections']:
+ config = os_conn_dict.get('connection')
+ if not config:
+ raise Exception('Invalid connection format')
+
+ name = config.get('name')
+ if not name:
+ raise Exception('Connection config requires a name field')
+
+ out[name] = __get_os_credentials(os_conn_dict)
+ return out
+
+
+def __get_creds(os_creds_dict, os_user_dict, inst_config):
+ """
+ Returns the appropriate credentials
+ :param os_creds_dict: a dictionary of OSCreds objects where the name is the
+ key
+ :param os_user_dict: a dictionary of OpenStackUser objects where the name
+ is the key
+ :param inst_config:
+ :return: an OSCreds instance or None
+ """
+ os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY)
+ if 'os_user' in inst_config:
+ os_user_conf = inst_config['os_user']
+ if 'name' in os_user_conf:
+ user_creator = os_user_dict.get(os_user_conf['name'])
+ if user_creator:
+ return user_creator.get_os_creds(
+ project_name=os_user_conf.get('project_name'))
+ elif 'os_creds_name' in inst_config:
+ if 'os_creds_name' in inst_config:
+ os_creds = os_creds_dict[inst_config['os_creds_name']]
+ return os_creds
+
+
+def __get_os_credentials(os_conn_config):
+ """
+ Returns an object containing all of the information required to access
+ OpenStack APIs
+ :param os_conn_config: The configuration holding the credentials
+ :return: an OSCreds instance
+ """
+ config = os_conn_config.get('connection')
+ if not config:
+ raise Exception('Invalid connection configuration')
+
+ proxy_settings = None
+ http_proxy = config.get('http_proxy')
+ if http_proxy:
+ tokens = re.split(':', http_proxy)
+ ssh_proxy_cmd = config.get('ssh_proxy_cmd')
+ proxy_settings = ProxySettings(host=tokens[0], port=tokens[1],
+ ssh_proxy_cmd=ssh_proxy_cmd)
+ else:
+ if 'proxy_settings' in config:
+ host = config['proxy_settings'].get('host')
+ port = config['proxy_settings'].get('port')
+ if host and host != 'None' and port and port != 'None':
+ proxy_settings = ProxySettings(**config['proxy_settings'])
+
+ if proxy_settings:
+ config['proxy_settings'] = proxy_settings
+ else:
+ if config.get('proxy_settings'):
+ del config['proxy_settings']
+
+ return OSCreds(**config)
+
+
+def __parse_ports_config(config):
+ """
+ Parses the "ports" configuration
+ :param config: The dictionary to parse
+ :return: a list of PortConfig objects
+ """
+ out = list()
+ for port_config in config:
+ out.append(PortConfig(**port_config.get('port')))
+ return out
+
+
+def __create_instances(os_creds_dict, creator_class, config_class, config,
+ config_key, cleanup=False, os_users_dict=None):
+ """
+ Returns a dictionary of SNAPS creator objects where the key is the name
+ :param os_creds_dict: Dictionary of OSCreds objects where the key is the
+ name
+ :param config: The list of configurations for the same type
+ :param config_key: The list of configurations for the same type
+ :param cleanup: Denotes whether or not this is being called for cleanup
+ :return: dictionary
+ """
+ out = {}
+
+ if config:
+ for config_dict in config:
+ inst_config = config_dict.get(config_key)
+ if inst_config:
+ creds = __get_creds(os_creds_dict, os_users_dict, inst_config)
+ if creds:
+ creator = creator_class(
+ creds,
+ config_class(**inst_config))
+
+ if creator:
+ if cleanup:
+ try:
+ creator.initialize()
+ except Unauthorized as e:
+ logger.warn(
+ 'Unable to initialize creator [%s] - %s',
+ creator, e)
+ else:
+ creator.create()
+
+ out[inst_config['name']] = creator
+ else:
+ raise Exception('Unable to instantiate creator')
+
+ logger.info('Initialized configured %ss', config_key)
+
+ return out
+
+
+def __create_vm_instances(os_creds_dict, os_users_dict, instances_config,
+ image_dict, keypairs_dict, cleanup=False):
+ """
+ Returns a dictionary of OpenStackVmInstance objects where the key is the
+ instance name
+ :param os_creds_dict: Dictionary of OSCreds objects where the key is the
+ name
+ :param os_users_dict: Dictionary of OpenStackUser objects where the key is
+ the username
+ :param instances_config: The list of VM instance configurations
+ :param image_dict: A dictionary of images that will probably be used to
+ instantiate the VM instance
+ :param keypairs_dict: A dictionary of keypairs that will probably be used
+ to instantiate the VM instance
+ :param cleanup: Denotes whether or not this is being called for cleanup
+ :return: dictionary
+ """
+ vm_dict = {}
+
+ if instances_config:
+ for instance_config in instances_config:
+ conf = instance_config.get('instance')
+ if conf:
+ if image_dict:
+ image_creator = image_dict.get(conf.get('imageName'))
+ if image_creator:
+ instance_settings = VmInstanceConfig(
+ **instance_config['instance'])
+ kp_creator = keypairs_dict.get(
+ conf.get('keypair_name'))
+
+ try:
+ vm_dict[conf[
+ 'name']] = deploy_utils.create_vm_instance(
+ __get_creds(
+ os_creds_dict, os_users_dict, conf),
+ instance_settings,
+ image_creator.image_settings,
+ keypair_creator=kp_creator,
+ init_only=cleanup)
+ except Unauthorized as e:
+ if not cleanup:
+ logger.warn('Unable to initialize VM - %s', e)
+ raise
+ else:
+ raise Exception('Image creator instance not found.'
+ ' Cannot instantiate')
+ else:
+ if not cleanup:
+ raise Exception('Image dictionary is None. Cannot '
+ 'instantiate')
+ else:
+ raise Exception('Instance configuration is None. Cannot '
+ 'instantiate')
+ logger.info('Created configured instances')
+
+ return vm_dict
+
+
+def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict,
+ image_dict, flavor_dict, networks_dict,
+ routers_dict, tmplt_file):
+ """
+ Applies ansible playbooks to running VMs with floating IPs
+ :param ansible_configs: a list of Ansible configurations
+ :param os_creds_dict: Dictionary of OSCreds objects where the key is the
+ name
+ :param vm_dict: the dictionary of newly instantiated VMs where the name is
+ the key
+ :param image_dict: the dictionary of newly instantiated images where the
+ name is the key
+ :param flavor_dict: the dictionary of newly instantiated flavors where the
+ name is the key
+ :param networks_dict: the dictionary of newly instantiated networks where
+ the name is the key
+ :param routers_dict: the dictionary of newly instantiated routers where
+ the name is the key
+ :param tmplt_file: the path of the SNAPS-OO template file for setting the
+ CWD so playbook location is relative to the deployment
+ file
+ :return: t/f - true if successful
+ """
+ logger.info("Applying Ansible Playbooks")
+ if ansible_configs:
+ # Set CWD so the deployment file's playbook location can leverage
+ # relative paths
+ orig_cwd = os.getcwd()
+ env_dir = os.path.dirname(tmplt_file)
+ os.chdir(env_dir)
+
+ # Apply playbooks
+ for ansible_config in ansible_configs:
+ # Ensure all hosts are accepting SSH session requests
+ for vm_name in ansible_config['hosts']:
+ vm_inst = vm_dict.get(vm_name)
+ if vm_inst:
+ if not vm_inst.vm_ssh_active(block=True):
+ logger.warning(
+ 'Timeout waiting for instance to respond to '
+ 'SSH requests')
+ return False
+
+ __apply_ansible_playbook(
+ ansible_config, os_creds_dict, vm_dict, image_dict,
+ flavor_dict, networks_dict, routers_dict)
+
+ # Return to original directory
+ os.chdir(orig_cwd)
+
+ return True
+
+
+def __apply_ansible_playbook(ansible_config, os_creds_dict, vm_dict,
+ image_dict, flavor_dict, networks_dict,
+ routers_dict):
+ """
+ Applies an Ansible configuration setting
+ :param ansible_config: the configuration settings
+ :param os_creds_dict: dict where the key is the name and value is OSCreds
+ :param vm_dict: the dictionary of newly instantiated VMs where the name is
+ the key
+ :param image_dict: the dictionary of newly instantiated images where the
+ name is the key
+ :param flavor_dict: the dictionary of newly instantiated flavors where the
+ name is the key
+ :param networks_dict: the dictionary of newly instantiated networks where
+ the name is the key
+ :param routers_dict: the dictionary of newly instantiated routers where
+ the name is the key
+ """
+ if ansible_config:
+ (remote_user, floating_ips, private_key_filepath,
+ proxy_settings) = __get_connection_info(
+ ansible_config, vm_dict)
+ if floating_ips:
+ for key, vm_creator in vm_dict.items():
+ fip = vm_creator.get_floating_ip()
+ if fip and fip.ip in floating_ips:
+ if not vm_creator.cloud_init_complete(block=True):
+ raise Exception(
+ 'Cannot apply playbooks as cloud-init has not '
+ 'completed')
+
+ variables = __get_variables(
+ ansible_config.get('variables'), os_creds_dict, vm_dict,
+ image_dict, flavor_dict, networks_dict, routers_dict)
+
+ ansible_utils.apply_playbook(
+ ansible_config['playbook_location'], floating_ips, remote_user,
+ ssh_priv_key_file_path=private_key_filepath,
+ variables=variables,
+ proxy_setting=proxy_settings)
+
+ if 'post_processing' in ansible_config:
+ post_proc_config = ansible_config['post_processing']
+ if 'sleep' in post_proc_config:
+ time.sleep(post_proc_config['sleep'])
+ if 'reboot' in post_proc_config:
+ for vm_name in post_proc_config['reboot']:
+ if vm_name in vm_dict:
+ logger.info('Rebooting VM - %s', vm_name)
+ vm_dict[vm_name].reboot(RebootType.hard)
+
+
+def __get_connection_info(ansible_config, vm_dict):
+ """
+ Returns a tuple of data required for connecting to the running VMs
+ (remote_user, [floating_ips], private_key_filepath, proxy_settings)
+ :param ansible_config: the configuration settings
+ :param vm_dict: the dictionary of VMs where the VM name is the key
+ :return: tuple where the first element is the user and the second is a list
+ of floating IPs and the third is the
+ private key file location and the fourth is an instance of the
+ snaps.ProxySettings class
+ (note: in order to work, each of the hosts need to have the same sudo_user
+ and private key file location values)
+ """
+ if ansible_config.get('hosts'):
+ hosts = ansible_config['hosts']
+ if len(hosts) > 0:
+ floating_ips = list()
+ remote_user = None
+ pk_file = None
+ proxy_settings = None
+ for host in hosts:
+ vm = vm_dict.get(host)
+ if vm:
+ fip = vm.get_floating_ip()
+ if fip:
+ remote_user = vm.get_image_user()
+
+ if fip:
+ floating_ips.append(fip.ip)
+ else:
+ raise Exception(
+ 'Could not find floating IP for VM - ' +
+ vm.name)
+
+ pk_file = vm.keypair_settings.private_filepath
+ proxy_settings = vm.get_os_creds().proxy_settings
+ else:
+ logger.error('Could not locate VM with name - ' + host)
+
+ return remote_user, floating_ips, pk_file, proxy_settings
+ return None
+
+
+def __get_variables(var_config, os_creds_dict, vm_dict, image_dict,
+ flavor_dict, networks_dict, routers_dict):
+ """
+ Returns a dictionary of substitution variables to be used for Ansible
+ templates
+ :param var_config: the variable configuration settings
+ :param os_creds_dict: dict where the key is the name and value is OSCreds
+ :param vm_dict: the dictionary of newly instantiated VMs where the name is
+ the key
+ :param image_dict: the dictionary of newly instantiated images where the
+ name is the key
+ :param flavor_dict: the dictionary of newly instantiated flavors where the
+ name is the key
+ :param networks_dict: the dictionary of newly instantiated networks where
+ the name is the key
+ :param routers_dict: the dictionary of newly instantiated routers where
+ the name is the key
+ :return: dictionary or None
+ """
+ if var_config and vm_dict and len(vm_dict) > 0:
+ variables = dict()
+ for key, value in var_config.items():
+ value = __get_variable_value(
+ value, os_creds_dict, vm_dict, image_dict, flavor_dict,
+ networks_dict, routers_dict)
+ if key and value:
+ variables[key] = value
+ logger.info(
+ "Set Jinga2 variable with key [%s] the value [%s]",
+ key, value)
+ else:
+ raise Exception(
+ 'Key - [' + str(key) + '] or Value [' + str(value)
+ + '] must not be None')
+ return variables
+ return None
+
+
+def __get_variable_value(var_config_values, os_creds_dict, vm_dict, image_dict,
+ flavor_dict, networks_dict, routers_dict):
+ """
+ Returns the associated variable value for use by Ansible for substitution
+ purposes
+ :param var_config_values: the configuration dictionary
+ :param os_creds_dict: dict where the key is the name and value is OSCreds
+ :param vm_dict: the dictionary of newly instantiated VMs where the name is
+ the key
+ :param image_dict: the dictionary of newly instantiated images where the
+ name is the key
+ :param flavor_dict: the dictionary of newly instantiated flavors where the
+ name is the key
+ :param networks_dict: the dictionary of newly instantiated networks where
+ the name is the key
+ :param routers_dict: the dictionary of newly instantiated routers where
+ the name is the key
+ :return:
+ """
+ if var_config_values['type'] == 'string':
+ return __get_string_variable_value(var_config_values)
+ if var_config_values['type'] == 'vm-attr':
+ return __get_vm_attr_variable_value(var_config_values, vm_dict)
+ if var_config_values['type'] == 'os_creds':
+ return __get_os_creds_variable_value(var_config_values, os_creds_dict)
+ if var_config_values['type'] == 'os_creds_dict':
+ return str(__get_os_creds_dict(var_config_values, os_creds_dict))
+ if var_config_values['type'] == 'network':
+ return __get_network_variable_value(var_config_values, networks_dict)
+ if var_config_values['type'] == 'router':
+ return __get_router_variable_value(var_config_values, routers_dict,
+ os_creds_dict)
+ if var_config_values['type'] == 'port':
+ return __get_vm_port_variable_value(var_config_values, vm_dict)
+ if var_config_values['type'] == 'floating_ip':
+ return __get_vm_fip_variable_value(var_config_values, vm_dict)
+ if var_config_values['type'] == 'image':
+ return __get_image_variable_value(var_config_values, image_dict)
+ if var_config_values['type'] == 'flavor':
+ return __get_flavor_variable_value(var_config_values, flavor_dict)
+ if var_config_values['type'] == 'vm-yaml':
+ return __create_yaml(var_config_values, vm_dict)
+ return None
+
+
+def __get_string_variable_value(var_config_values):
+ """
+ Returns the associated string value
+ :param var_config_values: the configuration dictionary
+ :return: the value contained in the dictionary with the key 'value'
+ """
+ return var_config_values['value']
+
+
+def __get_vm_attr_variable_value(var_config_values, vm_dict):
+ """
+ Returns the associated value contained on a VM instance
+ :param var_config_values: the configuration dictionary
+ :param vm_dict: the dictionary containing all VMs where the key is the VM's
+ name
+ :return: the value
+ """
+ vm = vm_dict.get(var_config_values['vm_name'])
+ if vm:
+ if var_config_values['value'] == 'floating_ip':
+ return vm.get_floating_ip().ip
+ if var_config_values['value'] == 'image_user':
+ return vm.get_image_user()
+
+
+def __get_os_creds_variable_value(var_config_values, os_creds_dict):
+ """
+ Returns the associated OS credentials value
+ :param var_config_values: the configuration dictionary
+ :param os_creds_dict: dict of OpenStack credentials where the key is the
+ name
+ :return: the value
+ """
+ if 'creds_name' in var_config_values:
+ os_creds = os_creds_dict.get[var_config_values['creds_name']]
+ else:
+ os_creds = os_creds_dict.get('admin-creds')
+
+ if os_creds:
+ if var_config_values['value'] == 'username':
+ logger.info("Returning OS username")
+ return os_creds.username
+ elif var_config_values['value'] == 'password':
+ logger.info("Returning OS password")
+ return os_creds.password
+ elif var_config_values['value'] == 'auth_url':
+ logger.info("Returning OS auth_url")
+ return os_creds.auth_url
+ elif var_config_values['value'] == 'project_name':
+ logger.info("Returning OS project_name")
+ return os_creds.project_name
+
+
+def __get_os_creds_dict(var_config_values, os_creds_dict):
+ """
+ Returns the associated OS credentials as a dict
+ :param var_config_values: the configuration dictionary
+ :param os_creds_dict: dict of creds where the key is the username
+ :return: the value dict
+ """
+ if 'creds_name' in var_config_values:
+ os_creds = os_creds_dict.get[var_config_values['creds_name']]
+ else:
+ os_creds = os_creds_dict.get('admin-creds')
+ if os_creds:
+ return os_creds.to_dict()
+
+
+def __get_network_variable_value(var_config_values, networks_dict):
+ """
+ Returns the associated network value
+ :param var_config_values: the configuration dictionary
+ :param networks_dict: the dictionary containing all networks where the key
+ is the network name
+ :return: the value
+ """
+ net_name = var_config_values.get('network_name')
+
+ if net_name and networks_dict.get(net_name):
+ network_creator = networks_dict[net_name]
+
+ if 'subnet_name' in var_config_values:
+ subnet_name = var_config_values.get('subnet_name')
+ if subnet_name:
+ for subnet in network_creator.get_network().subnets:
+ if subnet_name == subnet.name:
+ if 'value' in var_config_values:
+ if 'gateway_ip' == var_config_values['value']:
+ return subnet.gateway_ip
+ if 'ip_range' == var_config_values['value']:
+ return subnet.start + ' ' + subnet.end
+ if 'ip_range_start' == var_config_values['value']:
+ return subnet.start
+ if 'ip_range_end' == var_config_values['value']:
+ return subnet.end
+ if 'cidr' == var_config_values['value']:
+ return subnet.cidr
+ if 'cidr_ip' == var_config_values['value']:
+ cidr_split = subnet.cidr.split('/')
+ return cidr_split[0]
+ if 'netmask' == var_config_values['value']:
+ cidr_split = subnet.cidr.split('/')
+ cidr_bits = 32 - int(cidr_split[1])
+ netmask = socket.inet_ntoa(
+ struct.pack(
+ '!I', (1 << 32) - (1 << cidr_bits)))
+ return netmask
+ if 'broadcast_ip' == var_config_values['value']:
+ end_split = subnet.end.split('.')
+ broadcast_ip = (
+ end_split[0] + '.' + end_split[1] + '.'
+ + end_split[2] + '.255')
+ return broadcast_ip
+
+
+def __get_router_variable_value(var_config_values, routers_dict,
+ os_creds_dict):
+ """
+ Returns the associated network value
+ :param var_config_values: the configuration dictionary
+ :param routers_dict: the dictionary containing all networks where the key
+ is the network name
+ :param os_creds_dict: dict of OpenStack credentials where the key is the
+ name
+ :return: the value
+ """
+ if 'creds_name' in var_config_values:
+ os_creds = os_creds_dict.get[var_config_values['creds_name']]
+ else:
+ os_creds = os_creds_dict.get('admin-creds')
+
+ router_name = var_config_values.get('router_name')
+ router_creator = routers_dict[router_name]
+
+ if router_creator:
+ if 'external_fixed_ip' == var_config_values.get('attr'):
+ session = keystone_utils.keystone_session(os_creds)
+ neutron = neutron_utils.neutron_client(os_creds, session)
+ try:
+ ext_nets = neutron_utils.get_external_networks(neutron)
+
+ subnet_name = var_config_values.get('subnet_name')
+
+ for ext_net in ext_nets:
+ for subnet in ext_net.subnets:
+ if subnet_name == subnet.name:
+ router = router_creator.get_router()
+ for fixed_ips in router.external_fixed_ips:
+ if subnet.id == fixed_ips['subnet_id']:
+ return fixed_ips['ip_address']
+ finally:
+ keystone_utils.close_session(session)
+
+
+def __get_vm_port_variable_value(var_config_values, vm_dict):
+ """
+ Returns the associated OS credentials value
+ :param var_config_values: the configuration dictionary
+ :param vm_dict: the dictionary containing all VMs where the key is the VM's
+ name
+ :return: the value
+ """
+ port_name = var_config_values.get('port_name')
+ vm_name = var_config_values.get('vm_name')
+
+ if port_name and vm_name:
+ vm = vm_dict.get(vm_name)
+ if vm:
+ for vm_port in vm.get_vm_inst().ports:
+ if vm_port.name == port_name:
+ port_value_id = var_config_values.get('port_value')
+ if port_value_id:
+ if port_value_id == 'mac_address':
+ return vm_port.mac_address
+ if port_value_id == 'ip_address':
+ return vm_port.ips[0]['ip_address']
+
+
+def __get_vm_fip_variable_value(var_config_values, vm_dict):
+ """
+ Returns the floating IP value if found
+ :param var_config_values: the configuration dictionary
+ :param vm_dict: the dictionary containing all VMs where the key is the VM's
+ name
+ :return: the floating IP string value or None
+ """
+ fip_name = var_config_values.get('fip_name')
+ vm_name = var_config_values.get('vm_name')
+
+ if vm_name:
+ vm = vm_dict.get(vm_name)
+ if vm:
+ fip = vm.get_floating_ip(fip_name)
+ if fip:
+ return fip.ip
+
+
+def __get_image_variable_value(var_config_values, image_dict):
+ """
+ Returns the associated image value
+ :param var_config_values: the configuration dictionary
+ :param image_dict: the dictionary containing all images where the key is
+ the name
+ :return: the value
+ """
+ if image_dict:
+ if var_config_values.get('image_name'):
+ image_creator = image_dict.get(var_config_values['image_name'])
+ if image_creator:
+ if (var_config_values.get('value')
+ and var_config_values['value'] == 'id'):
+ return image_creator.get_image().id
+ if (var_config_values.get('value')
+ and var_config_values['value'] == 'user'):
+ return image_creator.image_settings.image_user
+
+
+def __get_flavor_variable_value(var_config_values, flavor_dict):
+ """
+ Returns the associated flavor value
+ :param var_config_values: the configuration dictionary
+ :param flavor_dict: the dictionary containing all flavor creators where the
+ key is the name
+ :return: the value or None
+ """
+ if flavor_dict:
+ if var_config_values.get('flavor_name'):
+ flavor_creator = flavor_dict.get(var_config_values['flavor_name'])
+ if flavor_creator:
+ if (var_config_values.get('value')
+ and var_config_values['value'] == 'id'):
+ return flavor_creator.get_flavor().id
+
+
+def __create_yaml(var_config_values, vm_dict):
+ """
+ Creates a yaml file containing an OpenStack pod's credentials with a list
+ of server IDs that can be used for obtaining SNAPS-OO instances for
+ manipulation such as rebooting
+ :param var_config_values: the configuration dictionary
+ :param vm_dict: the dictionary containing all vm creators where the
+ key is the name
+ :return: the name of the generated file
+ """
+ out_dict = dict()
+ out_dict['vms'] = list()
+ req_vm_names = var_config_values.get('vms')
+
+ for name, vm_creator in vm_dict.items():
+ vm_inst = vm_creator.get_vm_inst()
+ if vm_inst and vm_inst.name in req_vm_names:
+ out_dict['vms'].append({
+ 'name': str(vm_inst.name),
+ 'id': str(vm_inst.id),
+ 'os_creds': vm_creator.get_os_creds().to_dict()
+ })
+
+ out_file = file_utils.persist_dict_to_yaml(
+ out_dict, var_config_values.get('file_name'))
+
+ if out_file:
+ return out_file.name
+
+
+def __cleanup(creators, clean_image=False):
+ """
+ Cleans up environment
+ :param creators: the list of creators by type
+ :param clean_image: when true
+ :return:
+ """
+ for creator_dict in reversed(creators):
+ for key, creator in creator_dict.items():
+ if ((isinstance(creator, OpenStackImage) and clean_image)
+ or not isinstance(creator, OpenStackImage)):
+ creator.clean()
diff --git a/snaps/openstack/utils/magnum_utils.py b/snaps/openstack/utils/magnum_utils.py
new file mode 100644
index 0000000..1f39cfe
--- /dev/null
+++ b/snaps/openstack/utils/magnum_utils.py
@@ -0,0 +1,129 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import logging
+
+from magnumclient.client import Client
+
+from snaps.domain.cluster_template import ClusterTemplate
+from snaps.openstack.utils import keystone_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('magnum_utils')
+
+
+def magnum_client(os_creds, session=None):
+ """
+ Retrieves the Magnum client
+ :param os_creds: the OpenStack credentialsf
+ :param session: the keystone session object (optional)
+ :return: the client
+ """
+ logger.debug('Retrieving Magnum Client')
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
+
+ return Client(str(os_creds.magnum_api_version), session=session)
+
+
+def get_cluster_template(magnum, template_config=None, template_name=None):
+ """
+ Returns the first ClusterTemplate domain object that matches the parameters
+ :param magnum: the Magnum client
+ :param template_config: a ClusterTemplateConfig object (optional)
+ :param template_name: the name of the template to lookup
+ :return: ClusterTemplate object or None
+ """
+ name = None
+ if template_config:
+ name = template_config.name
+ elif template_name:
+ name = template_name
+
+ os_templates = magnum.cluster_templates.list()
+ for os_template in os_templates:
+ if os_template.name == name:
+ return __map_os_cluster_template(os_template)
+
+
+def get_cluster_template_by_id(magnum, tmplt_id):
+ """
+ Returns the first ClusterTemplate domain object that matches the parameters
+ :param magnum: the Magnum client
+ :param tmplt_id: the template's ID
+ :return: ClusterTemplate object or None
+ """
+ return __map_os_cluster_template(magnum.cluster_templates.get(tmplt_id))
+
+
+def create_cluster_template(magnum, cluster_template_config):
+ """
+ Creates a Magnum Cluster Template object in OpenStack
+ :param magnum: the Magnum client
+ :param cluster_template_config: a ClusterTemplateConfig object
+ :return: a SNAPS ClusterTemplate domain object
+ """
+ config_dict = cluster_template_config.magnum_dict()
+ os_cluster_template = magnum.cluster_templates.create(**config_dict)
+ logger.info('Creating cluster template named [%s]',
+ cluster_template_config.name)
+ return __map_os_cluster_template(os_cluster_template)
+
+
+def delete_cluster_template(magnum, tmplt_id):
+ """
+ Deletes a Cluster Template from OpenStack
+ :param magnum: the Magnum client
+ :param tmplt_id: the cluster template ID to delete
+ """
+ logger.info('Deleting cluster template with ID [%s]', tmplt_id)
+ magnum.cluster_templates.delete(tmplt_id)
+
+
+def __map_os_cluster_template(os_tmplt):
+ """
+ Returns a SNAPS ClusterTemplate object from an OpenStack ClusterTemplate
+ object
+ :param os_tmplt: the OpenStack ClusterTemplate object
+ :return: SNAPS ClusterTemplate object
+ """
+ return ClusterTemplate(
+ id=os_tmplt.uuid,
+ name=os_tmplt.name,
+ image=os_tmplt.image_id,
+ keypair=os_tmplt.keypair_id,
+ network_driver=os_tmplt.network_driver,
+ external_net=os_tmplt.external_network_id,
+ floating_ip_enabled=os_tmplt.floating_ip_enabled,
+ docker_volume_size=os_tmplt.docker_volume_size,
+ server_type=os_tmplt.server_type,
+ flavor=os_tmplt.flavor_id,
+ master_flavor=os_tmplt.master_flavor_id,
+ coe=os_tmplt.coe,
+ fixed_net=os_tmplt.fixed_network,
+ fixed_subnet=os_tmplt.fixed_subnet,
+ registry_enabled=os_tmplt.registry_enabled,
+ insecure_registry=os_tmplt.insecure_registry,
+ docker_storage_driver=os_tmplt.docker_storage_driver,
+ dns_nameserver=os_tmplt.dns_nameserver,
+ public=os_tmplt.public,
+ tls_disabled=os_tmplt.tls_disabled,
+ http_proxy=os_tmplt.http_proxy,
+ https_proxy=os_tmplt.https_proxy,
+ no_proxy=os_tmplt.no_proxy,
+ volume_driver=os_tmplt.volume_driver,
+ master_lb_enabled=os_tmplt.master_lb_enabled,
+ labels=os_tmplt.labels
+ )
diff --git a/snaps/openstack/utils/neutron_utils.py b/snaps/openstack/utils/neutron_utils.py
index 806bb53..f1a5ac2 100644
--- a/snaps/openstack/utils/neutron_utils.py
+++ b/snaps/openstack/utils/neutron_utils.py
@@ -33,15 +33,18 @@ Utilities for basic neutron API calls
"""
-def neutron_client(os_creds):
+def neutron_client(os_creds, session=None):
"""
Instantiates and returns a client for communications with OpenStack's
Neutron server
:param os_creds: the credentials for connecting to the OpenStack remote API
+ :param session: the keystone session object (optional)
:return: the client object
"""
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
return Client(api_version=os_creds.network_api_version,
- session=keystone_utils.keystone_session(os_creds),
+ session=session,
region_name=os_creds.region_name)
@@ -53,15 +56,33 @@ def create_network(neutron, os_creds, network_settings):
:param network_settings: A dictionary containing the network configuration
and is responsible for creating the network
request JSON body
- :return: a SNAPS-OO Network domain object
+ :return: a SNAPS-OO Network domain object if found else None
"""
- if neutron and network_settings:
- logger.info('Creating network with name ' + network_settings.name)
- json_body = network_settings.dict_for_neutron(os_creds)
- os_network = neutron.create_network(body=json_body)
- return Network(**os_network['network'])
- else:
- raise NeutronException('Failded to create network')
+ logger.info('Creating network with name ' + network_settings.name)
+ json_body = network_settings.dict_for_neutron(os_creds)
+ os_network = neutron.create_network(body=json_body)
+
+ if os_network:
+ network = get_network_by_id(neutron, os_network['network']['id'])
+
+ subnets = list()
+ for subnet_settings in network_settings.subnet_settings:
+ try:
+ subnets.append(
+ create_subnet(neutron, subnet_settings, os_creds, network))
+ except:
+ logger.error(
+ 'Unexpected error creating subnet [%s] for network [%s]',
+ subnet_settings.name, network.name)
+
+ for subnet in subnets:
+ delete_subnet(neutron, subnet)
+
+ delete_network(neutron, network)
+
+ raise
+
+ return get_network_by_id(neutron, network.id)
def delete_network(neutron, network):
@@ -71,21 +92,30 @@ def delete_network(neutron, network):
:param network: a SNAPS-OO Network domain object
"""
if neutron and network:
+ if network.subnets:
+ for subnet in network.subnets:
+ logger.info('Deleting subnet with name ' + subnet.name)
+ try:
+ delete_subnet(neutron, subnet)
+ except NotFound:
+ pass
+
logger.info('Deleting network with name ' + network.name)
neutron.delete_network(network.id)
-def get_network(neutron, network_settings=None, network_name=None,
- project_id=None):
+def get_network(neutron, keystone, network_settings=None, network_name=None,
+ project_name=None):
"""
Returns Network SNAPS-OO domain object the first network found with
either the given attributes from the network_settings object if not None,
else the query will use just the name from the network_name parameter.
- When the project_id is included, that will be added to the query filter.
- :param neutron: the client
- :param network_settings: the NetworkSettings object used to create filter
+ When the project_name is included, that will be added to the query filter.
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
+ :param network_settings: the NetworkConfig object used to create filter
:param network_name: the name of the network to retrieve
- :param project_id: the id of the network's project
+ :param project_name: the name of the network's project
:return: a SNAPS-OO Network domain object
"""
net_filter = dict()
@@ -94,37 +124,69 @@ def get_network(neutron, network_settings=None, network_name=None,
elif network_name:
net_filter['name'] = network_name
- if project_id:
- net_filter['project_id'] = project_id
-
networks = neutron.list_networks(**net_filter)
for network, netInsts in networks.items():
for inst in netInsts:
- return Network(**inst)
+ if project_name:
+ if 'project_id' in inst.keys():
+ project = keystone_utils.get_project_by_id(
+ keystone, inst['project_id'])
+ else:
+ project = keystone_utils.get_project_by_id(
+ keystone, inst['tenant_id'])
+ if project and project.name == project_name:
+ return __map_network(neutron, inst)
+ else:
+ return __map_network(neutron, inst)
+
+
+def __get_os_network_by_id(neutron, network_id):
+ """
+ Returns the OpenStack network object (dictionary) with the given ID else
+ None
+ :param neutron: the client
+ :param network_id: the id of the network to retrieve
+ :return: a SNAPS-OO Network domain object
+ """
+ networks = neutron.list_networks(**{'id': network_id})
+ for network in networks['networks']:
+ if network['id'] == network_id:
+ return network
def get_network_by_id(neutron, network_id):
"""
- Returns the network object (dictionary) with the given ID else None
+ Returns the SNAPS Network domain object for the given ID else None
:param neutron: the client
:param network_id: the id of the network to retrieve
:return: a SNAPS-OO Network domain object
"""
- networks = neutron.list_networks(**{'id': network_id})
- for network in networks['networks']:
- if network['id'] == network_id:
- return Network(**network)
+ os_network = __get_os_network_by_id(neutron, network_id)
+ if os_network:
+ return __map_network(neutron, os_network)
+
+
+def __map_network(neutron, os_network):
+ """
+ Returns the network object (dictionary) with the given ID else None
+ :param neutron: the client
+ :param os_network: the OpenStack Network dict
+ :return: a SNAPS-OO Network domain object
+ """
+ subnets = get_subnets_by_network_id(neutron, os_network['id'])
+ os_network['subnets'] = subnets
+ return Network(**os_network)
-def create_subnet(neutron, subnet_settings, os_creds, network=None):
+def create_subnet(neutron, subnet_settings, os_creds, network):
"""
Creates a network subnet for OpenStack
:param neutron: the client
- :param network: the network object
:param subnet_settings: A dictionary containing the subnet configuration
and is responsible for creating the subnet request
JSON body
:param os_creds: the OpenStack credentials
+ :param network: the network object
:return: a SNAPS-OO Subnet domain object
"""
if neutron and network and subnet_settings:
@@ -148,16 +210,17 @@ def delete_subnet(neutron, subnet):
neutron.delete_subnet(subnet.id)
-def get_subnet(neutron, subnet_settings=None, subnet_name=None):
+def get_subnet(neutron, network, subnet_settings=None, subnet_name=None):
"""
Returns the first subnet object that fits the query else None including
if subnet_settings or subnet_name parameters are None.
:param neutron: the client
+ :param network: the associated SNAPS-OO Network domain object
:param subnet_settings: the subnet settings of the object to retrieve
:param subnet_name: the name of the subnet to retrieve
:return: a SNAPS-OO Subnet domain object or None
"""
- sub_filter = dict()
+ sub_filter = {'network_id': network.id}
if subnet_settings:
sub_filter['name'] = subnet_settings.name
sub_filter['cidr'] = subnet_settings.cidr
@@ -188,6 +251,30 @@ def get_subnet(neutron, subnet_settings=None, subnet_name=None):
return Subnet(**subnet)
+def get_subnet_by_name(neutron, keystone, subnet_name, project_name=None):
+ """
+ Returns the first subnet object that fits the query else None including
+ if subnet_settings or subnet_name parameters are None.
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
+ :param subnet_name: the name of the subnet to retrieve
+ :param project_name: the name of the associated project to the subnet to
+ retrieve
+ :return: a SNAPS-OO Subnet domain object or None
+ """
+ sub_filter = {'name': subnet_name}
+ subnets = neutron.list_subnets(**sub_filter)
+ for subnet in subnets['subnets']:
+ subnet = Subnet(**subnet)
+ if project_name:
+ project = keystone_utils.get_project_by_id(
+ keystone, subnet.project_id)
+ if project and project.name == project_name:
+ return subnet
+ else:
+ return subnet
+
+
def get_subnet_by_id(neutron, subnet_id):
"""
Returns a SNAPS-OO Subnet domain object for a given ID
@@ -207,9 +294,19 @@ def get_subnets_by_network(neutron, network):
:param network: the SNAPS-OO Network domain object
:return: a list of Subnet objects
"""
+ return get_subnets_by_network_id(neutron, network.id)
+
+
+def get_subnets_by_network_id(neutron, network_id):
+ """
+ Returns a list of SNAPS-OO Subnet domain objects
+ :param neutron: the OpenStack neutron client
+ :param network_id: the subnet's ID
+ :return: a list of Subnet objects
+ """
out = list()
- os_subnets = neutron.list_subnets(network_id=network.id)
+ os_subnets = neutron.list_subnets(network_id=network_id)
for os_subnet in os_subnets['subnets']:
out.append(Subnet(**os_subnet))
@@ -231,7 +328,7 @@ def create_router(neutron, os_creds, router_settings):
json_body = router_settings.dict_for_neutron(neutron, os_creds)
logger.info('Creating router with name - ' + router_settings.name)
os_router = neutron.create_router(json_body)
- return Router(**os_router['router'])
+ return __map_router(neutron, os_router['router'])
else:
logger.error("Failed to create router.")
raise NeutronException('Failed to create router')
@@ -257,17 +354,20 @@ def get_router_by_id(neutron, router_id):
"""
router = neutron.show_router(router_id)
if router:
- return Router(**router['router'])
+ return __map_router(neutron, router['router'])
-def get_router(neutron, router_settings=None, router_name=None):
+def get_router(neutron, keystone, router_settings=None, router_name=None,
+ project_name=None):
"""
Returns the first router object (dictionary) found the given the settings
values if not None, else finds the first with the value of the router_name
parameter, else None
- :param neutron: the client
- :param router_settings: the RouterSettings object
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
+ :param router_settings: the RouterConfig object
:param router_name: the name of the network to retrieve
+ :param project_name: the name of the router's project
:return: a SNAPS-OO Router domain object
"""
router_filter = dict()
@@ -280,10 +380,45 @@ def get_router(neutron, router_settings=None, router_name=None):
else:
return None
- routers = neutron.list_routers(**router_filter)
- for routerInst in routers['routers']:
- return Router(**routerInst)
- return None
+ os_routers = neutron.list_routers(**router_filter)
+ for os_router in os_routers['routers']:
+ if project_name:
+ if 'project_id' in os_router.keys():
+ project = keystone_utils.get_project_by_id(
+ keystone, os_router['project_id'])
+ else:
+ project = keystone_utils.get_project_by_id(
+ keystone, os_router['tenant_id'])
+ if project and project.name == project_name:
+ return __map_router(neutron, os_router)
+
+
+def __map_router(neutron, os_router):
+ """
+ Takes an OpenStack router instance and maps it to a SNAPS Router domain
+ object
+ :param neutron: the neutron client
+ :param os_router: the OpenStack Router object
+ :return:
+ """
+ device_ports = neutron.list_ports(
+ **{'device_id': os_router['id']})['ports']
+ port_subnets = list()
+
+ # Order by create date
+ sorted_ports = sorted(
+ device_ports, key=lambda dev_port: dev_port['created_at'])
+
+ for port in sorted_ports:
+ subnets = list()
+ for fixed_ip in port['fixed_ips']:
+ subnet = get_subnet_by_id(neutron, fixed_ip['subnet_id'])
+ if subnet and subnet.network_id == port['network_id']:
+ subnets.append(subnet)
+ port_subnets.append((Port(**port), subnets))
+
+ os_router['port_subnets'] = port_subnets
+ return Router(**os_router)
def add_interface_router(neutron, router, subnet=None, port=None):
@@ -369,10 +504,7 @@ def create_port(neutron, os_creds, port_settings):
logger.info('Creating port for network with name - %s',
port_settings.network_name)
os_port = neutron.create_port(body=json_body)['port']
- return Port(name=os_port['name'], id=os_port['id'],
- ips=os_port['fixed_ips'],
- mac_address=os_port['mac_address'],
- allowed_address_pairs=os_port['allowed_address_pairs'])
+ return Port(**os_port)
def delete_port(neutron, port):
@@ -385,13 +517,16 @@ def delete_port(neutron, port):
neutron.delete_port(port.id)
-def get_port(neutron, port_settings=None, port_name=None):
+def get_port(neutron, keystone, port_settings=None, port_name=None,
+ project_name=None):
"""
Returns the first port object (dictionary) found for the given query
- :param neutron: the client
- :param port_settings: the PortSettings object used for generating the query
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
+ :param port_settings: the PortConfig object used for generating the query
:param port_name: if port_settings is None, this name is the value to place
into the query
+ :param project_name: the associated project name
:return: a SNAPS-OO Port domain object
"""
port_filter = dict()
@@ -405,16 +540,33 @@ def get_port(neutron, port_settings=None, port_name=None):
port_filter['device_id'] = port_settings.device_id
if port_settings.mac_address:
port_filter['mac_address'] = port_settings.mac_address
+ if port_settings.project_name:
+ project_name = port_settings.project_name
if port_settings.network_name:
- network = get_network(neutron,
- network_name=port_settings.network_name)
- port_filter['network_id'] = network.id
+ network = get_network(
+ neutron, keystone, network_name=port_settings.network_name)
+ if network and not (network.shared or network.external):
+ network = get_network(
+ neutron, keystone, network_name=port_settings.network_name,
+ project_name=project_name)
+ if network:
+ port_filter['network_id'] = network.id
elif port_name:
port_filter['name'] = port_name
ports = neutron.list_ports(**port_filter)
for port in ports['ports']:
- return Port(**port)
+ if project_name:
+ if 'project_id' in port.keys():
+ project = keystone_utils.get_project_by_id(
+ keystone, port['project_id'])
+ else:
+ project = keystone_utils.get_project_by_id(
+ keystone, port['tenant_id'])
+ if project and project.name == project_name:
+ return Port(**port)
+ else:
+ return Port(**port)
return None
@@ -467,7 +619,7 @@ def create_security_group(neutron, keystone, sec_grp_settings):
sec_grp_settings.name)
os_group = neutron.create_security_group(
sec_grp_settings.dict_for_neutron(keystone))
- return SecurityGroup(**os_group['security_group'])
+ return __map_os_security_group(neutron, os_group['security_group'])
def delete_security_group(neutron, sec_grp):
@@ -480,30 +632,31 @@ def delete_security_group(neutron, sec_grp):
neutron.delete_security_group(sec_grp.id)
-def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
- project_id=None):
+def get_security_group(neutron, keystone, sec_grp_settings=None,
+ sec_grp_name=None, project_name=None):
"""
Returns the first security group for a given query. The query gets built
from the sec_grp_settings parameter if not None, else only the name of
the security group will be used, else if the query parameters are None then
None will be returned
- :param neutron: the client
- :param sec_grp_settings: an instance of SecurityGroupSettings config object
+ :param neutron: the neutron client
+ :param keystone: the keystone client
+ :param sec_grp_settings: an instance of SecurityGroupConfig object
:param sec_grp_name: the name of security group object to retrieve
- :param project_id: the ID of the project/tentant object that owns the
+ :param project_name: the name of the project/tentant object that owns the
secuity group to retrieve
:return: a SNAPS-OO SecurityGroup domain object or None if not found
"""
sec_grp_filter = dict()
- if project_id:
- sec_grp_filter['tenant_id'] = project_id
if sec_grp_settings:
sec_grp_filter['name'] = sec_grp_settings.name
if sec_grp_settings.description:
sec_grp_filter['description'] = sec_grp_settings.description
+ if sec_grp_settings.project_name:
+ project_name = sec_grp_settings.project_name
elif sec_grp_name:
sec_grp_filter['name'] = sec_grp_name
else:
@@ -511,7 +664,30 @@ def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
groups = neutron.list_security_groups(**sec_grp_filter)
for group in groups['security_groups']:
- return SecurityGroup(**group)
+ if project_name:
+ if 'project_id' in group.keys():
+ project = keystone_utils.get_project_by_id(
+ keystone, group['project_id'])
+ else:
+ project = keystone_utils.get_project_by_id(
+ keystone, group['tenant_id'])
+ if project and project_name == project.name:
+ return __map_os_security_group(neutron, group)
+ else:
+ return __map_os_security_group(neutron, group)
+
+
+def __map_os_security_group(neutron, os_sec_grp):
+ """
+ Creates a SecurityGroup SNAPS domain object from an OpenStack Security
+ Group dict
+ :param neutron: the neutron client for performing rule lookups
+ :param os_sec_grp: the OpenStack Security Group dict object
+ :return: a SecurityGroup object
+ """
+ os_sec_grp['rules'] = get_rules_by_security_group_id(
+ neutron, os_sec_grp['id'])
+ return SecurityGroup(**os_sec_grp)
def get_security_group_by_id(neutron, sec_grp_id):
@@ -526,27 +702,45 @@ def get_security_group_by_id(neutron, sec_grp_id):
groups = neutron.list_security_groups(**{'id': sec_grp_id})
for group in groups['security_groups']:
if group['id'] == sec_grp_id:
- return SecurityGroup(**group)
+ return __map_os_security_group(neutron, group)
return None
-def create_security_group_rule(neutron, sec_grp_rule_settings):
+def list_security_groups(neutron):
+
"""
- Creates a security group object in OpenStack
- :param neutron: the client
+ Lists the available security groups
+ :param neutron: the neutron client
+ """
+ logger.info('Listing the available security groups')
+ sec_groups = []
+ response = neutron.list_security_groups()
+ for sg in response['security_groups']:
+ sec_groups.append(__map_os_security_group(neutron, sg))
+
+ return sec_groups
+
+
+def create_security_group_rule(neutron, keystone, sec_grp_rule_settings,
+ proj_name):
+ """
+ Creates a security group rule in OpenStack
+ :param neutron: the neutron client
+ :param keystone: the keystone client
:param sec_grp_rule_settings: the security group rule settings
+ :param proj_name: the default project name
:return: a SNAPS-OO SecurityGroupRule domain object
"""
logger.info('Creating security group to security group - %s',
sec_grp_rule_settings.sec_grp_name)
os_rule = neutron.create_security_group_rule(
- sec_grp_rule_settings.dict_for_neutron(neutron))
+ sec_grp_rule_settings.dict_for_neutron(neutron, keystone, proj_name))
return SecurityGroupRule(**os_rule['security_group_rule'])
def delete_security_group_rule(neutron, sec_grp_rule):
"""
- Deletes a security group object from OpenStack
+ Deletes a security group rule object from OpenStack
:param neutron: the client
:param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
"""
@@ -561,20 +755,29 @@ def get_rules_by_security_group(neutron, sec_grp):
:param neutron: the client
:param sec_grp: a list of SNAPS SecurityGroupRule domain objects
"""
+ return get_rules_by_security_group_id(neutron, sec_grp.id)
+
+
+def get_rules_by_security_group_id(neutron, sec_grp_id):
+ """
+ Retrieves all of the rules for a given security group by it's ID
+ :param neutron: the client
+ :param sec_grp_id: the ID of the associated security group
+ """
logger.info('Retrieving security group rules associate with the '
- 'security group - %s', sec_grp.name)
+ 'security group with ID - %s', sec_grp_id)
out = list()
rules = neutron.list_security_group_rules(
- **{'security_group_id': sec_grp.id})
+ **{'security_group_id': sec_grp_id})
for rule in rules['security_group_rules']:
- if rule['security_group_id'] == sec_grp.id:
+ if rule['security_group_id'] == sec_grp_id:
out.append(SecurityGroupRule(**rule))
return out
def get_rule_by_id(neutron, sec_grp, rule_id):
"""
- Deletes a security group object from OpenStack
+ Returns a SecurityGroupRule object from OpenStack
:param neutron: the client
:param sec_grp: the SNAPS SecurityGroup domain object
:param rule_id: the rule's ID
@@ -598,52 +801,62 @@ def get_external_networks(neutron):
out = list()
for network in neutron.list_networks(
**{'router:external': True})['networks']:
- out.append(Network(**network))
+ out.append(__map_network(neutron, network))
return out
-def get_floating_ips(neutron, ports=None):
+def get_port_floating_ips(neutron, ports):
"""
- Returns all of the floating IPs
- When ports is not None, FIPs returned must be associated with one of the
- ports in the list and a tuple 2 where the first element being the port's
- ID and the second being the FloatingIp SNAPS-OO domain object.
- When ports is None, all known FloatingIp SNAPS-OO domain objects will be
- returned in a list
+ Returns all of the floating IPs associated with the ports returned in a
+ list of tuples where the port object is in the first position and the
+ floating IP object is in the second
:param neutron: the Neutron client
:param ports: a list of tuple 2 where index 0 is the port name and index 1
is the SNAPS-OO Port object
:return: a list of tuple 2 (port_id, SNAPS FloatingIp) objects when ports
- is not None else a list of Port objects
+ is not None else a list of FloatingIp objects
"""
out = list()
fips = neutron.list_floatingips()
for fip in fips['floatingips']:
- if ports:
- for port_name, port in ports:
- if port and port.id == fip['port_id']:
- out.append((port.id, FloatingIp(**fip)))
- break
- else:
- out.append(FloatingIp(**fip))
+ for port_name, port in ports:
+ if port and port.id == fip['port_id']:
+ out.append((port.id, FloatingIp(**fip)))
+ break
+ return out
+
+def get_floating_ips(neutron):
+ """
+ Returns a list of all of the floating IPs
+ :param neutron: the Neutron client
+ """
+ out = list()
+ fips = neutron.list_floatingips()
+ for fip in fips['floatingips']:
+ out.append(FloatingIp(**fip))
return out
-def create_floating_ip(neutron, ext_net_name):
+def create_floating_ip(neutron, keystone, ext_net_name, port_id=None):
"""
Returns the floating IP object that was created with this call
:param neutron: the Neutron client
+ :param keystone: the Keystone client
:param ext_net_name: the name of the external network on which to apply the
floating IP address
+ :param port_id: the ID of the port to which the floating IP will be
+ associated
:return: the SNAPS FloatingIp object
"""
logger.info('Creating floating ip to external network - ' + ext_net_name)
- ext_net = get_network(neutron, network_name=ext_net_name)
+ ext_net = get_network(neutron, keystone, network_name=ext_net_name)
if ext_net:
- fip = neutron.create_floatingip(
- body={'floatingip':
- {'floating_network_id': ext_net.id}})
+ body = {'floatingip': {'floating_network_id': ext_net.id}}
+ if port_id:
+ body['floatingip']['port_id'] = port_id
+
+ fip = neutron.create_floatingip(body=body)
return FloatingIp(id=fip['floatingip']['id'],
ip=fip['floatingip']['floating_ip_address'])
@@ -677,7 +890,7 @@ def __get_os_floating_ip(neutron, floating_ip):
"""
logger.debug('Attempting to retrieve existing floating ip with IP - %s',
floating_ip.ip)
- fips = neutron.list_floatingips(ip=floating_ip.id)
+ fips = neutron.list_floatingips(floating_ip_address=floating_ip.ip)
for fip in fips['floatingips']:
if fip['id'] == floating_ip.id:
@@ -698,7 +911,7 @@ def delete_floating_ip(neutron, floating_ip):
def get_network_quotas(neutron, project_id):
"""
- Returns a list of all available keypairs
+ Returns a list of NetworkQuotas objects
:param neutron: the neutron client
:param project_id: the project's ID of the quotas to lookup
:return: an object of type NetworkQuotas or None if not found
diff --git a/snaps/openstack/utils/nova_utils.py b/snaps/openstack/utils/nova_utils.py
index 1665fd0..005b56f 100644
--- a/snaps/openstack/utils/nova_utils.py
+++ b/snaps/openstack/utils/nova_utils.py
@@ -15,12 +15,14 @@
import logging
+import enum
import os
+import time
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from novaclient.client import Client
-from novaclient.exceptions import NotFound
+from novaclient.exceptions import NotFound, ClientException
from snaps import file_utils
from snaps.domain.flavor import Flavor
@@ -33,101 +35,119 @@ __author__ = 'spisarski'
logger = logging.getLogger('nova_utils')
+POLL_INTERVAL = 3
+
"""
Utilities for basic OpenStack Nova API calls
"""
-def nova_client(os_creds):
+def nova_client(os_creds, session=None):
"""
Instantiates and returns a client for communications with OpenStack's Nova
server
:param os_creds: The connection credentials to the OpenStack API
+ :param session: the keystone session object (optional)
:return: the client object
"""
logger.debug('Retrieving Nova Client')
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
+
return Client(os_creds.compute_api_version,
- session=keystone_utils.keystone_session(os_creds),
+ session=session,
region_name=os_creds.region_name)
-def create_server(nova, neutron, glance, instance_settings, image_settings,
- keypair_settings=None):
+def create_server(nova, keystone, neutron, glance, instance_config,
+ image_config, project_name, keypair_config=None):
"""
Creates a VM instance
:param nova: the nova client (required)
+ :param keystone: the keystone client for retrieving projects (required)
:param neutron: the neutron client for retrieving ports (required)
:param glance: the glance client (required)
- :param instance_settings: the VM instance settings object (required)
- :param image_settings: the VM's image settings object (required)
- :param keypair_settings: the VM's keypair settings object (optional)
+ :param instance_config: the VMInstConfig object (required)
+ :param image_config: the VM's ImageConfig object (required)
+ :param project_name: the associated project name (required)
+ :param keypair_config: the VM's KeypairConfig object (optional)
:return: a snaps.domain.VmInst object
"""
ports = list()
- for port_setting in instance_settings.port_settings:
- ports.append(neutron_utils.get_port(
- neutron, port_settings=port_setting))
+ for port_setting in instance_config.port_settings:
+ port = neutron_utils.get_port(
+ neutron, keystone, port_settings=port_setting,
+ project_name=project_name)
+ if port:
+ ports.append(port)
+ else:
+ raise Exception('Cannot find port named - ' + port_setting.name)
nics = []
for port in ports:
kv = dict()
kv['port-id'] = port.id
nics.append(kv)
- logger.info('Creating VM with name - ' + instance_settings.name)
+ logger.info('Creating VM with name - ' + instance_config.name)
keypair_name = None
- if keypair_settings:
- keypair_name = keypair_settings.name
+ if keypair_config:
+ keypair_name = keypair_config.name
- flavor = get_flavor_by_name(nova, instance_settings.flavor)
+ flavor = get_flavor_by_name(nova, instance_config.flavor)
if not flavor:
raise NovaException(
- 'Flavor not found with name - %s', instance_settings.flavor)
+ 'Flavor not found with name - %s', instance_config.flavor)
- image = glance_utils.get_image(glance, image_settings=image_settings)
+ image = glance_utils.get_image(glance, image_settings=image_config)
if image:
userdata = None
- if instance_settings.userdata:
- if isinstance(instance_settings.userdata, str):
- userdata = instance_settings.userdata + '\n'
- elif (isinstance(instance_settings.userdata, dict) and
- 'script_file' in instance_settings.userdata):
+ if instance_config.userdata:
+ if isinstance(instance_config.userdata, str):
+ userdata = instance_config.userdata + '\n'
+ elif (isinstance(instance_config.userdata, dict) and
+ 'script_file' in instance_config.userdata):
try:
userdata = file_utils.read_file(
- instance_settings.userdata['script_file'])
+ instance_config.userdata['script_file'])
except Exception as e:
logger.warn('error reading userdata file %s - %s',
- instance_settings.userdata, e)
- args = {'name': instance_settings.name,
+ instance_config.userdata, e)
+ args = {'name': instance_config.name,
'flavor': flavor,
'image': image,
'nics': nics,
'key_name': keypair_name,
'security_groups':
- instance_settings.security_group_names,
+ instance_config.security_group_names,
'userdata': userdata}
- if instance_settings.availability_zone:
- args['availability_zone'] = instance_settings.availability_zone
+ if instance_config.availability_zone:
+ args['availability_zone'] = instance_config.availability_zone
server = nova.servers.create(**args)
- return __map_os_server_obj_to_vm_inst(server)
+ return __map_os_server_obj_to_vm_inst(
+ neutron, keystone, server, project_name)
else:
raise NovaException(
'Cannot create instance, image cannot be located with name %s',
- image_settings.name)
+ image_config.name)
-def get_server(nova, vm_inst_settings=None, server_name=None):
+def get_server(nova, neutron, keystone, vm_inst_settings=None,
+ server_name=None, project_id=None):
"""
Returns a VmInst object for the first server instance found.
:param nova: the Nova client
- :param vm_inst_settings: the VmInstanceSettings object from which to build
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
+ :param vm_inst_settings: the VmInstanceConfig object from which to build
the query if not None
:param server_name: the server with this name to return if vm_inst_settings
is not None
+ :param project_id: the assocaited project ID
:return: a snaps.domain.VmInst object or None if not found
"""
search_opts = dict()
@@ -138,13 +158,39 @@ def get_server(nova, vm_inst_settings=None, server_name=None):
servers = nova.servers.list(search_opts=search_opts)
for server in servers:
- return __map_os_server_obj_to_vm_inst(server)
+ return __map_os_server_obj_to_vm_inst(
+ neutron, keystone, server, project_id)
-def __map_os_server_obj_to_vm_inst(os_server):
+def get_server_connection(nova, vm_inst_settings=None, server_name=None):
+ """
+ Returns a VmInst object for the first server instance found.
+ :param nova: the Nova client
+ :param vm_inst_settings: the VmInstanceConfig object from which to build
+ the query if not None
+ :param server_name: the server with this name to return if vm_inst_settings
+ is not None
+ :return: a snaps.domain.VmInst object or None if not found
+ """
+ search_opts = dict()
+ if vm_inst_settings:
+ search_opts['name'] = vm_inst_settings.name
+ elif server_name:
+ search_opts['name'] = server_name
+
+ servers = nova.servers.list(search_opts=search_opts)
+ for server in servers:
+ return server.links[0]
+
+
+def __map_os_server_obj_to_vm_inst(neutron, keystone, os_server,
+ project_name=None):
"""
Returns a VmInst object for an OpenStack Server object
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
:param os_server: the OpenStack server object
+ :param project_name: the associated project name
:return: an equivalent SNAPS-OO VmInst domain object
"""
sec_grp_names = list()
@@ -154,11 +200,32 @@ def __map_os_server_obj_to_vm_inst(os_server):
if sec_group.get('name'):
sec_grp_names.append(sec_group.get('name'))
+ out_ports = list()
+ if len(os_server.networks) > 0:
+ for net_name, ips in os_server.networks.items():
+ network = neutron_utils.get_network(
+ neutron, keystone, network_name=net_name,
+ project_name=project_name)
+ if network:
+ ports = neutron_utils.get_ports(neutron, network, ips)
+ for port in ports:
+ out_ports.append(port)
+ else:
+ raise NovaException(
+ 'Unable to locate network in project {} with '
+ 'name {}'.format(project_name, net_name))
+
+ volumes = None
+ if hasattr(os_server, 'os-extended-volumes:volumes_attached'):
+ volumes = getattr(os_server, 'os-extended-volumes:volumes_attached')
+
return VmInst(
name=os_server.name, inst_id=os_server.id,
image_id=os_server.image['id'], flavor_id=os_server.flavor['id'],
- networks=os_server.networks, keypair_name=os_server.key_name,
- sec_grp_names=sec_grp_names)
+ ports=out_ports, keypair_name=os_server.key_name,
+ sec_grp_names=sec_grp_names, volume_ids=volumes,
+ compute_host=os_server._info.get('OS-EXT-SRV-ATTR:host'),
+ availability_zone=os_server._info.get('OS-EXT-AZ:availability_zone'))
def __get_latest_server_os_object(nova, server):
@@ -207,26 +274,35 @@ def get_server_console_output(nova, server):
return None
-def get_latest_server_object(nova, server):
+def get_latest_server_object(nova, neutron, keystone, server, project_name):
"""
Returns a server with a given id
:param nova: the Nova client
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
:param server: the old server object
+ :param project_name: the associated project name
:return: the list of servers or None if not found
"""
server = __get_latest_server_os_object(nova, server)
- return __map_os_server_obj_to_vm_inst(server)
+ return __map_os_server_obj_to_vm_inst(
+ neutron, keystone, server, project_name)
-def get_server_object_by_id(nova, server_id):
+def get_server_object_by_id(nova, neutron, keystone, server_id,
+ project_name=None):
"""
Returns a server with a given id
:param nova: the Nova client
+ :param neutron: the Neutron client
+ :param keystone: the Keystone client
:param server_id: the server's id
+ :param project_name: the associated project name
:return: an SNAPS-OO VmInst object or None if not found
"""
server = __get_latest_server_os_object_by_id(nova, server_id)
- return __map_os_server_obj_to_vm_inst(server)
+ return __map_os_server_obj_to_vm_inst(
+ neutron, keystone, server, project_name)
def get_server_security_group_names(nova, server):
@@ -238,8 +314,9 @@ def get_server_security_group_names(nova, server):
"""
out = list()
os_vm_inst = __get_latest_server_os_object(nova, server)
- for sec_grp_dict in os_vm_inst.security_groups:
- out.append(sec_grp_dict['name'])
+ if hasattr(os_vm_inst, 'security_groups'):
+ for sec_grp_dict in os_vm_inst.security_groups:
+ out.append(sec_grp_dict['name'])
return out
@@ -256,6 +333,22 @@ def get_server_info(nova, server):
return None
+def reboot_server(nova, server, reboot_type=None):
+ """
+ Returns a dictionary of a VMs info as returned by OpenStack
+ :param nova: the Nova client
+ :param server: the old server object
+ :param reboot_type: Acceptable values 'SOFT', 'HARD'
+ (api uses SOFT as the default)
+ :return: a dict of the info if VM exists else None
+ """
+ vm = __get_latest_server_os_object(nova, server)
+ if vm:
+ vm.reboot(reboot_type=reboot_type.value)
+ else:
+ raise ServerNotFoundError('Cannot locate server')
+
+
def create_keys(key_size=2048):
"""
Generates public and private keys
@@ -393,6 +486,18 @@ def get_keypair_by_name(nova, name):
return None
+def get_keypair_by_id(nova, kp_id):
+ """
+ Returns a list of all available keypairs
+ :param nova: the Nova client
+ :param kp_id: the ID of the keypair to return
+ :return: the keypair object
+ """
+ keypair = nova.keypairs.get(kp_id)
+ return Keypair(name=keypair.name, kp_id=keypair.id,
+ public_key=keypair.public_key)
+
+
def delete_keypair(nova, key):
"""
Deletes a keypair object from OpenStack
@@ -421,6 +526,21 @@ def get_availability_zone_hosts(nova, zone_name='nova'):
return out
+def get_hypervisor_hosts(nova):
+ """
+ Returns the host names of all nova nodes with active hypervisors
+ :param nova: the Nova client
+ :return: a list of hypervisor host names
+ """
+ out = list()
+ hypervisors = nova.hypervisors.list()
+ for hypervisor in hypervisors:
+ if hypervisor.state == "up":
+ out.append(hypervisor.hypervisor_hostname)
+
+ return out
+
+
def delete_vm_instance(nova, vm_inst):
"""
Deletes a VM instance
@@ -567,7 +687,18 @@ def add_security_group(nova, vm, security_group_name):
:param vm: the OpenStack server object (VM) to alter
:param security_group_name: the name of the security group to add
"""
- nova.servers.add_security_group(str(vm.id), security_group_name)
+ try:
+ nova.servers.add_security_group(str(vm.id), security_group_name)
+ except ClientException as e:
+ sec_grp_names = get_server_security_group_names(nova, vm)
+ if security_group_name in sec_grp_names:
+ logger.warn('Security group [%s] already added to VM [%s]',
+ security_group_name, vm.name)
+ return
+
+ logger.error('Unexpected error while adding security group [%s] - %s',
+ security_group_name, e)
+ raise
def remove_security_group(nova, vm, security_group):
@@ -580,18 +711,6 @@ def remove_security_group(nova, vm, security_group):
nova.servers.remove_security_group(str(vm.id), security_group.name)
-def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
- """
- Adds a floating IP to a server instance
- :param nova: the nova client
- :param vm: VmInst domain object
- :param floating_ip: FloatingIp domain object
- :param ip_addr: the IP to which to bind the floating IP to
- """
- vm = __get_latest_server_os_object(nova, vm)
- vm.add_floating_ip(floating_ip.ip, ip_addr)
-
-
def get_compute_quotas(nova, project_id):
"""
Returns a list of all available keypairs
@@ -618,7 +737,8 @@ def update_quotas(nova, project_id, compute_quotas):
update_values['cores'] = compute_quotas.cores
update_values['instances'] = compute_quotas.instances
update_values['injected_files'] = compute_quotas.injected_files
- update_values['injected_file_content_bytes'] = compute_quotas.injected_file_content_bytes
+ update_values['injected_file_content_bytes'] = (
+ compute_quotas.injected_file_content_bytes)
update_values['ram'] = compute_quotas.ram
update_values['fixed_ips'] = compute_quotas.fixed_ips
update_values['key_pairs'] = compute_quotas.key_pairs
@@ -626,7 +746,91 @@ def update_quotas(nova, project_id, compute_quotas):
return nova.quotas.update(project_id, **update_values)
+def attach_volume(nova, neutron, keystone, server, volume, project_name,
+ timeout=120):
+ """
+ Attaches a volume to a server. When the timeout parameter is used, a VmInst
+ object with the proper volume updates is returned unless it has not been
+ updated in the allotted amount of time then an Exception will be raised.
+ :param nova: the nova client
+ :param neutron: the neutron client
+ :param keystone: the neutron client
+ :param server: the VMInst domain object
+ :param volume: the Volume domain object
+ :param project_name: the associated project name
+ :param timeout: denotes the amount of time to block to determine if the
+ has been properly attached.
+ :return: updated VmInst object
+ """
+ nova.volumes.create_server_volume(server.id, volume.id)
+
+ start_time = time.time()
+ while time.time() < start_time + timeout:
+ vm = get_server_object_by_id(
+ nova, neutron, keystone, server.id, project_name)
+ for vol_dict in vm.volume_ids:
+ if volume.id == vol_dict['id']:
+ return vm
+ time.sleep(POLL_INTERVAL)
+
+ raise NovaException(
+ 'Attach failed on volume - {} and server - {}'.format(
+ volume.id, server.id))
+
+
+def detach_volume(nova, neutron, keystone, server, volume, project_name,
+ timeout=120):
+ """
+ Detaches a volume to a server. When the timeout parameter is used, a VmInst
+ object with the proper volume updates is returned unless it has not been
+ updated in the allotted amount of time then an Exception will be raised.
+ :param nova: the nova client
+ :param neutron: the neutron client
+ :param keystone: the keystone client
+ :param server: the VMInst domain object
+ :param volume: the Volume domain object
+ :param project_name: the associated project name
+ :param timeout: denotes the amount of time to block to determine if the
+ has been properly detached.
+ :return: updated VmInst object
+ """
+ nova.volumes.delete_server_volume(server.id, volume.id)
+
+ start_time = time.time()
+ while time.time() < start_time + timeout:
+ vm = get_server_object_by_id(
+ nova, neutron, keystone, server.id, project_name)
+ if len(vm.volume_ids) == 0:
+ return vm
+ else:
+ ids = list()
+ for vol_dict in vm.volume_ids:
+ ids.append(vol_dict['id'])
+ if volume.id not in ids:
+ return vm
+ time.sleep(POLL_INTERVAL)
+
+ raise NovaException(
+ 'Detach failed on volume - {} server - {}'.format(
+ volume.id, server.id))
+
+
+class RebootType(enum.Enum):
+ """
+ A rule's direction
+ """
+ soft = 'SOFT'
+ hard = 'HARD'
+
+
class NovaException(Exception):
"""
Exception when calls to the Keystone client cannot be served properly
"""
+
+
+class ServerNotFoundError(Exception):
+ """
+ Exception when operations to a VM/Server is requested and the OpenStack
+ Server instance cannot be located
+ """
diff --git a/snaps/openstack/utils/settings_utils.py b/snaps/openstack/utils/settings_utils.py
index 7f00075..e43f8f5 100644
--- a/snaps/openstack/utils/settings_utils.py
+++ b/snaps/openstack/utils/settings_utils.py
@@ -15,30 +15,59 @@
import uuid
from snaps import file_utils
-from snaps.openstack.create_instance import (
- VmInstanceSettings, FloatingIpSettings)
-from snaps.openstack.create_keypairs import KeypairSettings
-from snaps.openstack.create_network import (
- PortSettings, SubnetSettings, NetworkSettings)
+from snaps.config.flavor import FlavorConfig
+from snaps.config.keypair import KeypairConfig
+from snaps.config.network import SubnetConfig, PortConfig, NetworkConfig
+from snaps.config.router import RouterConfig
+from snaps.config.security_group import (
+ SecurityGroupRuleConfig, SecurityGroupConfig)
+from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
+from snaps.config.volume import VolumeConfig
+from snaps.config.volume_type import (
+ ControlLocation, VolumeTypeEncryptionConfig, VolumeTypeConfig)
from snaps.openstack.utils import (
neutron_utils, nova_utils, heat_utils, glance_utils)
-def create_network_settings(neutron, network):
+def create_network_config(neutron, network):
"""
- Returns a NetworkSettings object
+ Returns a NetworkConfig object
:param neutron: the neutron client
:param network: a SNAPS-OO Network domain object
:return:
"""
- return NetworkSettings(
+ return NetworkConfig(
name=network.name, network_type=network.type,
- subnet_settings=create_subnet_settings(neutron, network))
+ subnet_settings=create_subnet_config(neutron, network))
-def create_subnet_settings(neutron, network):
+def create_security_group_config(neutron, security_group):
"""
- Returns a list of SubnetSettings objects for a given network
+ Returns a SecurityGroupConfig object
+ :param neutron: the neutron client
+ :param security_group: a SNAPS-OO SecurityGroup domain object
+ :return:
+ """
+ rules = neutron_utils.get_rules_by_security_group(neutron, security_group)
+
+ rule_settings = list()
+ for rule in rules:
+ rule_settings.append(SecurityGroupRuleConfig(
+ sec_grp_name=security_group.name, description=rule.description,
+ direction=rule.direction, ethertype=rule.ethertype,
+ port_range_min=rule.port_range_min,
+ port_range_max=rule.port_range_max, protocol=rule.protocol,
+ remote_group_id=rule.remote_group_id,
+ remote_ip_prefix=rule.remote_ip_prefix))
+
+ return SecurityGroupConfig(
+ name=security_group.name, description=security_group.description,
+ rule_settings=rule_settings)
+
+
+def create_subnet_config(neutron, network):
+ """
+ Returns a list of SubnetConfig objects for a given network
:param neutron: the OpenStack neutron client
:param network: the SNAPS-OO Network domain object
:return: a list
@@ -59,16 +88,152 @@ def create_subnet_settings(neutron, network):
kwargs['host_routes'] = subnet.host_routes
kwargs['ipv6_ra_mode'] = subnet.ipv6_ra_mode
kwargs['ipv6_address_mode'] = subnet.ipv6_address_mode
- out.append(SubnetSettings(**kwargs))
+ out.append(SubnetConfig(**kwargs))
return out
-def create_vm_inst_settings(nova, neutron, server):
+def create_router_config(neutron, router):
+ """
+ Returns a RouterConfig object
+ :param neutron: the neutron client
+ :param router: a SNAPS-OO Router domain object
+ :return:
+ """
+ ext_net_name = None
+
+ if router.external_network_id:
+ network = neutron_utils.get_network_by_id(
+ neutron, router.external_network_id)
+ if network:
+ ext_net_name = network.name
+
+ out_ports = list()
+ if router.port_subnets:
+ for port, subnets in router.port_subnets:
+ network = neutron_utils.get_network_by_id(
+ neutron, port.network_id)
+
+ ip_addrs = list()
+ if network and router.external_fixed_ips:
+ for ext_fixed_ips in router.external_fixed_ips:
+ for subnet in subnets:
+ if ext_fixed_ips['subnet_id'] == subnet.id:
+ ip_addrs.append(ext_fixed_ips['ip_address'])
+ else:
+ for ip in port.ips:
+ ip_addrs.append(ip['ip_address'])
+
+ ports = neutron_utils.get_ports(neutron, network, ip_addrs)
+ for out_port in ports:
+ out_ports.append(out_port)
+
+ port_settings = __create_port_configs(neutron, out_ports)
+
+ filtered_settings = list()
+ for port_setting in port_settings:
+ if port_setting.network_name != ext_net_name:
+ filtered_settings.append(port_setting)
+
+ return RouterConfig(
+ name=router.name, external_gateway=ext_net_name,
+ admin_state_up=router.admin_state_up,
+ port_settings=filtered_settings)
+
+
+def create_volume_config(volume):
+ """
+ Returns a VolumeConfig object
+ :param volume: a SNAPS-OO Volume object
+ """
+
+ return VolumeConfig(
+ name=volume.name, description=volume.description,
+ size=volume.size, type_name=volume.type,
+ availability_zone=volume.availability_zone,
+ multi_attach=volume.multi_attach)
+
+
+def create_volume_type_config(volume_type):
"""
- Returns a NetworkSettings object
+ Returns a VolumeTypeConfig object
+ :param volume_type: a SNAPS-OO VolumeType object
+ """
+
+ control = None
+ if volume_type.encryption:
+ if (volume_type.encryption.control_location
+ == ControlLocation.front_end.value):
+ control = ControlLocation.front_end
+ else:
+ control = ControlLocation.back_end
+
+ if volume_type and volume_type.encryption:
+ encrypt_settings = VolumeTypeEncryptionConfig(
+ name=volume_type.encryption.__class__,
+ provider_class=volume_type.encryption.provider,
+ control_location=control,
+ cipher=volume_type.encryption.cipher,
+ key_size=volume_type.encryption.key_size)
+ else:
+ encrypt_settings = None
+
+ qos_spec_name = None
+ if volume_type.qos_spec:
+ qos_spec_name = volume_type.qos_spec.name
+
+ return VolumeTypeConfig(
+ name=volume_type.name, encryption=encrypt_settings,
+ qos_spec_name=qos_spec_name, public=volume_type.public)
+
+
+def create_flavor_config(flavor):
+ """
+ Returns a FlavorConfig object
+ :param flavor: a FlavorConfig object
+ """
+ return FlavorConfig(
+ name=flavor.name, flavor_id=flavor.id, ram=flavor.ram,
+ disk=flavor.disk, vcpus=flavor.vcpus, ephemeral=flavor.ephemeral,
+ swap=flavor.swap, rxtx_factor=flavor.rxtx_factor,
+ is_public=flavor.is_public)
+
+
+def create_keypair_config(heat_cli, stack, keypair, pk_output_key):
+ """
+ Instantiates a KeypairConfig object from a Keypair domain objects
+ :param heat_cli: the heat client
+ :param stack: the Stack domain object
+ :param keypair: the Keypair SNAPS domain object
+ :param pk_output_key: the key to the heat template's outputs for retrieval
+ of the private key file
+ :return: a KeypairConfig object
+ """
+ if pk_output_key:
+ outputs = heat_utils.get_outputs(heat_cli, stack)
+ for output in outputs:
+ if output.key == pk_output_key:
+ # Save to file
+ guid = uuid.uuid4()
+ key_file = file_utils.save_string_to_file(
+ output.value, str(guid), 0o400)
+
+ # Use outputs, file and resources for the KeypairConfig
+ return KeypairConfig(
+ name=keypair.name, private_filepath=key_file.name)
+
+ return KeypairConfig(name=keypair.name)
+
+
+def create_vm_inst_config(nova, keystone, neutron, server, project_name):
+ """
+ Returns a VmInstanceConfig object
+ note: if the server instance is not active, the PortSettings objects will
+ not be generated resulting in an invalid configuration
:param nova: the nova client
+ :param keystone: the keystone client
:param neutron: the neutron client
:param server: a SNAPS-OO VmInst domain object
+ :param project_name: the associated project name
:return:
"""
@@ -77,29 +242,34 @@ def create_vm_inst_settings(nova, neutron, server):
kwargs = dict()
kwargs['name'] = server.name
kwargs['flavor'] = flavor_name
- kwargs['port_settings'] = __create_port_settings(
- neutron, server.networks)
+
+ kwargs['port_settings'] = __create_port_configs(neutron, server.ports)
kwargs['security_group_names'] = server.sec_grp_names
- kwargs['floating_ip_settings'] = __create_floatingip_settings(
- neutron, kwargs['port_settings'])
+ kwargs['floating_ip_settings'] = __create_floatingip_config(
+ neutron, keystone, kwargs['port_settings'], project_name)
- return VmInstanceSettings(**kwargs)
+ return VmInstanceConfig(**kwargs)
-def __create_port_settings(neutron, networks):
+def __create_port_configs(neutron, ports):
"""
- Returns a list of port settings based on the networks parameter
+ Returns a list of PortConfig objects based on the networks parameter
:param neutron: the neutron client
- :param networks: a dict where the key is the network name and the value
- is a list of IP addresses
+ :param ports: a list of SNAPS-OO Port domain objects
:return:
"""
out = list()
- for net_name, ips in networks.items():
- network = neutron_utils.get_network(neutron, network_name=net_name)
- ports = neutron_utils.get_ports(neutron, network, ips)
- for port in ports:
+ for port in ports:
+ if port.device_owner != 'network:dhcp':
+ ip_addrs = list()
+ for ip_dict in port.ips:
+ subnet = neutron_utils.get_subnet_by_id(
+ neutron, ip_dict['subnet_id'])
+ ip_addrs.append({'subnet_name': subnet.name,
+ 'ip': ip_dict['ip_address']})
+
+ network = neutron_utils.get_network_by_id(neutron, port.network_id)
kwargs = dict()
if port.name:
kwargs['name'] = port.name
@@ -107,18 +277,20 @@ def __create_port_settings(neutron, networks):
kwargs['mac_address'] = port.mac_address
kwargs['allowed_address_pairs'] = port.allowed_address_pairs
kwargs['admin_state_up'] = port.admin_state_up
- out.append(PortSettings(**kwargs))
+ kwargs['ip_addrs'] = ip_addrs
+ out.append(PortConfig(**kwargs))
return out
-def __create_floatingip_settings(neutron, port_settings):
+def __create_floatingip_config(neutron, keystone, port_settings, project_name):
"""
- Returns a list of FloatingIPSettings objects as they pertain to an
+ Returns a list of FloatingIpConfig objects as they pertain to an
existing deployed server instance
:param neutron: the neutron client
- :param port_settings: list of SNAPS-OO PortSettings objects
- :return: a list of FloatingIPSettings objects or an empty list if no
+ :param keystone: the keystone client
+ :param port_settings: list of SNAPS-OO PortConfig objects
+ :return: a list of FloatingIpConfig objects or an empty list if no
floating IPs have been created
"""
base_fip_name = 'fip-'
@@ -127,10 +299,11 @@ def __create_floatingip_settings(neutron, port_settings):
fip_ports = list()
for port_setting in port_settings:
- setting_port = neutron_utils.get_port(neutron, port_setting)
+ setting_port = neutron_utils.get_port(
+ neutron, keystone, port_setting, project_name=project_name)
if setting_port:
network = neutron_utils.get_network(
- neutron, network_name=port_setting.network_name)
+ neutron, keystone, network_name=port_setting.network_name)
network_ports = neutron_utils.get_ports(neutron, network)
if network_ports:
for setting_port in network_ports:
@@ -138,7 +311,7 @@ def __create_floatingip_settings(neutron, port_settings):
fip_ports.append((port_setting.name, setting_port))
break
- floating_ips = neutron_utils.get_floating_ips(neutron, fip_ports)
+ floating_ips = neutron_utils.get_port_floating_ips(neutron, fip_ports)
for port_id, floating_ip in floating_ips:
router = neutron_utils.get_router_by_id(neutron, floating_ip.router_id)
@@ -160,21 +333,21 @@ def __create_floatingip_settings(neutron, port_settings):
if subnet:
kwargs['subnet_name'] = subnet.name
- out.append(FloatingIpSettings(**kwargs))
+ out.append(FloatingIpConfig(**kwargs))
fip_ctr += 1
return out
-def determine_image_settings(glance, server, image_settings):
+def determine_image_config(glance, server, image_settings):
"""
- Returns a ImageSettings object from the list that matches the name in one
+ Returns a ImageConfig object from the list that matches the name in one
of the image_settings parameter
:param glance: the glance client
:param server: a SNAPS-OO VmInst domain object
- :param image_settings: list of ImageSettings objects
- :return: ImageSettings or None
+ :param image_settings: list of ImageConfig objects
+ :return: ImageConfig or None
"""
if image_settings:
for image_setting in image_settings:
@@ -183,20 +356,20 @@ def determine_image_settings(glance, server, image_settings):
return image_setting
-def determine_keypair_settings(heat_cli, stack, server, keypair_settings=None,
- priv_key_key=None):
+def determine_keypair_config(heat_cli, stack, server, keypair_settings=None,
+ priv_key_key=None):
"""
- Returns a KeypairSettings object from the list that matches the
+ Returns a KeypairConfig object from the list that matches the
server.keypair_name value in the keypair_settings parameter if not None,
else if the output_key is not None, the output's value when contains the
string 'BEGIN RSA PRIVATE KEY', this value will be stored into a file and
- encoded into the KeypairSettings object returned
+ encoded into the KeypairConfig object returned
:param heat_cli: the OpenStack heat client
:param stack: a SNAPS-OO Stack domain object
:param server: a SNAPS-OO VmInst domain object
- :param keypair_settings: list of KeypairSettings objects
+ :param keypair_settings: list of KeypairConfig objects
:param priv_key_key: the stack options that holds the private key value
- :return: KeypairSettings or None
+ :return: KeypairConfig or None
"""
# Existing keypair being used by Heat Template
if keypair_settings:
@@ -214,6 +387,6 @@ def determine_keypair_settings(heat_cli, stack, server, keypair_settings=None,
key_file = file_utils.save_string_to_file(
output.value, str(guid), 0o400)
- # Use outputs, file and resources for the KeypairSettings
- return KeypairSettings(
+ # Use outputs, file and resources for the KeypairConfig
+ return KeypairConfig(
name=server.keypair_name, private_filepath=key_file.name)
diff --git a/snaps/openstack/utils/tests/cinder_utils_tests.py b/snaps/openstack/utils/tests/cinder_utils_tests.py
index a45167e..073d574 100644
--- a/snaps/openstack/utils/tests/cinder_utils_tests.py
+++ b/snaps/openstack/utils/tests/cinder_utils_tests.py
@@ -15,14 +15,18 @@
import logging
import uuid
+import time
from cinderclient.exceptions import NotFound, BadRequest
-from snaps.openstack.create_qos import QoSSettings, Consumer
-from snaps.openstack.create_volume_type import (
- VolumeTypeSettings, VolumeTypeEncryptionSettings, ControlLocation)
+from snaps.config.volume import VolumeConfig
+from snaps.config.volume_type import (
+ VolumeTypeConfig, ControlLocation, VolumeTypeEncryptionConfig)
+from snaps.config.qos import Consumer, QoSConfig
+from snaps.openstack import create_volume
+from snaps.openstack.create_qos import Consumer
from snaps.openstack.tests import validation_utils
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
-from snaps.openstack.utils import cinder_utils
+from snaps.openstack.utils import cinder_utils, keystone_utils
__author__ = 'spisarski'
@@ -39,7 +43,7 @@ class CinderSmokeTests(OSComponentTestCase):
"""
Tests to ensure that the proper credentials can connect.
"""
- cinder = cinder_utils.cinder_client(self.os_creds)
+ cinder = cinder_utils.cinder_client(self.os_creds, self.os_session)
volumes = cinder.volumes.list()
self.assertIsNotNone(volumes)
self.assertTrue(isinstance(volumes, list))
@@ -57,6 +61,120 @@ class CinderSmokeTests(OSComponentTestCase):
cinder.volumes.list()
+class CinderUtilsVolumeTests(OSComponentTestCase):
+ """
+ Test for the CreateVolume class defined in create_volume.py
+ """
+
+ def setUp(self):
+ """
+ Instantiates the CreateVolume object that is responsible for
+ downloading and creating an OS volume file within OpenStack
+ """
+ guid = uuid.uuid4()
+ self.volume_name = self.__class__.__name__ + '-' + str(guid)
+ self.volume = None
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the remote OpenStack objects
+ """
+ if self.volume:
+ try:
+ cinder_utils.delete_volume(self.cinder, self.volume)
+ except NotFound:
+ pass
+
+ self.assertTrue(volume_deleted(self.cinder, self.volume))
+
+ def test_create_simple_volume(self):
+ """
+ Tests the cinder_utils.create_volume()
+ """
+ volume_settings = VolumeConfig(name=self.volume_name)
+ self.volume = cinder_utils.create_volume(
+ self.cinder, self.keystone, volume_settings)
+ self.assertIsNotNone(self.volume)
+ self.assertEqual(self.volume_name, self.volume.name)
+
+ self.assertTrue(volume_active(self.cinder, self.volume))
+
+ volume = cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings=volume_settings,
+ project_name=self.os_creds.project_name)
+ self.assertIsNotNone(volume)
+ validation_utils.objects_equivalent(self.volume, volume)
+
+ def test_create_delete_volume(self):
+ """
+ Tests the cinder_utils.create_volume()
+ """
+ volume_settings = VolumeConfig(name=self.volume_name)
+ self.volume = cinder_utils.create_volume(
+ self.cinder, self.keystone, volume_settings)
+ self.assertIsNotNone(self.volume)
+ self.assertEqual(self.volume_name, self.volume.name)
+
+ self.assertTrue(volume_active(self.cinder, self.volume))
+
+ volume = cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings=volume_settings,
+ project_name=self.os_creds.project_name)
+ self.assertIsNotNone(volume)
+ validation_utils.objects_equivalent(self.volume, volume)
+
+ cinder_utils.delete_volume(self.cinder, self.volume)
+ self.assertTrue(volume_deleted(self.cinder, self.volume))
+ self.assertIsNone(
+ cinder_utils.get_volume(
+ self.cinder, self.keystone, volume_settings,
+ project_name=self.os_creds.project_name))
+
+
+def volume_active(cinder, volume):
+ """
+ Returns true if volume becomes active
+ :param cinder:
+ :param volume:
+ :return:
+ """
+ end_time = time.time() + create_volume.VOLUME_ACTIVE_TIMEOUT
+ while time.time() < end_time:
+ status = cinder_utils.get_volume_status(cinder, volume)
+ if status == create_volume.STATUS_ACTIVE:
+ return True
+ elif status == create_volume.STATUS_FAILED:
+ return False
+ time.sleep(3)
+
+ return False
+
+
+def volume_deleted(cinder, volume):
+ """
+ Returns true if volume becomes active
+ :param cinder:
+ :param volume:
+ :return:
+ """
+ end_time = time.time() + create_volume.VOLUME_ACTIVE_TIMEOUT
+ while time.time() < end_time:
+ try:
+ status = cinder_utils.get_volume_status(cinder, volume)
+ if status == create_volume.STATUS_DELETED:
+ return True
+ except NotFound:
+ return True
+
+ time.sleep(3)
+
+ return False
+
+
class CinderUtilsQoSTests(OSComponentTestCase):
"""
Test for the CreateQos class defined in create_qos.py
@@ -70,7 +188,8 @@ class CinderUtilsQoSTests(OSComponentTestCase):
self.qos_name = self.__class__.__name__ + '-' + str(guid)
self.specs = {'foo': 'bar '}
self.qos = None
- self.cinder = cinder_utils.cinder_client(self.os_creds)
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
@@ -82,12 +201,14 @@ class CinderUtilsQoSTests(OSComponentTestCase):
except NotFound:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_qos_both(self):
"""
Tests the cinder_utils.create_qos()
"""
- qos_settings = QoSSettings(name=self.qos_name, specs=self.specs,
- consumer=Consumer.both)
+ qos_settings = QoSConfig(
+ name=self.qos_name, specs=self.specs, consumer=Consumer.both)
self.qos = cinder_utils.create_qos(self.cinder, qos_settings)
self.assertIsNotNone(self.qos)
@@ -103,8 +224,8 @@ class CinderUtilsQoSTests(OSComponentTestCase):
"""
Tests the cinder_utils.create_qos()
"""
- qos_settings = QoSSettings(name=self.qos_name, specs=self.specs,
- consumer=Consumer.front_end)
+ qos_settings = QoSConfig(
+ name=self.qos_name, specs=self.specs, consumer=Consumer.front_end)
self.qos = cinder_utils.create_qos(self.cinder, qos_settings)
self.assertIsNotNone(self.qos)
@@ -120,8 +241,8 @@ class CinderUtilsQoSTests(OSComponentTestCase):
"""
Tests the cinder_utils.create_qos()
"""
- qos_settings = QoSSettings(name=self.qos_name, specs=self.specs,
- consumer=Consumer.back_end)
+ qos_settings = QoSConfig(
+ name=self.qos_name, specs=self.specs, consumer=Consumer.back_end)
self.qos = cinder_utils.create_qos(self.cinder, qos_settings)
self.assertIsNotNone(self.qos)
@@ -137,7 +258,7 @@ class CinderUtilsQoSTests(OSComponentTestCase):
"""
Tests the cinder_utils.create_qos()
"""
- qos_settings = QoSSettings(name=self.qos_name, consumer=Consumer.both)
+ qos_settings = QoSConfig(name=self.qos_name, consumer=Consumer.both)
self.qos = cinder_utils.create_qos(self.cinder, qos_settings)
self.assertIsNotNone(self.qos)
self.assertEqual(self.qos_name, self.qos.name)
@@ -165,9 +286,10 @@ class CinderUtilsSimpleVolumeTypeTests(OSComponentTestCase):
"""
guid = uuid.uuid4()
volume_type_name = self.__class__.__name__ + '-' + str(guid)
- self.volume_type_settings = VolumeTypeSettings(name=volume_type_name)
+ self.volume_type_settings = VolumeTypeConfig(name=volume_type_name)
self.volume_type = None
- self.cinder = cinder_utils.cinder_client(self.os_creds)
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
@@ -179,6 +301,8 @@ class CinderUtilsSimpleVolumeTypeTests(OSComponentTestCase):
except NotFound:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_simple_volume_type(self):
"""
Tests the cinder_utils.create_volume_type(), get_volume_type(), and
@@ -235,11 +359,12 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase):
self.encryption_name = self.__class__.__name__ + '-' + str(guid)
self.encryption = None
- self.cinder = cinder_utils.cinder_client(self.os_creds)
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
volume_type_name = self.__class__.__name__ + '-' + str(guid) + '-type'
self.volume_type = cinder_utils.create_volume_type(
- self.cinder, VolumeTypeSettings(name=volume_type_name))
+ self.cinder, VolumeTypeConfig(name=volume_type_name))
def tearDown(self):
"""
@@ -258,12 +383,14 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase):
except NotFound:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_simple_encryption(self):
"""
Tests the cinder_utils.create_volume_encryption(),
get_volume_encryption(), and get_volume_encryption_by_id()
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name=self.encryption_name, provider_class='foo',
control_location=ControlLocation.front_end)
self.encryption = cinder_utils.create_volume_encryption(
@@ -281,7 +408,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase):
"""
Primarily tests the cinder_utils.delete_volume_type_encryption()
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name=self.encryption_name, provider_class='LuksEncryptor',
control_location=ControlLocation.back_end)
self.encryption = cinder_utils.create_volume_encryption(
@@ -307,7 +434,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase):
Tests the cinder_utils.create_volume_encryption() with all valid
settings
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name=self.encryption_name, provider_class='foo',
cipher='bar', control_location=ControlLocation.back_end,
key_size=1)
@@ -329,7 +456,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase):
Tests the cinder_utils.create_volume_encryption() raises an exception
when the provider class does not exist
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name=self.encryption_name, provider_class='foo',
cipher='bar', control_location=ControlLocation.back_end,
key_size=-1)
@@ -352,9 +479,10 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase):
self.qos_name = self.__class__.__name__ + '-' + str(guid) + '-qos'
self.vol_type_name = self.__class__.__name__ + '-' + str(guid)
self.specs = {'foo': 'bar'}
- self.cinder = cinder_utils.cinder_client(self.os_creds)
- qos_settings = QoSSettings(name=self.qos_name, specs=self.specs,
- consumer=Consumer.both)
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+ qos_settings = QoSConfig(
+ name=self.qos_name, specs=self.specs, consumer=Consumer.both)
self.qos = cinder_utils.create_qos(self.cinder, qos_settings)
self.volume_type = None
@@ -380,15 +508,17 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase):
except NotFound:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_with_encryption(self):
"""
Tests the cinder_utils.create_volume_type() where encryption has been
configured
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name='foo', provider_class='bar',
control_location=ControlLocation.back_end)
- volume_type_settings = VolumeTypeSettings(
+ volume_type_settings = VolumeTypeConfig(
name=self.vol_type_name, encryption=encryption_settings)
self.volume_type = cinder_utils.create_volume_type(
self.cinder, volume_type_settings)
@@ -404,7 +534,7 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase):
"""
Tests the cinder_utils.create_volume_type() with an associated QoS Spec
"""
- volume_type_settings = VolumeTypeSettings(
+ volume_type_settings = VolumeTypeConfig(
name=self.vol_type_name, qos_spec_name=self.qos_name)
self.volume_type = cinder_utils.create_volume_type(
self.cinder, volume_type_settings)
@@ -419,7 +549,7 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase):
Tests the cinder_utils.create_volume_type() when the QoS Spec name
does not exist
"""
- volume_type_settings = VolumeTypeSettings(
+ volume_type_settings = VolumeTypeConfig(
name=self.vol_type_name, qos_spec_name='foo')
self.volume_type = cinder_utils.create_volume_type(
@@ -432,10 +562,10 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase):
Tests the cinder_utils.create_volume_type() with encryption and an
associated QoS Spec
"""
- encryption_settings = VolumeTypeEncryptionSettings(
+ encryption_settings = VolumeTypeEncryptionConfig(
name='foo', provider_class='bar',
control_location=ControlLocation.back_end)
- volume_type_settings = VolumeTypeSettings(
+ volume_type_settings = VolumeTypeConfig(
name=self.vol_type_name, qos_spec_name=self.qos_name,
encryption=encryption_settings)
self.volume_type = cinder_utils.create_volume_type(
diff --git a/snaps/openstack/utils/tests/glance_utils_tests.py b/snaps/openstack/utils/tests/glance_utils_tests.py
index e61b795..e761ed8 100644
--- a/snaps/openstack/utils/tests/glance_utils_tests.py
+++ b/snaps/openstack/utils/tests/glance_utils_tests.py
@@ -39,7 +39,7 @@ class GlanceSmokeTests(OSComponentTestCase):
"""
Tests to ensure that the proper credentials can connect.
"""
- glance = glance_utils.glance_client(self.os_creds)
+ glance = glance_utils.glance_client(self.os_creds, self.os_session)
image = glance_utils.get_image(glance, image_name='foo')
self.assertIsNone(image)
@@ -69,7 +69,8 @@ class GlanceUtilsTests(OSComponentTestCase):
guid = uuid.uuid4()
self.image_name = self.__class__.__name__ + '-' + str(guid)
self.image = None
- self.glance = glance_utils.glance_client(self.os_creds)
+ self.glance = glance_utils.glance_client(
+ self.os_creds, self.os_session)
if self.image_metadata:
self.glance_test_meta = self.image_metadata.get('glance_tests')
else:
@@ -89,6 +90,8 @@ class GlanceUtilsTests(OSComponentTestCase):
if os.path.exists(self.tmp_dir) and os.path.isdir(self.tmp_dir):
shutil.rmtree(self.tmp_dir)
+ super(self.__class__, self).__clean__()
+
def test_create_image_minimal_url(self):
"""
Tests the glance_utils.create_image() function with a URL unless the
diff --git a/snaps/openstack/utils/tests/heat_utils_tests.py b/snaps/openstack/utils/tests/heat_utils_tests.py
index 44235ff..fa240bd 100644
--- a/snaps/openstack/utils/tests/heat_utils_tests.py
+++ b/snaps/openstack/utils/tests/heat_utils_tests.py
@@ -13,21 +13,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
+import os
+
import pkg_resources
import uuid
import time
-from snaps.openstack import create_stack
-from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
+import snaps.config.stack as stack_config
+from snaps.config.flavor import FlavorConfig
+from snaps.openstack.create_flavor import OpenStackFlavor
from snaps.openstack.create_image import OpenStackImage
from snaps.openstack.create_instance import OpenStackVmInstance
-from snaps.openstack.create_stack import StackSettings
+from snaps.openstack.create_stack import StackConfig
from snaps.openstack.tests import openstack_tests
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
from snaps.openstack.utils import (
- heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils)
+ heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils,
+ cinder_utils, keystone_utils)
__author__ = 'spisarski'
@@ -43,12 +47,12 @@ class HeatSmokeTests(OSComponentTestCase):
"""
Tests to ensure that the proper credentials can connect.
"""
- heat = heat_utils.heat_client(self.os_creds)
+ heat = heat_utils.heat_client(self.os_creds, self.os_session)
# This should not throw an exception
stacks = heat.stacks.list()
for stack in stacks:
- print stack
+ logger.info('Stack - %s', stack)
def test_heat_connect_fail(self):
"""
@@ -66,7 +70,7 @@ class HeatSmokeTests(OSComponentTestCase):
# This should throw an exception
with self.assertRaises(Exception):
for stack in stacks:
- print stack
+ logger.info('Stack - %s', stack)
class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
@@ -93,7 +97,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
# Create Flavor
self.flavor_creator = OpenStackFlavor(
self.os_creds,
- FlavorSettings(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
+ FlavorConfig(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
self.flavor_creator.create()
env_values = {'image_name': self.image_creator.image_settings.name,
@@ -103,19 +107,20 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
'inst_name': self.vm_inst_name}
heat_tmplt_path = pkg_resources.resource_filename(
'snaps.openstack.tests.heat', 'test_heat_template.yaml')
- self.stack_settings1 = StackSettings(
+ self.stack_settings1 = StackConfig(
name=stack_name1, template_path=heat_tmplt_path,
env_values=env_values)
- self.stack_settings2 = StackSettings(
+ self.stack_settings2 = StackConfig(
name=stack_name2, template_path=heat_tmplt_path,
env_values=env_values)
self.stack1 = None
self.stack2 = None
- self.heat_client = heat_utils.heat_client(self.os_creds)
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
- Cleans the image and downloaded image file
+ Cleans the stack and image
"""
if self.stack1:
try:
@@ -141,6 +146,8 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_stack(self):
"""
Tests the creation of an OpenStack Heat stack1 that does not exist.
@@ -160,7 +167,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
self.stack1.id)
self.assertEqual(self.stack1, stack_query_3)
- resources = heat_utils.get_resources(self.heat_client, self.stack1)
+ resources = heat_utils.get_resources(self.heat_client, self.stack1.id)
self.assertIsNotNone(resources)
self.assertEqual(4, len(resources))
@@ -168,24 +175,9 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
self.assertIsNotNone(outputs)
self.assertEqual(0, len(outputs))
- # Wait until stack deployment has completed
- end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
- is_active = False
- while time.time() < end_time:
- status = heat_utils.get_stack_status(self.heat_client,
- self.stack1.id)
- if status == create_stack.STATUS_CREATE_COMPLETE:
- is_active = True
- break
- elif status == create_stack.STATUS_CREATE_FAILED:
- is_active = False
- break
-
- time.sleep(3)
-
- self.assertTrue(is_active)
+ self.assertTrue(stack_active(self.heat_client, self.stack1))
- neutron = neutron_utils.neutron_client(self.os_creds)
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
networks = heat_utils.get_stack_networks(
self.heat_client, neutron, self.stack1)
self.assertIsNotNone(networks)
@@ -196,9 +188,12 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
self.assertEqual(1, len(subnets))
self.assertEqual(self.subnet_name, subnets[0].name)
- nova = nova_utils.nova_client(self.os_creds)
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
servers = heat_utils.get_stack_servers(
- self.heat_client, nova, self.stack1)
+ self.heat_client, nova, neutron, keystone, self.stack1,
+ self.os_creds.project_name)
self.assertIsNotNone(servers)
self.assertEqual(1, len(servers))
self.assertEqual(self.vm_inst_name, servers[0].name)
@@ -222,21 +217,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
self.stack1.id)
self.assertEqual(self.stack1, stack1_query_3)
- end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
- is_active = False
- while time.time() < end_time:
- status = heat_utils.get_stack_status(self.heat_client,
- self.stack1.id)
- if status == create_stack.STATUS_CREATE_COMPLETE:
- is_active = True
- break
- elif status == create_stack.STATUS_CREATE_FAILED:
- is_active = False
- break
-
- time.sleep(3)
-
- self.assertTrue(is_active)
+ self.assertTrue(stack_active(self.heat_client, self.stack1))
self.stack2 = heat_utils.create_stack(self.heat_client,
self.stack_settings2)
@@ -253,22 +234,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
self.stack2.id)
self.assertEqual(self.stack2, stack2_query_3)
- end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
-
- is_active = False
- while time.time() < end_time:
- status = heat_utils.get_stack_status(self.heat_client,
- self.stack2.id)
- if status == create_stack.STATUS_CREATE_COMPLETE:
- is_active = True
- break
- elif status == create_stack.STATUS_CREATE_FAILED:
- is_active = False
- break
-
- time.sleep(3)
-
- self.assertTrue(is_active)
+ self.assertTrue(stack_active(self.heat_client, self.stack2))
class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
@@ -312,60 +278,59 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
'external_net_name': self.ext_net_name}
heat_tmplt_path = pkg_resources.resource_filename(
'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
- stack_settings = StackSettings(
+ stack_settings = StackConfig(
name=stack_name, template_path=heat_tmplt_path,
env_values=env_values)
- self.heat_client = heat_utils.heat_client(self.os_creds)
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
self.stack = heat_utils.create_stack(self.heat_client, stack_settings)
- # Wait until stack deployment has completed
- end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
- is_active = False
- while time.time() < end_time:
- status = heat_utils.get_stack_status(self.heat_client,
- self.stack.id)
- if status == create_stack.STATUS_CREATE_COMPLETE:
- is_active = True
- break
- elif status == create_stack.STATUS_CREATE_FAILED:
- is_active = False
- break
+ self.assertTrue(stack_active(self.heat_client, self.stack))
- time.sleep(3)
- self.assertTrue(is_active)
+ self.keypair1_settings = None
+ self.keypair2_settings = None
def tearDown(self):
"""
- Cleans the image and downloaded image file
+ Cleans the stack and image
"""
if self.stack:
try:
heat_utils.delete_stack(self.heat_client, self.stack)
# Wait until stack deployment has completed
- end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
+ end_time = (time.time() +
+ stack_config.STACK_COMPLETE_TIMEOUT)
is_deleted = False
while time.time() < end_time:
status = heat_utils.get_stack_status(self.heat_client,
self.stack.id)
- if status == create_stack.STATUS_DELETE_COMPLETE:
+ if status == stack_config.STATUS_DELETE_COMPLETE:
is_deleted = True
break
- elif status == create_stack.STATUS_DELETE_FAILED:
+ elif status == stack_config.STATUS_DELETE_FAILED:
is_deleted = False
break
time.sleep(3)
if not is_deleted:
- nova = nova_utils.nova_client(self.os_creds)
- neutron = neutron_utils.neutron_client(self.os_creds)
- glance = glance_utils.glance_client(self.os_creds)
+ nova = nova_utils.nova_client(
+ self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ glance = glance_utils.glance_client(
+ self.os_creds, self.os_session)
+
servers = heat_utils.get_stack_servers(
- self.heat_client, nova, self.stack)
+ self.heat_client, nova, neutron, keystone, self.stack,
+ self.os_creds.project_name)
for server in servers:
- vm_settings = settings_utils.create_vm_inst_settings(
- nova, neutron, server)
- img_settings = settings_utils.determine_image_settings(
+ vm_settings = settings_utils.create_vm_inst_config(
+ nova, keystone, neutron, server,
+ self.os_creds.project_name)
+ img_settings = settings_utils.determine_image_config(
glance, server,
[self.image_creator1.image_settings,
self.image_creator2.image_settings])
@@ -392,40 +357,56 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
except:
pass
+ if self.keypair1_settings:
+ expanded_path = os.path.expanduser(
+ self.keypair1_settings.private_filepath)
+ os.chmod(expanded_path, 0o755)
+ os.remove(expanded_path)
+
+ if self.keypair2_settings:
+ expanded_path = os.path.expanduser(
+ self.keypair2_settings.private_filepath)
+ os.chmod(expanded_path, 0o755)
+ os.remove(expanded_path)
+
+ super(self.__class__, self).__clean__()
+
def test_get_settings_from_stack(self):
"""
Tests that a heat template with floating IPs and can have the proper
settings derived from settings_utils.py.
"""
- resources = heat_utils.get_resources(self.heat_client, self.stack)
+ resources = heat_utils.get_resources(self.heat_client, self.stack.id)
self.assertIsNotNone(resources)
- self.assertEqual(11, len(resources))
+ self.assertEqual(13, len(resources))
options = heat_utils.get_outputs(self.heat_client, self.stack)
self.assertIsNotNone(options)
self.assertEqual(1, len(options))
- neutron = neutron_utils.neutron_client(self.os_creds)
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
networks = heat_utils.get_stack_networks(
self.heat_client, neutron, self.stack)
self.assertIsNotNone(networks)
self.assertEqual(1, len(networks))
self.assertEqual(self.network_name, networks[0].name)
- network_settings = settings_utils.create_network_settings(
+ network_settings = settings_utils.create_network_config(
neutron, networks[0])
self.assertIsNotNone(network_settings)
self.assertEqual(self.network_name, network_settings.name)
- nova = nova_utils.nova_client(self.os_creds)
- glance = glance_utils.glance_client(self.os_creds)
-
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ glance = glance_utils.glance_client(self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
servers = heat_utils.get_stack_servers(
- self.heat_client, nova, self.stack)
+ self.heat_client, nova, neutron, keystone, self.stack,
+ self.os_creds.project_name)
self.assertIsNotNone(servers)
self.assertEqual(2, len(servers))
- image_settings = settings_utils.determine_image_settings(
+ image_settings = settings_utils.determine_image_config(
glance, servers[0],
[self.image_creator1.image_settings,
self.image_creator2.image_settings])
@@ -438,7 +419,7 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
self.assertEqual(
self.image_creator2.image_settings.name, image_settings.name)
- image_settings = settings_utils.determine_image_settings(
+ image_settings = settings_utils.determine_image_config(
glance, servers[1],
[self.image_creator1.image_settings,
self.image_creator2.image_settings])
@@ -449,14 +430,420 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
self.assertEqual(
self.image_creator2.image_settings.name, image_settings.name)
- keypair1_settings = settings_utils.determine_keypair_settings(
+ self.keypair1_settings = settings_utils.determine_keypair_config(
self.heat_client, self.stack, servers[0],
priv_key_key='private_key')
- self.assertIsNotNone(keypair1_settings)
- self.assertEqual(self.keypair_name, keypair1_settings.name)
+ self.assertIsNotNone(self.keypair1_settings)
+ self.assertEqual(self.keypair_name, self.keypair1_settings.name)
- keypair2_settings = settings_utils.determine_keypair_settings(
+ self.keypair2_settings = settings_utils.determine_keypair_config(
self.heat_client, self.stack, servers[1],
priv_key_key='private_key')
- self.assertIsNotNone(keypair2_settings)
- self.assertEqual(self.keypair_name, keypair2_settings.name)
+ self.assertIsNotNone(self.keypair2_settings)
+ self.assertEqual(self.keypair_name, self.keypair2_settings.name)
+
+
+class HeatUtilsRouterTests(OSComponentTestCase):
+ """
+ Test Heat volume functionality
+ """
+
+ def setUp(self):
+ """
+ Instantiates OpenStack instances that cannot be spawned by Heat
+ """
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ stack_name = guid + '-stack'
+
+ self.net_name = guid + '-net'
+ self.subnet_name = guid + '-subnet'
+ self.router_name = guid + '-router'
+
+ env_values = {
+ 'net_name': self.net_name,
+ 'subnet_name': self.subnet_name,
+ 'router_name': self.router_name,
+ 'external_net_name': self.ext_net_name}
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'router_heat_template.yaml')
+ self.stack_settings = StackConfig(
+ name=stack_name, template_path=heat_tmplt_path,
+ env_values=env_values)
+ self.stack = None
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the image and downloaded image file
+ """
+ if self.stack:
+ try:
+ heat_utils.delete_stack(self.heat_client, self.stack)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_router_with_stack(self):
+ """
+ Tests the creation of an OpenStack router with Heat and the retrieval
+ of the Router Domain objects from heat_utils#get_stack_routers().
+ """
+ self.stack = heat_utils.create_stack(
+ self.heat_client, self.stack_settings)
+
+ # Wait until stack deployment has completed
+ end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
+ is_active = False
+ while time.time() < end_time:
+ status = heat_utils.get_stack_status(self.heat_client,
+ self.stack.id)
+ if status == stack_config.STATUS_CREATE_COMPLETE:
+ is_active = True
+ break
+ elif status == stack_config.STATUS_CREATE_FAILED:
+ is_active = False
+ break
+
+ time.sleep(3)
+
+ self.assertTrue(is_active)
+
+ routers = heat_utils.get_stack_routers(
+ self.heat_client, self.neutron, self.stack)
+
+ self.assertEqual(1, len(routers))
+
+ router = routers[0]
+ self.assertEqual(self.router_name, router.name)
+
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ ext_net = neutron_utils.get_network(
+ self.neutron, keystone, network_name=self.ext_net_name)
+ self.assertEqual(ext_net.id, router.external_network_id)
+
+
+class HeatUtilsVolumeTests(OSComponentTestCase):
+ """
+ Test Heat volume functionality
+ """
+
+ def setUp(self):
+ """
+ Instantiates OpenStack instances that cannot be spawned by Heat
+ """
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ stack_name = guid + '-stack'
+ self.volume_name = guid + '-vol'
+ self.volume_type_name = guid + '-vol-type'
+
+ env_values = {
+ 'volume_name': self.volume_name,
+ 'volume_type_name': self.volume_type_name}
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
+ self.stack_settings = StackConfig(
+ name=stack_name, template_path=heat_tmplt_path,
+ env_values=env_values)
+ self.stack = None
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the stack
+ """
+ if self.stack:
+ try:
+ heat_utils.delete_stack(self.heat_client, self.stack)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_vol_with_stack(self):
+ """
+ Tests the creation of an OpenStack volume with Heat.
+ """
+ self.stack = heat_utils.create_stack(
+ self.heat_client, self.stack_settings)
+ self.assertTrue(stack_active(self.heat_client, self.stack))
+
+ volumes = heat_utils.get_stack_volumes(
+ self.heat_client, self.cinder, self.stack)
+
+ self.assertEqual(1, len(volumes))
+
+ volume = volumes[0]
+ self.assertEqual(self.volume_name, volume.name)
+ self.assertEqual(self.volume_type_name, volume.type)
+ self.assertEqual(1, volume.size)
+ self.assertEqual(False, volume.multi_attach)
+
+ def test_create_vol_types_with_stack(self):
+ """
+ Tests the creation of an OpenStack volume with Heat.
+ """
+ self.stack = heat_utils.create_stack(
+ self.heat_client, self.stack_settings)
+ self.assertTrue(stack_active(self.heat_client, self.stack))
+
+ volume_types = heat_utils.get_stack_volume_types(
+ self.heat_client, self.cinder, self.stack)
+
+ self.assertEqual(1, len(volume_types))
+
+ volume_type = volume_types[0]
+
+ self.assertEqual(self.volume_type_name, volume_type.name)
+ self.assertTrue(volume_type.public)
+ self.assertIsNone(volume_type.qos_spec)
+
+ # TODO - Add encryption back and find out why it broke in Pike
+ # encryption = volume_type.encryption
+ # self.assertIsNotNone(encryption)
+ # self.assertIsNone(encryption.cipher)
+ # self.assertEqual('front-end', encryption.control_location)
+ # self.assertIsNone(encryption.key_size)
+ # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
+ # encryption.provider)
+ # self.assertEqual(volume_type.id, encryption.volume_type_id)
+
+
+class HeatUtilsFlavorTests(OSComponentTestCase):
+ """
+ Test Heat volume functionality
+ """
+
+ def setUp(self):
+ """
+ Instantiates OpenStack instances that cannot be spawned by Heat
+ """
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.name_prefix = guid
+ stack_name = guid + '-stack'
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml')
+ self.stack_settings = StackConfig(
+ name=stack_name, template_path=heat_tmplt_path)
+ self.stack = None
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
+ self.nova = nova_utils.nova_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the stack
+ """
+ if self.stack:
+ try:
+ heat_utils.delete_stack(self.heat_client, self.stack)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_flavor_with_stack(self):
+ """
+ Tests the creation of an OpenStack volume with Heat.
+ """
+ self.stack = heat_utils.create_stack(
+ self.heat_client, self.stack_settings)
+
+ self.assertTrue(stack_active(self.heat_client, self.stack))
+
+ flavors = heat_utils.get_stack_flavors(
+ self.heat_client, self.nova, self.stack)
+
+ self.assertEqual(1, len(flavors))
+
+ flavor = flavors[0]
+ self.assertTrue(flavor.name.startswith(self.name_prefix))
+ self.assertEqual(1024, flavor.ram)
+ self.assertEqual(200, flavor.disk)
+ self.assertEqual(8, flavor.vcpus)
+ self.assertEqual(0, flavor.ephemeral)
+ self.assertIsNone(flavor.swap)
+ self.assertEqual(1.0, flavor.rxtx_factor)
+ self.assertTrue(flavor.is_public)
+
+
+class HeatUtilsKeypairTests(OSComponentTestCase):
+ """
+ Test Heat volume functionality
+ """
+
+ def setUp(self):
+ """
+ Instantiates OpenStack instances that cannot be spawned by Heat
+ """
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ stack_name = guid + '-stack'
+ self.keypair_name = guid + '-kp'
+
+ env_values = {'keypair_name': self.keypair_name}
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
+ self.stack_settings = StackConfig(
+ name=stack_name, template_path=heat_tmplt_path,
+ env_values=env_values)
+ self.stack = None
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
+ self.nova = nova_utils.nova_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the stack
+ """
+ if self.stack:
+ try:
+ heat_utils.delete_stack(self.heat_client, self.stack)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_keypair_with_stack(self):
+ """
+ Tests the creation of an OpenStack keypair with Heat.
+ """
+ self.stack = heat_utils.create_stack(
+ self.heat_client, self.stack_settings)
+ self.assertTrue(stack_active(self.heat_client, self.stack))
+
+ keypairs = heat_utils.get_stack_keypairs(
+ self.heat_client, self.nova, self.stack)
+
+ self.assertEqual(1, len(keypairs))
+ keypair = keypairs[0]
+
+ self.assertEqual(self.keypair_name, keypair.name)
+
+ outputs = heat_utils.get_outputs(self.heat_client, self.stack)
+
+ for output in outputs:
+ if output.key == 'private_key':
+ self.assertTrue(output.value.startswith(
+ '-----BEGIN RSA PRIVATE KEY-----'))
+
+ keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id)
+ self.assertIsNotNone(keypair)
+
+ self.assertEqual(self.keypair_name, keypair.name)
+
+
+class HeatUtilsSecurityGroupTests(OSComponentTestCase):
+ """
+ Test Heat volume functionality
+ """
+
+ def setUp(self):
+ """
+ Instantiates OpenStack instances that cannot be spawned by Heat
+ """
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ stack_name = guid + '-stack'
+ self.sec_grp_name = guid + '-sec-grp'
+
+ env_values = {'security_group_name': self.sec_grp_name}
+
+ heat_tmplt_path = pkg_resources.resource_filename(
+ 'snaps.openstack.tests.heat', 'security_group_heat_template.yaml')
+ self.stack_settings = StackConfig(
+ name=stack_name, template_path=heat_tmplt_path,
+ env_values=env_values)
+ self.stack = None
+ self.heat_client = heat_utils.heat_client(
+ self.os_creds, self.os_session)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+
+ def tearDown(self):
+ """
+ Cleans the stack
+ """
+ if self.stack:
+ try:
+ heat_utils.delete_stack(self.heat_client, self.stack)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_security_group_with_stack(self):
+ """
+ Tests the creation of an OpenStack SecurityGroup with Heat.
+ """
+ self.stack = heat_utils.create_stack(
+ self.heat_client, self.stack_settings)
+ self.assertTrue(stack_active(self.heat_client, self.stack))
+
+ sec_grp = heat_utils.get_stack_security_groups(
+ self.heat_client, self.neutron, self.stack)[0]
+
+ self.assertEqual(self.sec_grp_name, sec_grp.name)
+ self.assertEqual('Test description', sec_grp.description)
+ self.assertEqual(2, len(sec_grp.rules))
+
+ has_ssh_rule = False
+ has_icmp_rule = False
+
+ for rule in sec_grp.rules:
+ if (rule.security_group_id == sec_grp.id
+ and rule.direction == 'egress'
+ and rule.ethertype == 'IPv4'
+ and rule.port_range_min == 22
+ and rule.port_range_max == 22
+ and rule.protocol == 'tcp'
+ and rule.remote_group_id is None
+ and rule.remote_ip_prefix == '0.0.0.0/0'):
+ has_ssh_rule = True
+ if (rule.security_group_id == sec_grp.id
+ and rule.direction == 'ingress'
+ and rule.ethertype == 'IPv4'
+ and rule.port_range_min is None
+ and rule.port_range_max is None
+ and rule.protocol == 'icmp'
+ and rule.remote_group_id is None
+ and rule.remote_ip_prefix == '0.0.0.0/0'):
+ has_icmp_rule = True
+
+ self.assertTrue(has_ssh_rule)
+ self.assertTrue(has_icmp_rule)
+
+
+def stack_active(heat_cli, stack):
+ """
+ Blocks until stack application has successfully completed or failed
+ :param heat_cli: the Heat client
+ :param stack: the Stack domain object
+ :return: T/F
+ """
+ # Wait until stack deployment has completed
+ end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
+ is_active = False
+ while time.time() < end_time:
+ status = heat_utils.get_stack_status(heat_cli, stack.id)
+ if status == stack_config.STATUS_CREATE_COMPLETE:
+ is_active = True
+ break
+ elif status == stack_config.STATUS_CREATE_FAILED:
+ is_active = False
+ break
+
+ time.sleep(3)
+
+ return is_active
diff --git a/snaps/openstack/utils/tests/keystone_utils_tests.py b/snaps/openstack/utils/tests/keystone_utils_tests.py
index bd0086b..cc3a8ad 100644
--- a/snaps/openstack/utils/tests/keystone_utils_tests.py
+++ b/snaps/openstack/utils/tests/keystone_utils_tests.py
@@ -14,8 +14,8 @@
# limitations under the License.
import uuid
-from snaps.openstack.create_project import ProjectSettings
-from snaps.openstack.create_user import UserSettings
+from snaps.config.project import ProjectConfig
+from snaps.config.user import UserConfig
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
from snaps.openstack.utils import keystone_utils, neutron_utils
@@ -31,7 +31,8 @@ class KeystoneSmokeTests(OSComponentTestCase):
"""
Tests to ensure that the proper credentials can connect.
"""
- keystone = keystone_utils.keystone_client(self.os_creds)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
users = keystone.users.list()
self.assertIsNotNone(users)
@@ -66,17 +67,19 @@ class KeystoneUtilsTests(OSComponentTestCase):
self.project_name = self.guid + '-projName'
self.project = None
self.role = None
- self.keystone = keystone_utils.keystone_client(self.os_creds)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
Cleans the remote OpenStack objects
"""
if self.project:
- neutron = neutron_utils.neutron_client(self.os_creds)
+ neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
default_sec_grp = neutron_utils.get_security_group(
- neutron, sec_grp_name='default',
- project_id=self.project.id)
+ neutron, self.keystone, sec_grp_name='default',
+ project_name=self.os_creds.project_name)
if default_sec_grp:
try:
neutron_utils.delete_security_group(
@@ -92,11 +95,13 @@ class KeystoneUtilsTests(OSComponentTestCase):
if self.role:
keystone_utils.delete_role(self.keystone, self.role)
+ super(self.__class__, self).__clean__()
+
def test_create_user_minimal(self):
"""
Tests the keystone_utils.create_user() function
"""
- user_settings = UserSettings(
+ user_settings = UserConfig(
name=self.username,
password=str(uuid.uuid4()),
domain_name=self.os_creds.user_domain_name)
@@ -111,7 +116,7 @@ class KeystoneUtilsTests(OSComponentTestCase):
"""
Tests the keyston_utils.create_project() funtion
"""
- project_settings = ProjectSettings(
+ project_settings = ProjectConfig(
name=self.project_name, domain=self.os_creds.project_domain_name)
self.project = keystone_utils.create_project(self.keystone,
project_settings)
@@ -124,8 +129,11 @@ class KeystoneUtilsTests(OSComponentTestCase):
domain = keystone_utils.get_domain_by_id(
self.keystone, project.domain_id)
- self.assertIsNotNone(domain)
- self.assertEqual(domain.id, project.domain_id)
+ if self.keystone.version == keystone_utils.V2_VERSION_STR:
+ self.assertIsNone(domain)
+ else:
+ self.assertIsNotNone(domain)
+ self.assertEqual(domain.id, project.domain_id)
def test_get_endpoint_success(self):
"""
@@ -180,13 +188,13 @@ class KeystoneUtilsTests(OSComponentTestCase):
Tests the keystone_utils function grant_user_role_to_project()
:return:
"""
- user_settings = UserSettings(
+ user_settings = UserConfig(
name=self.username, password=str(uuid.uuid4()),
domain_name=self.os_creds.user_domain_name)
self.user = keystone_utils.create_user(self.keystone, user_settings)
self.assertEqual(self.username, self.user.name)
- project_settings = ProjectSettings(
+ project_settings = ProjectConfig(
name=self.project_name, domain=self.os_creds.project_domain_name)
self.project = keystone_utils.create_project(self.keystone,
project_settings)
diff --git a/snaps/openstack/utils/tests/magnum_utils_tests.py b/snaps/openstack/utils/tests/magnum_utils_tests.py
new file mode 100644
index 0000000..d87d97a
--- /dev/null
+++ b/snaps/openstack/utils/tests/magnum_utils_tests.py
@@ -0,0 +1,303 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+import logging
+import uuid
+
+from magnumclient.common.apiclient.exceptions import BadRequest
+
+from snaps.config.cluster_template import (
+ ClusterTemplateConfig, ServerType, ContainerOrchestrationEngine,
+ DockerStorageDriver)
+from snaps.config.flavor import FlavorConfig
+from snaps.config.keypair import KeypairConfig
+from snaps.openstack.create_flavor import OpenStackFlavor
+from snaps.openstack.create_image import OpenStackImage
+from snaps.openstack.create_keypairs import OpenStackKeypair
+from snaps.openstack.os_credentials import OSCreds
+from snaps.openstack.tests import openstack_tests
+from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
+from snaps.openstack.utils import magnum_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('magnum_utils_tests')
+
+
+class MagnumSmokeTests(OSComponentTestCase):
+ """
+ Tests to ensure that the magnum client can communicate with the cloud
+ """
+
+ def test_connect_success(self):
+ """
+ Tests to ensure that the proper credentials can connect.
+ """
+ magnum = magnum_utils.magnum_client(
+ self.os_creds, self.os_session)
+
+ # This should not throw an exception
+ self.assertIsNotNone(magnum.clusters.list())
+
+ def test_nova_connect_fail(self):
+ """
+ Tests to ensure that the improper credentials cannot connect.
+ """
+
+ with self.assertRaises(RuntimeError):
+ magnum_utils.magnum_client(
+ OSCreds(username='user', password='pass',
+ auth_url=self.os_creds.auth_url,
+ project_name=self.os_creds.project_name,
+ proxy_settings=self.os_creds.proxy_settings))
+
+
+class MagnumUtilsClusterTypeTests(OSComponentTestCase):
+ """
+ Tests individual functions within magnum_utils.py
+ """
+
+ def setUp(self):
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.cluster_type_name = self.guid + '-cluster-type'
+ self.magnum = magnum_utils.magnum_client(
+ self.os_creds, self.os_session)
+
+ metadata = self.image_metadata
+ if not metadata:
+ metadata = dict()
+ if 'extra_properties' not in metadata:
+ metadata['extra_properties'] = dict()
+ metadata['extra_properties']['os_distro'] = 'cirros'
+
+ os_image_settings = openstack_tests.cirros_image_settings(
+ name=self.guid + '-image', image_metadata=metadata)
+
+ self.image_creator = OpenStackImage(self.os_creds, os_image_settings)
+
+ self.flavor_creator = OpenStackFlavor(
+ self.os_creds, FlavorConfig(
+ name=self.guid + '-flavor', ram=512, disk=10, vcpus=1))
+
+ keypair_priv_filepath = 'tmp/' + self.guid
+ keypair_pub_filepath = keypair_priv_filepath + '.pub'
+
+ self.keypair_creator = OpenStackKeypair(
+ self.os_creds, KeypairConfig(
+ name=self.guid + '-keypair',
+ public_filepath=keypair_pub_filepath,
+ private_filepath=keypair_priv_filepath))
+
+ self.cluster_template = None
+
+ try:
+ self.image_creator.create()
+ self.flavor_creator.create()
+ self.keypair_creator.create()
+ except:
+ self.tearDown()
+ raise
+
+ def tearDown(self):
+ if self.cluster_template:
+ try:
+ magnum_utils.delete_cluster_template(
+ self.magnum, self.cluster_template.id)
+ except:
+ pass
+ if self.keypair_creator:
+ try:
+ self.keypair_creator.clean()
+ except:
+ pass
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except:
+ pass
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_cluster_template_simple(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name)
+
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+ self.assertIsNotNone(self.cluster_template)
+ self.assertTrue(
+ validate_cluster_template(config, self.cluster_template))
+
+ template_by_name = magnum_utils.get_cluster_template(
+ self.magnum, template_name=config.name)
+ self.assertEqual(self.cluster_template, template_by_name)
+ template_by_id = magnum_utils.get_cluster_template_by_id(
+ self.magnum, self.cluster_template.id)
+ self.assertEqual(self.cluster_template, template_by_id)
+
+ def test_create_cluster_template_all(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ network_driver='flannel', external_net=self.ext_net_name,
+ floating_ip_enabled=True, docker_volume_size=100,
+ server_type=ServerType.vm,
+ flavor=self.flavor_creator.flavor_settings.name,
+ master_flavor=self.flavor_creator.flavor_settings.name,
+ coe=ContainerOrchestrationEngine.kubernetes,
+ fixed_net='foo', fixed_subnet='bar',
+ registry_enabled=True, insecure_registry='localhost',
+ docker_storage_driver=DockerStorageDriver.overlay,
+ dns_nameserver='8.8.4.4', public=True, tls_disabled=True,
+ http_proxy=None, https_proxy=None, volume_driver='cinder',
+ master_lb_enabled=False, labels={'foo': 'bar'})
+
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+ self.assertIsNotNone(self.cluster_template)
+ self.assertTrue(
+ validate_cluster_template(config, self.cluster_template))
+
+ template_by_name = magnum_utils.get_cluster_template(
+ self.magnum, template_name=config.name)
+ self.assertEqual(self.cluster_template, template_by_name)
+ template_by_id = magnum_utils.get_cluster_template_by_id(
+ self.magnum, self.cluster_template.id)
+ self.assertEqual(self.cluster_template, template_by_id)
+
+ def test_create_cluster_template_bad_image(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image='foo',
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+
+ def test_create_cluster_template_bad_ext_net(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net='foo',
+ flavor=self.flavor_creator.flavor_settings.name)
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+
+ def test_create_cluster_template_bad_flavor(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor='foo')
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+
+ def test_create_cluster_template_bad_master_flavor(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ flavor=self.flavor_creator.flavor_settings.name,
+ master_flavor='foo')
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+
+ def test_create_cluster_template_bad_network_driver(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ network_driver='foo')
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+
+ def test_create_cluster_template_bad_volume_driver(self):
+ config = ClusterTemplateConfig(
+ name=self.cluster_type_name,
+ image=self.image_creator.image_settings.name,
+ keypair=self.keypair_creator.keypair_settings.name,
+ external_net=self.ext_net_name,
+ volume_driver='foo')
+
+ with self.assertRaises(BadRequest):
+ self.cluster_template = magnum_utils.create_cluster_template(
+ self.magnum, config)
+
+
+def validate_cluster_template(tmplt_config, tmplt_obj):
+ """
+ Returns true if the configuration matches the ClusterTemplate object
+ :param tmplt_config: the ClusterTemplateConfig object
+ :param tmplt_obj: the ClusterTemplate domain object
+ :return: T/F
+ """
+ if not tmplt_config.network_driver:
+ network_driver = 'flannel'
+ else:
+ network_driver = tmplt_config.network_driver
+
+ return (
+ tmplt_config.coe.value == tmplt_obj.coe and
+ tmplt_config.dns_nameserver == tmplt_obj.dns_nameserver and
+ tmplt_config.docker_storage_driver.value
+ == tmplt_obj.docker_storage_driver and
+ tmplt_config.docker_volume_size == tmplt_obj.docker_volume_size and
+ tmplt_config.external_net == tmplt_obj.external_net and
+ tmplt_config.fixed_net == tmplt_obj.fixed_net and
+ tmplt_config.fixed_subnet == tmplt_obj.fixed_subnet and
+ tmplt_config.flavor == tmplt_obj.flavor and
+ tmplt_config.floating_ip_enabled == tmplt_obj.floating_ip_enabled and
+ tmplt_config.http_proxy == tmplt_obj.http_proxy and
+ tmplt_config.https_proxy == tmplt_obj.https_proxy and
+ tmplt_config.no_proxy == tmplt_obj.no_proxy and
+ tmplt_config.image == tmplt_obj.image and
+ tmplt_config.insecure_registry == tmplt_obj.insecure_registry and
+ tmplt_config.keypair == tmplt_obj.keypair and
+ tmplt_config.labels == tmplt_obj.labels and
+ tmplt_config.master_flavor == tmplt_obj.master_flavor and
+ tmplt_config.master_lb_enabled == tmplt_obj.master_lb_enabled and
+ tmplt_config.name == tmplt_obj.name and
+ network_driver == tmplt_obj.network_driver and
+ tmplt_config.no_proxy == tmplt_obj.no_proxy and
+ tmplt_config.public == tmplt_obj.public and
+ tmplt_config.registry_enabled == tmplt_obj.registry_enabled and
+ tmplt_config.server_type.value == tmplt_obj.server_type and
+ tmplt_config.tls_disabled == tmplt_obj.tls_disabled and
+ tmplt_config.volume_driver == tmplt_obj.volume_driver
+ )
diff --git a/snaps/openstack/utils/tests/neutron_utils_tests.py b/snaps/openstack/utils/tests/neutron_utils_tests.py
index 05d508d..a55f779 100644
--- a/snaps/openstack/utils/tests/neutron_utils_tests.py
+++ b/snaps/openstack/utils/tests/neutron_utils_tests.py
@@ -14,11 +14,11 @@
# limitations under the License.
import uuid
-from snaps.openstack import create_router
-from snaps.openstack.create_network import NetworkSettings, SubnetSettings, \
- PortSettings
-from snaps.openstack.create_security_group import SecurityGroupSettings, \
- SecurityGroupRuleSettings, Direction
+from neutronclient.common.exceptions import NotFound, BadRequest
+
+from snaps.config.network import NetworkConfig, SubnetConfig, PortConfig
+from snaps.config.security_group import (
+ SecurityGroupConfig, SecurityGroupRuleConfig, Direction)
from snaps.openstack.tests import openstack_tests
from snaps.openstack.tests import validation_utils
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
@@ -41,7 +41,7 @@ class NeutronSmokeTests(OSComponentTestCase):
"""
Tests to ensure that the proper credentials can connect.
"""
- neutron = neutron_utils.neutron_client(self.os_creds)
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
networks = neutron.list_networks()
@@ -70,7 +70,7 @@ class NeutronSmokeTests(OSComponentTestCase):
configured self.ext_net_name is contained within the returned list
:return:
"""
- neutron = neutron_utils.neutron_client(self.os_creds)
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
ext_networks = neutron_utils.get_external_networks(neutron)
found = False
for network in ext_networks:
@@ -88,9 +88,13 @@ class NeutronUtilsNetworkTests(OSComponentTestCase):
def setUp(self):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.port_name = str(guid) + '-port'
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
self.network = None
self.net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net')
def tearDown(self):
@@ -100,36 +104,42 @@ class NeutronUtilsNetworkTests(OSComponentTestCase):
if self.network:
neutron_utils.delete_network(self.neutron, self.network)
+ super(self.__class__, self).__clean__()
+
def test_create_network(self):
"""
- Tests the neutron_utils.create_neutron_net() function
+ Tests the neutron_utils.create_network() function
"""
self.network = neutron_utils.create_network(
self.neutron, self.os_creds, self.net_config.network_settings)
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
+ self.assertEqual(len(self.net_config.network_settings.subnet_settings),
+ len(self.network.subnets))
def test_create_network_empty_name(self):
"""
- Tests the neutron_utils.create_neutron_net() function with an empty
+ Tests the neutron_utils.create_network() function with an empty
network name
"""
with self.assertRaises(Exception):
self.network = neutron_utils.create_network(
self.neutron, self.os_creds,
- network_settings=NetworkSettings(name=''))
+ network_settings=NetworkConfig(name=''))
def test_create_network_null_name(self):
"""
- Tests the neutron_utils.create_neutron_net() function when the network
+ Tests the neutron_utils.create_network() function when the network
name is None
"""
with self.assertRaises(Exception):
self.network = neutron_utils.create_network(
self.neutron, self.os_creds,
- network_settings=NetworkSettings())
+ network_settings=NetworkConfig())
class NeutronUtilsSubnetTests(OSComponentTestCase):
@@ -140,10 +150,13 @@ class NeutronUtilsSubnetTests(OSComponentTestCase):
def setUp(self):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.port_name = str(guid) + '-port'
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
self.network = None
- self.subnet = None
self.net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
external_net=self.ext_net_name)
@@ -151,43 +164,47 @@ class NeutronUtilsSubnetTests(OSComponentTestCase):
"""
Cleans the remote OpenStack objects
"""
- if self.subnet:
- try:
- neutron_utils.delete_subnet(self.neutron, self.subnet)
- except:
- pass
if self.network:
try:
neutron_utils.delete_network(self.neutron, self.network)
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_subnet(self):
"""
- Tests the neutron_utils.create_neutron_net() function
+ Tests the neutron_utils.create_network() function
"""
self.network = neutron_utils.create_network(
self.neutron, self.os_creds, self.net_config.network_settings)
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting, self.os_creds, network=self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
subnet_query1 = neutron_utils.get_subnet(
- self.neutron, subnet_name=subnet_setting.name)
- self.assertEqual(self.subnet, subnet_query1)
+ self.neutron, self.network, subnet_name=subnet_setting.name)
+ self.assertEqual(self.network.subnets[0], subnet_query1)
subnet_query2 = neutron_utils.get_subnets_by_network(self.neutron,
self.network)
self.assertIsNotNone(subnet_query2)
self.assertEqual(1, len(subnet_query2))
- self.assertEqual(self.subnet, subnet_query2[0])
+ self.assertEqual(self.network.subnets[0], subnet_query2[0])
+
+ subnet_query3 = neutron_utils.get_subnet_by_name(
+ self.neutron, self.keystone, subnet_setting.name,
+ self.os_creds.project_name)
+ self.assertIsNotNone(subnet_query3)
+ self.assertEqual(self.network.subnets[0], subnet_query3)
def test_create_subnet_null_name(self):
"""
@@ -199,14 +216,16 @@ class NeutronUtilsSubnetTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
with self.assertRaises(Exception):
- SubnetSettings(cidr=self.net_config.subnet_cidr)
+ SubnetConfig(cidr=self.net_config.subnet_cidr)
def test_create_subnet_empty_name(self):
"""
- Tests the neutron_utils.create_neutron_net() function with an empty
+ Tests the neutron_utils.create_network() function with an empty
name
"""
self.network = neutron_utils.create_network(
@@ -214,61 +233,281 @@ class NeutronUtilsSubnetTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting, self.os_creds, network=self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
self.assertFalse(validate_subnet(
- self.neutron, '', subnet_setting.cidr, True))
+ self.neutron, self.network, '', subnet_setting.cidr, True))
subnet_query1 = neutron_utils.get_subnet(
- self.neutron, subnet_name=subnet_setting.name)
- self.assertEqual(self.subnet, subnet_query1)
+ self.neutron, self.network, subnet_name=subnet_setting.name)
+ self.assertEqual(self.network.subnets[0], subnet_query1)
- subnet_query2 = neutron_utils.get_subnets_by_network(self.neutron,
- self.network)
+ subnet_query2 = neutron_utils.get_subnets_by_network(
+ self.neutron, self.network)
self.assertIsNotNone(subnet_query2)
self.assertEqual(1, len(subnet_query2))
- self.assertEqual(self.subnet, subnet_query2[0])
+ self.assertEqual(self.network.subnets[0], subnet_query2[0])
def test_create_subnet_null_cidr(self):
"""
Tests the neutron_utils.create_neutron_subnet() function for an
Exception when the subnet CIDR value is None
"""
- self.network = neutron_utils.create_network(
- self.neutron, self.os_creds, self.net_config.network_settings)
- self.assertEqual(self.net_config.network_settings.name,
- self.network.name)
- self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
-
+ self.net_config.network_settings.subnet_settings[0].cidr = None
with self.assertRaises(Exception):
- sub_sets = SubnetSettings(
- cidr=None, name=self.net_config.subnet_name)
- neutron_utils.create_subnet(
- self.neutron, sub_sets, self.os_creds, network=self.network)
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.net_config.network_settings)
def test_create_subnet_empty_cidr(self):
"""
Tests the neutron_utils.create_neutron_subnet() function for an
Exception when the subnet CIDR value is empty
"""
+ self.net_config.network_settings.subnet_settings[0].cidr = ''
+ with self.assertRaises(Exception):
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.net_config.network_settings)
+
+
+class NeutronUtilsIPv6Tests(OSComponentTestCase):
+ """
+ Test for creating IPv6 networks with subnets via neutron_utils.py
+ """
+
+ def setUp(self):
+ self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.network = None
+
+ def tearDown(self):
+ """
+ Cleans the remote OpenStack objects
+ """
+ if self.network:
+ try:
+ neutron_utils.delete_network(self.neutron, self.network)
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_create_network_slaac(self):
+ """
+ Tests the neutron_utils.create_network() with an IPv6 subnet where DHCP
+ is True and IPv6 modes are slaac
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'],
+ gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff',
+ enable_dhcp=True, ipv6_ra_mode='slaac', ipv6_address_mode='slaac')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
self.network = neutron_utils.create_network(
- self.neutron, self.os_creds, self.net_config.network_settings)
- self.assertEqual(self.net_config.network_settings.name,
- self.network.name)
- self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.os_creds, self.network_settings)
+ self.assertEqual(self.network_settings.name, self.network.name)
+
+ subnet_settings = self.network_settings.subnet_settings[0]
+ self.assertEqual(1, len(self.network.subnets))
+ subnet = self.network.subnets[0]
+
+ self.assertEqual(self.network.id, subnet.network_id)
+ self.assertEqual(subnet_settings.name, subnet.name)
+ self.assertEqual(subnet_settings.start, subnet.start)
+ self.assertEqual(subnet_settings.end, subnet.end)
+ self.assertEqual('1:1::/64', subnet.cidr)
+ self.assertEqual(6, subnet.ip_version)
+ self.assertEqual(1, len(subnet.dns_nameservers))
+ self.assertEqual(
+ sub_setting.dns_nameservers[0], subnet.dns_nameservers[0])
+ self.assertTrue(subnet.enable_dhcp)
+ self.assertEqual(
+ subnet_settings.ipv6_ra_mode.value, subnet.ipv6_ra_mode)
+ self.assertEqual(
+ subnet_settings.ipv6_address_mode.value, subnet.ipv6_address_mode)
- with self.assertRaises(Exception):
- sub_sets = SubnetSettings(
- cidr='', name=self.net_config.subnet_name)
- neutron_utils.create_subnet(self.neutron, sub_sets, self.os_creds,
- network=self.network)
+ def test_create_network_stateful(self):
+ """
+ Tests the neutron_utils.create_network() with an IPv6 subnet where DHCP
+ is True and IPv6 modes are stateful
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'],
+ gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff',
+ enable_dhcp=True, ipv6_ra_mode='dhcpv6-stateful',
+ ipv6_address_mode='dhcpv6-stateful')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ self.assertEqual(self.network_settings.name, self.network.name)
+
+ subnet_settings = self.network_settings.subnet_settings[0]
+ self.assertEqual(1, len(self.network.subnets))
+ subnet = self.network.subnets[0]
+
+ self.assertEqual(self.network.id, subnet.network_id)
+ self.assertEqual(subnet_settings.name, subnet.name)
+ self.assertEqual(subnet_settings.start, subnet.start)
+ self.assertEqual(subnet_settings.end, subnet.end)
+ self.assertEqual('1:1::/64', subnet.cidr)
+ self.assertEqual(6, subnet.ip_version)
+ self.assertEqual(1, len(subnet.dns_nameservers))
+ self.assertEqual(
+ sub_setting.dns_nameservers[0], subnet.dns_nameservers[0])
+ self.assertTrue(subnet.enable_dhcp)
+ self.assertEqual(
+ subnet_settings.ipv6_ra_mode.value, subnet.ipv6_ra_mode)
+ self.assertEqual(
+ subnet_settings.ipv6_address_mode.value, subnet.ipv6_address_mode)
+
+ def test_create_network_stateless(self):
+ """
+ Tests the neutron_utils.create_network() when DHCP is enabled and
+ the RA and address modes are both 'slaac'
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'],
+ gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff',
+ enable_dhcp=True, ipv6_ra_mode='dhcpv6-stateless',
+ ipv6_address_mode='dhcpv6-stateless')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ self.assertEqual(self.network_settings.name, self.network.name)
+
+ subnet_settings = self.network_settings.subnet_settings[0]
+ self.assertEqual(1, len(self.network.subnets))
+ subnet = self.network.subnets[0]
+
+ self.assertEqual(self.network.id, subnet.network_id)
+ self.assertEqual(subnet_settings.name, subnet.name)
+ self.assertEqual(subnet_settings.start, subnet.start)
+ self.assertEqual(subnet_settings.end, subnet.end)
+ self.assertEqual('1:1::/64', subnet.cidr)
+ self.assertEqual(6, subnet.ip_version)
+ self.assertEqual(1, len(subnet.dns_nameservers))
+ self.assertEqual(
+ sub_setting.dns_nameservers[0], subnet.dns_nameservers[0])
+ self.assertTrue(subnet.enable_dhcp)
+ self.assertEqual(
+ subnet_settings.ipv6_ra_mode.value, subnet.ipv6_ra_mode)
+ self.assertEqual(
+ subnet_settings.ipv6_address_mode.value, subnet.ipv6_address_mode)
+
+ def test_create_network_no_dhcp_slaac(self):
+ """
+ Tests the neutron_utils.create_network() for a BadRequest when
+ DHCP is not enabled and the RA and address modes are both 'slaac'
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64',
+ ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'],
+ gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff',
+ enable_dhcp=False, ipv6_ra_mode='slaac', ipv6_address_mode='slaac')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ with self.assertRaises(BadRequest):
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ def test_create_network_invalid_start_ip(self):
+ """
+ Tests the neutron_utils.create_network() that contains one IPv6 subnet
+ with an invalid start IP to ensure Neutron assigns it the smallest IP
+ possible
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6,
+ start='foo')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ self.assertEqual('1:1::2', self.network.subnets[0].start)
+ self.assertEqual(
+ '1:1:0:ffff:ffff:ffff:ffff:ffff', self.network.subnets[0].end)
+
+ def test_create_network_invalid_end_ip(self):
+ """
+ Tests the neutron_utils.create_network() that contains one IPv6 subnet
+ with an invalid end IP to ensure Neutron assigns it the largest IP
+ possible
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6,
+ end='bar')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ self.assertEqual('1:1::2', self.network.subnets[0].start)
+ self.assertEqual(
+ '1:1:0:ffff:ffff:ffff:ffff:ffff', self.network.subnets[0].end)
+
+ def test_create_network_with_bad_cidr(self):
+ """
+ Tests the neutron_utils.create_network() for a BadRequest when
+ the subnet CIDR is invalid
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1:1:/48', ip_version=6)
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ with self.assertRaises(BadRequest):
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ def test_create_network_invalid_gateway_ip(self):
+ """
+ Tests the neutron_utils.create_network() for a BadRequest when
+ the subnet gateway IP is invalid
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6,
+ gateway_ip='192.168.0.1')
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ with self.assertRaises(BadRequest):
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
+
+ def test_create_network_with_bad_dns(self):
+ """
+ Tests the neutron_utils.create_network() for a BadRequest when
+ the DNS IP is invalid
+ """
+ sub_setting = SubnetConfig(
+ name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6,
+ dns_nameservers=['foo'])
+ self.network_settings = NetworkConfig(
+ name=self.guid + '-net', subnet_settings=[sub_setting])
+
+ with self.assertRaises(BadRequest):
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.network_settings)
class NeutronUtilsRouterTests(OSComponentTestCase):
@@ -279,13 +518,16 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
def setUp(self):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.port_name = str(guid) + '-port'
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
self.network = None
- self.subnet = None
self.port = None
self.router = None
self.interface_router = None
self.net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
router_name=guid + '-pub-router', external_net=self.ext_net_name)
@@ -294,13 +536,15 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
Cleans the remote OpenStack objects
"""
if self.interface_router:
- neutron_utils.remove_interface_router(self.neutron, self.router,
- self.subnet)
+ neutron_utils.remove_interface_router(
+ self.neutron, self.router, self.network.subnets[0])
if self.router:
try:
neutron_utils.delete_router(self.neutron, self.router)
- validate_router(self.neutron, self.router.name, False)
+ validate_router(
+ self.neutron, self.keystone, self.router.name,
+ self.os_creds.project_name, False)
except:
pass
@@ -310,71 +554,41 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
except:
pass
- if self.subnet:
- try:
- neutron_utils.delete_subnet(self.neutron, self.subnet)
- except:
- pass
-
if self.network:
- try:
- neutron_utils.delete_network(self.neutron, self.network)
- except:
- pass
+ neutron_utils.delete_network(self.neutron, self.network)
+
+ super(self.__class__, self).__clean__()
def test_create_router_simple(self):
"""
- Tests the neutron_utils.create_neutron_net() function when an external
- gateway is requested
+ Tests the neutron_utils.create_router()
"""
self.router = neutron_utils.create_router(
self.neutron, self.os_creds, self.net_config.router_settings)
- validate_router(self.neutron, self.net_config.router_settings.name,
- True)
+ validate_router(
+ self.neutron, self.keystone, self.net_config.router_settings.name,
+ self.os_creds.project_name, True)
def test_create_router_with_public_interface(self):
"""
- Tests the neutron_utils.create_neutron_net() function when an external
- gateway is requested
+ Tests the neutron_utils.create_router() function with a pubic interface
"""
subnet_setting = self.net_config.network_settings.subnet_settings[0]
self.net_config = openstack_tests.OSNetworkConfig(
- self.net_config.network_settings.name,
- subnet_setting.name,
- subnet_setting.cidr,
- self.net_config.router_settings.name,
- self.ext_net_name)
+ project_name=self.os_creds.project_name,
+ net_name=self.net_config.network_settings.name,
+ subnet_name=subnet_setting.name, subnet_cidr=subnet_setting.cidr,
+ router_name=self.net_config.router_settings.name,
+ external_gateway=self.ext_net_name)
self.router = neutron_utils.create_router(
self.neutron, self.os_creds, self.net_config.router_settings)
- validate_router(self.neutron, self.net_config.router_settings.name,
- True)
+ validate_router(
+ self.neutron, self.keystone, self.net_config.router_settings.name,
+ self.os_creds.project_name, True)
ext_net = neutron_utils.get_network(
- self.neutron, network_name=self.ext_net_name)
- self.assertEqual(
- self.router.external_gateway_info['network_id'], ext_net.id)
-
- def test_create_router_empty_name(self):
- """
- Tests the neutron_utils.create_neutron_net() function
- """
- with self.assertRaises(Exception):
- this_router_settings = create_router.RouterSettings(name='')
- self.router = neutron_utils.create_router(self.neutron,
- self.os_creds,
- this_router_settings)
-
- def test_create_router_null_name(self):
- """
- Tests the neutron_utils.create_neutron_subnet() function when the
- subnet CIDR value is None
- """
- with self.assertRaises(Exception):
- this_router_settings = create_router.RouterSettings()
- self.router = neutron_utils.create_router(self.neutron,
- self.os_creds,
- this_router_settings)
- validate_router(self.neutron, None, True)
+ self.neutron, self.keystone, network_name=self.ext_net_name)
+ self.assertEqual(self.router.external_network_id, ext_net.id)
def test_add_interface_router(self):
"""
@@ -385,24 +599,25 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting,
- self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
self.router = neutron_utils.create_router(
self.neutron, self.os_creds, self.net_config.router_settings)
- validate_router(self.neutron, self.net_config.router_settings.name,
- True)
+ validate_router(
+ self.neutron, self.keystone, self.net_config.router_settings.name,
+ self.os_creds.project_name, True)
self.interface_router = neutron_utils.add_interface_router(
- self.neutron, self.router, self.subnet)
+ self.neutron, self.router, self.network.subnets[0])
validate_interface_router(self.interface_router, self.router,
- self.subnet)
+ self.network.subnets[0])
def test_add_interface_router_null_router(self):
"""
@@ -414,18 +629,18 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting,
- self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
with self.assertRaises(NeutronException):
self.interface_router = neutron_utils.add_interface_router(
- self.neutron, self.router, self.subnet)
+ self.neutron, self.router, self.network.subnets[0])
def test_add_interface_router_null_subnet(self):
"""
@@ -437,16 +652,46 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
self.router = neutron_utils.create_router(
self.neutron, self.os_creds, self.net_config.router_settings)
- validate_router(self.neutron, self.net_config.router_settings.name,
- True)
+ validate_router(
+ self.neutron, self.keystone, self.net_config.router_settings.name,
+ self.os_creds.project_name, True)
with self.assertRaises(NeutronException):
self.interface_router = neutron_utils.add_interface_router(
- self.neutron, self.router, self.subnet)
+ self.neutron, self.router, None)
+
+ def test_add_interface_router_missing_subnet(self):
+ """
+ Tests the neutron_utils.add_interface_router() function for an
+ Exception when the subnet object has been deleted
+ """
+ self.network = neutron_utils.create_network(
+ self.neutron, self.os_creds, self.net_config.network_settings)
+ self.assertEqual(self.net_config.network_settings.name,
+ self.network.name)
+ self.assertTrue(validate_network(
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
+
+ self.router = neutron_utils.create_router(
+ self.neutron, self.os_creds, self.net_config.router_settings)
+ validate_router(
+ self.neutron, self.keystone, self.net_config.router_settings.name,
+ self.os_creds.project_name, True)
+
+ for subnet in self.network.subnets:
+ neutron_utils.delete_subnet(self.neutron, subnet)
+
+ with self.assertRaises(NotFound):
+ self.interface_router = neutron_utils.add_interface_router(
+ self.neutron, self.router, self.network.subnets[0])
def test_create_port(self):
"""
@@ -457,16 +702,17 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting, self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
self.port = neutron_utils.create_port(
- self.neutron, self.os_creds, PortSettings(
+ self.neutron, self.os_creds, PortConfig(
name=self.port_name,
ip_addrs=[{
'subnet_name': subnet_setting.name,
@@ -483,16 +729,17 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting, self.os_creds, self.network)
- self.assertTrue(validate_subnet(self.neutron, subnet_setting.name,
- subnet_setting.cidr, True))
+ self.assertTrue(validate_subnet(
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
self.port = neutron_utils.create_port(
- self.neutron, self.os_creds, PortSettings(
+ self.neutron, self.os_creds, PortConfig(
name=self.port_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{
@@ -509,18 +756,18 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting,
- self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
self.port = neutron_utils.create_port(
self.neutron, self.os_creds,
- PortSettings(
+ PortConfig(
network_name=self.net_config.network_settings.name,
ip_addrs=[{
'subnet_name': subnet_setting.name,
@@ -537,7 +784,7 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
with self.assertRaises(Exception):
self.port = neutron_utils.create_port(
self.neutron, self.os_creds,
- PortSettings(
+ PortConfig(
name=self.port_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{
@@ -556,19 +803,19 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting,
- self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
with self.assertRaises(Exception):
self.port = neutron_utils.create_port(
self.neutron, self.os_creds,
- PortSettings(
+ PortConfig(
name=self.port_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{
@@ -585,18 +832,19 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting, self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
with self.assertRaises(Exception):
self.port = neutron_utils.create_port(
self.neutron, self.os_creds,
- PortSettings(
+ PortConfig(
name=self.port_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{
@@ -613,18 +861,19 @@ class NeutronUtilsRouterTests(OSComponentTestCase):
self.assertEqual(self.net_config.network_settings.name,
self.network.name)
self.assertTrue(validate_network(
- self.neutron, self.net_config.network_settings.name, True))
+ self.neutron, self.keystone,
+ self.net_config.network_settings.name, True,
+ self.os_creds.project_name))
subnet_setting = self.net_config.network_settings.subnet_settings[0]
- self.subnet = neutron_utils.create_subnet(
- self.neutron, subnet_setting, self.os_creds, self.network)
self.assertTrue(validate_subnet(
- self.neutron, subnet_setting.name, subnet_setting.cidr, True))
+ self.neutron, self.network, subnet_setting.name,
+ subnet_setting.cidr, True))
with self.assertRaises(Exception):
self.port = neutron_utils.create_port(
self.neutron, self.os_creds,
- PortSettings(
+ PortConfig(
name=self.port_name,
network_name=self.net_config.network_settings.name,
ip_addrs=[{
@@ -643,8 +892,10 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase):
self.security_groups = list()
self.security_group_rules = list()
- self.neutron = neutron_utils.neutron_client(self.os_creds)
- self.keystone = keystone_utils.keystone_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
@@ -660,57 +911,57 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_delete_simple_sec_grp(self):
"""
Tests the neutron_utils.create_security_group() function
"""
- sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name)
- security_group = neutron_utils.create_security_group(self.neutron,
- self.keystone,
- sec_grp_settings)
+ sec_grp_settings = SecurityGroupConfig(name=self.sec_grp_name)
+ security_group = neutron_utils.create_security_group(
+ self.neutron, self.keystone, sec_grp_settings)
self.assertTrue(sec_grp_settings.name, security_group.name)
sec_grp_get = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNotNone(sec_grp_get)
self.assertTrue(validation_utils.objects_equivalent(
security_group, sec_grp_get))
neutron_utils.delete_security_group(self.neutron, security_group)
sec_grp_get = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNone(sec_grp_get)
def test_create_sec_grp_no_name(self):
"""
- Tests the SecurityGroupSettings constructor and
+ Tests the SecurityGroupConfig constructor and
neutron_utils.create_security_group() function to ensure that
attempting to create a security group without a name will raise an
exception
"""
with self.assertRaises(Exception):
- sec_grp_settings = SecurityGroupSettings()
+ sec_grp_settings = SecurityGroupConfig()
self.security_groups.append(
- neutron_utils.create_security_group(self.neutron,
- self.keystone,
- sec_grp_settings))
+ neutron_utils.create_security_group(
+ self.neutron, self.keystone, sec_grp_settings))
def test_create_sec_grp_no_rules(self):
"""
Tests the neutron_utils.create_security_group() function
"""
- sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name,
- description='hello group')
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.sec_grp_name, description='hello group')
self.security_groups.append(
- neutron_utils.create_security_group(self.neutron, self.keystone,
- sec_grp_settings))
+ neutron_utils.create_security_group(
+ self.neutron, self.keystone, sec_grp_settings))
self.assertTrue(sec_grp_settings.name, self.security_groups[0].name)
self.assertEqual(sec_grp_settings.name, self.security_groups[0].name)
sec_grp_get = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNotNone(sec_grp_get)
self.assertEqual(self.security_groups[0], sec_grp_get)
@@ -719,30 +970,33 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase):
Tests the neutron_utils.create_security_group() function
"""
- sec_grp_rule_settings = SecurityGroupRuleSettings(
+ sec_grp_rule_settings = SecurityGroupRuleConfig(
sec_grp_name=self.sec_grp_name, direction=Direction.ingress)
- sec_grp_settings = SecurityGroupSettings(
+ sec_grp_settings = SecurityGroupConfig(
name=self.sec_grp_name, description='hello group',
rule_settings=[sec_grp_rule_settings])
self.security_groups.append(
- neutron_utils.create_security_group(self.neutron, self.keystone,
- sec_grp_settings))
+ neutron_utils.create_security_group(
+ self.neutron, self.keystone, sec_grp_settings))
free_rules = neutron_utils.get_rules_by_security_group(
self.neutron, self.security_groups[0])
for free_rule in free_rules:
self.security_group_rules.append(free_rule)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
self.security_group_rules.append(
neutron_utils.create_security_group_rule(
- self.neutron, sec_grp_settings.rule_settings[0]))
+ self.neutron, keystone, sec_grp_settings.rule_settings[0],
+ self.os_creds.project_name))
# Refresh object so it is populated with the newly added rule
security_group = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
- rules = neutron_utils.get_rules_by_security_group(self.neutron,
- security_group)
+ rules = neutron_utils.get_rules_by_security_group(
+ self.neutron, security_group)
self.assertTrue(
validation_utils.objects_equivalent(
@@ -751,7 +1005,7 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase):
self.assertTrue(sec_grp_settings.name, security_group.name)
sec_grp_get = neutron_utils.get_security_group(
- self.neutron, sec_grp_settings=sec_grp_settings)
+ self.neutron, self.keystone, sec_grp_settings=sec_grp_settings)
self.assertIsNotNone(sec_grp_get)
self.assertEqual(security_group, sec_grp_get)
@@ -762,12 +1016,12 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase):
self.security_groups.append(neutron_utils.create_security_group(
self.neutron, self.keystone,
- SecurityGroupSettings(name=self.sec_grp_name + '-1',
- description='hello group')))
+ SecurityGroupConfig(
+ name=self.sec_grp_name + '-1', description='hello group')))
self.security_groups.append(neutron_utils.create_security_group(
self.neutron, self.keystone,
- SecurityGroupSettings(name=self.sec_grp_name + '-2',
- description='hello group')))
+ SecurityGroupConfig(
+ name=self.sec_grp_name + '-2', description='hello group')))
sec_grp_1b = neutron_utils.get_security_group_by_id(
self.neutron, self.security_groups[0].id)
@@ -777,6 +1031,33 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase):
self.assertEqual(self.security_groups[0].id, sec_grp_1b.id)
self.assertEqual(self.security_groups[1].id, sec_grp_2b.id)
+ def test_create_list_sec_grp_no_rules(self):
+ """
+ Tests the neutron_utils.create_security_group() and
+ list_security_groups function
+ """
+ sec_grp_settings = SecurityGroupConfig(
+ name=self.sec_grp_name + "-1", description='hello group')
+ self.security_groups.append(neutron_utils.create_security_group(
+ self.neutron, self.keystone, sec_grp_settings))
+
+ sec_grp_settings2 = SecurityGroupConfig(
+ name=self.sec_grp_name + "-2", description='hola group')
+ self.security_groups.append(neutron_utils.create_security_group(
+ self.neutron, self.keystone, sec_grp_settings2))
+
+ returned_sec_groups = neutron_utils.list_security_groups(self.neutron)
+
+ self.assertIsNotNone(returned_sec_groups)
+ worked = 0
+ for sg in returned_sec_groups:
+ if sec_grp_settings.name == sg.name:
+ worked += 1
+ elif sec_grp_settings2.name == sg.name:
+ worked += 1
+
+ self.assertEqual(worked, 2)
+
class NeutronUtilsFloatingIpTests(OSComponentTestCase):
"""
@@ -788,7 +1069,10 @@ class NeutronUtilsFloatingIpTests(OSComponentTestCase):
Instantiates the CreateImage object that is responsible for downloading
and creating an OS image file within OpenStack
"""
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
self.floating_ip = None
def tearDown(self):
@@ -802,6 +1086,8 @@ class NeutronUtilsFloatingIpTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_floating_ips(self):
"""
Tests the creation of a floating IP
@@ -809,8 +1095,8 @@ class NeutronUtilsFloatingIpTests(OSComponentTestCase):
"""
initial_fips = neutron_utils.get_floating_ips(self.neutron)
- self.floating_ip = neutron_utils.create_floating_ip(self.neutron,
- self.ext_net_name)
+ self.floating_ip = neutron_utils.create_floating_ip(
+ self.neutron, self.keystone, self.ext_net_name)
all_fips = neutron_utils.get_floating_ips(self.neutron)
self.assertEqual(len(initial_fips) + 1, len(all_fips))
returned = neutron_utils.get_floating_ip(self.neutron,
@@ -824,36 +1110,43 @@ Validation routines
"""
-def validate_network(neutron, name, exists):
+def validate_network(neutron, keystone, name, exists, project_name, mtu=None):
"""
Returns true if a network for a given name DOES NOT exist if the exists
parameter is false conversely true. Returns false if a network for a given
name DOES exist if the exists parameter is true conversely false.
:param neutron: The neutron client
+ :param keystone: The keystone client
:param name: The expected network name
:param exists: Whether or not the network name should exist or not
+ :param project_name: the associated project name
:return: True/False
"""
- network = neutron_utils.get_network(neutron, network_name=name)
+ network = neutron_utils.get_network(
+ neutron, keystone, network_name=name, project_name=project_name)
if exists and network:
return True
if not exists and not network:
return True
+ if mtu:
+ return mtu == network.mtu
return False
-def validate_subnet(neutron, name, cidr, exists):
+def validate_subnet(neutron, network, name, cidr, exists):
"""
Returns true if a subnet for a given name DOES NOT exist if the exists
parameter is false conversely true. Returns false if a subnet for a given
name DOES exist if the exists parameter is true conversely false.
:param neutron: The neutron client
+ :param network: The SNAPS-OO Network domain object
:param name: The expected subnet name
:param cidr: The expected CIDR value
:param exists: Whether or not the network name should exist or not
:return: True/False
"""
- subnet = neutron_utils.get_subnet(neutron, subnet_name=name)
+ subnet = neutron_utils.get_subnet(
+ neutron, network, subnet_name=name)
if exists and subnet and subnet.name == name:
return subnet.cidr == cidr
if not exists and not subnet:
@@ -861,17 +1154,21 @@ def validate_subnet(neutron, name, cidr, exists):
return False
-def validate_router(neutron, name, exists):
+def validate_router(neutron, keystone, name, project_name, exists):
"""
Returns true if a router for a given name DOES NOT exist if the exists
parameter is false conversely true. Returns false if a router for a given
name DOES exist if the exists parameter is true conversely false.
:param neutron: The neutron client
+ :param keystone: The keystone client
:param name: The expected router name
+ :param project_name: The name of the project in which the router should
+ exist
:param exists: Whether or not the network name should exist or not
:return: True/False
"""
- router = neutron_utils.get_router(neutron, router_name=name)
+ router = neutron_utils.get_router(
+ neutron, keystone, router_name=name, project_name=project_name)
if exists and router:
return True
return False
diff --git a/snaps/openstack/utils/tests/nova_utils_tests.py b/snaps/openstack/utils/tests/nova_utils_tests.py
index c5b29b5..7c343f8 100644
--- a/snaps/openstack/utils/tests/nova_utils_tests.py
+++ b/snaps/openstack/utils/tests/nova_utils_tests.py
@@ -13,20 +13,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
+import time
import uuid
import os
-import time
from snaps import file_utils
+from snaps.config.flavor import FlavorConfig
+from snaps.config.network import PortConfig
+from snaps.config.vm_inst import VmInstanceConfig
+from snaps.config.volume import VolumeConfig
from snaps.openstack import create_instance
-from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor
+from snaps.openstack.create_flavor import OpenStackFlavor
from snaps.openstack.create_image import OpenStackImage
-from snaps.openstack.create_instance import VmInstanceSettings
-from snaps.openstack.create_network import OpenStackNetwork, PortSettings
+from snaps.openstack.create_instance import OpenStackVmInstance
+from snaps.openstack.create_network import OpenStackNetwork
+from snaps.openstack.create_volume import OpenStackVolume
from snaps.openstack.tests import openstack_tests
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
-from snaps.openstack.utils import nova_utils, neutron_utils, glance_utils
+from snaps.openstack.utils import (
+ nova_utils, neutron_utils, glance_utils, cinder_utils, keystone_utils)
+from snaps.openstack.utils.nova_utils import NovaException
__author__ = 'spisarski'
@@ -42,11 +49,21 @@ class NovaSmokeTests(OSComponentTestCase):
"""
Tests to ensure that the proper credentials can connect.
"""
- nova = nova_utils.nova_client(self.os_creds)
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
# This should not throw an exception
nova.flavors.list()
+ def test_nova_get_hypervisor_hosts(self):
+ """
+ Tests to ensure that get_hypervisors() function works.
+ """
+ nova = nova_utils.nova_client(self.os_creds, self.os_session)
+
+ hosts = nova_utils.get_hypervisor_hosts(nova)
+ # This should not throw an exception
+ self.assertGreaterEqual(len(hosts), 1)
+
def test_nova_connect_fail(self):
"""
Tests to ensure that the improper credentials cannot connect.
@@ -78,7 +95,7 @@ class NovaUtilsKeypairTests(OSComponentTestCase):
self.priv_key_file_path = 'tmp/' + guid
self.pub_key_file_path = self.priv_key_file_path + '.pub'
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
self.keys = nova_utils.create_keys()
self.public_key = nova_utils.public_key_openssh(self.keys)
self.keypair_name = guid
@@ -106,6 +123,8 @@ class NovaUtilsKeypairTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_keypair(self):
"""
Tests the creation of an OpenStack keypair that does not exist.
@@ -156,12 +175,10 @@ class NovaUtilsFlavorTests(OSComponentTestCase):
and creating an OS image file within OpenStack
"""
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.flavor_settings = FlavorSettings(name=guid + '-name',
- flavor_id=guid + '-id', ram=1,
- disk=1, vcpus=1,
- ephemeral=1, swap=2,
- rxtx_factor=3.0, is_public=False)
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.flavor_settings = FlavorConfig(
+ name=guid + '-name', flavor_id=guid + '-id', ram=1, disk=1,
+ vcpus=1, ephemeral=1, swap=2, rxtx_factor=3.0, is_public=False)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
self.flavor = None
def tearDown(self):
@@ -174,6 +191,8 @@ class NovaUtilsFlavorTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_flavor(self):
"""
Tests the creation of an OpenStack keypair that does not exist.
@@ -227,9 +246,14 @@ class NovaUtilsInstanceTests(OSComponentTestCase):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.nova = nova_utils.nova_client(self.os_creds)
- self.neutron = neutron_utils.neutron_client(self.os_creds)
- self.glance = glance_utils.glance_client(self.os_creds)
+ self.nova = nova_utils.nova_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ self.glance = glance_utils.glance_client(
+ self.os_creds, self.os_session)
self.image_creator = None
self.network_creator = None
@@ -245,23 +269,25 @@ class NovaUtilsInstanceTests(OSComponentTestCase):
self.image_creator.create()
network_settings = openstack_tests.get_priv_net_config(
- guid + '-net', guid + '-subnet').network_settings
+ project_name=self.os_creds.project_name,
+ net_name="{}-{}".format(guid, 'net'),
+ subnet_name="{}-{}".format(guid, 'subnet')).network_settings
self.network_creator = OpenStackNetwork(
self.os_creds, network_settings)
self.network_creator.create()
- self.flavor_creator = OpenStackFlavor(
- self.os_creds,
- FlavorSettings(
- name=guid + '-flavor-name', ram=256, disk=10, vcpus=1))
+ flavor_config = openstack_tests.get_flavor_config(
+ name="{}-{}".format(guid, 'flavor-name'), ram=256, disk=10,
+ vcpus=1, metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_config)
self.flavor_creator.create()
- port_settings = PortSettings(name=guid + '-port',
- network_name=network_settings.name)
+ port_settings = PortConfig(
+ name=guid + '-port', network_name=network_settings.name)
self.port = neutron_utils.create_port(
self.neutron, self.os_creds, port_settings)
- self.instance_settings = VmInstanceSettings(
+ self.instance_settings = VmInstanceConfig(
name=guid + '-vm_inst',
flavor=self.flavor_creator.flavor_settings.name,
port_settings=[port_settings])
@@ -300,6 +326,8 @@ class NovaUtilsInstanceTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_create_instance(self):
"""
Tests the nova_utils.create_server() method
@@ -307,25 +335,280 @@ class NovaUtilsInstanceTests(OSComponentTestCase):
"""
self.vm_inst = nova_utils.create_server(
- self.nova, self.neutron, self.glance, self.instance_settings,
- self.image_creator.image_settings)
+ self.nova, self.keystone, self.neutron, self.glance,
+ self.instance_settings, self.image_creator.image_settings,
+ self.os_creds.project_name)
self.assertIsNotNone(self.vm_inst)
# Wait until instance is ACTIVE
iters = 0
active = False
+ status = None
while iters < 60:
- if create_instance.STATUS_ACTIVE == nova_utils.get_server_status(
- self.nova, self.vm_inst):
+ status = nova_utils.get_server_status(self.nova, self.vm_inst)
+ if create_instance.STATUS_ACTIVE == status:
active = True
break
time.sleep(3)
iters += 1
- self.assertTrue(active)
- vm_inst = nova_utils.get_latest_server_object(self.nova, self.vm_inst)
+ self.assertTrue(active, msg='VM {} status {} is not {}'.format(
+ self.vm_inst.name, status, create_instance.STATUS_ACTIVE))
+ vm_inst = nova_utils.get_latest_server_object(
+ self.nova, self.neutron, self.keystone, self.vm_inst,
+ self.os_creds.project_name)
self.assertEqual(self.vm_inst.name, vm_inst.name)
self.assertEqual(self.vm_inst.id, vm_inst.id)
+
+
+class NovaUtilsInstanceVolumeTests(OSComponentTestCase):
+ """
+ Tests the creation of VM instances via nova_utils.py
+ """
+
+ def setUp(self):
+ """
+ Setup objects required by VM instances
+ :return:
+ """
+
+ guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
+
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
+ self.cinder = cinder_utils.cinder_client(
+ self.os_creds, self.os_session)
+
+ self.image_creator = None
+ self.network_creator = None
+ self.flavor_creator = None
+ self.volume_creator = None
+ self.instance_creator = None
+
+ try:
+ image_settings = openstack_tests.cirros_image_settings(
+ name=guid + '-image', image_metadata=self.image_metadata)
+ self.image_creator = OpenStackImage(
+ self.os_creds, image_settings=image_settings)
+ self.image_creator.create()
+
+ network_settings = openstack_tests.get_priv_net_config(
+ project_name=self.os_creds.project_name,
+ net_name="{}-{}".format(guid, 'net'),
+ subnet_name="{}-{}".format(guid, 'subnet')).network_settings
+
+ self.network_creator = OpenStackNetwork(
+ self.os_creds, network_settings)
+ self.network_creator.create()
+
+ flavor_settings = openstack_tests.get_flavor_config(
+ name=guid + '-flavor', ram=256, disk=10, vcpus=1,
+ metadata=self.flavor_metadata)
+ self.flavor_creator = OpenStackFlavor(
+ self.os_creds, flavor_settings)
+ self.flavor_creator.create()
+
+ # Create Volume
+ volume_settings = VolumeConfig(
+ name=self.__class__.__name__ + '-' + str(guid))
+ self.volume_creator = OpenStackVolume(
+ self.os_creds, volume_settings)
+ self.volume_creator.create(block=True)
+
+ port_settings = PortConfig(
+ name=guid + '-port', network_name=network_settings.name)
+ instance_settings = VmInstanceConfig(
+ name=guid + '-vm_inst',
+ flavor=self.flavor_creator.flavor_settings.name,
+ port_settings=[port_settings])
+ self.instance_creator = OpenStackVmInstance(
+ self.os_creds, instance_settings, image_settings)
+ self.instance_creator.create(block=True)
+ except:
+ self.tearDown()
+ raise
+
+ def tearDown(self):
+ """
+ Cleanup deployed resources
+ :return:
+ """
+ if self.instance_creator:
+ try:
+ self.instance_creator.clean()
+ except:
+ pass
+ if self.volume_creator:
+ try:
+ self.volume_creator.clean()
+ except:
+ pass
+ if self.flavor_creator:
+ try:
+ self.flavor_creator.clean()
+ except:
+ pass
+ if self.network_creator:
+ try:
+ self.network_creator.clean()
+ except:
+ pass
+ if self.image_creator:
+ try:
+ self.image_creator.clean()
+ except:
+ pass
+
+ super(self.__class__, self).__clean__()
+
+ def test_add_remove_volume(self):
+ """
+ Tests the nova_utils.attach_volume() and detach_volume functions with
+ a timeout value
+ :return:
+ """
+
+ self.assertIsNotNone(self.volume_creator.get_volume())
+ self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
+
+ # Attach volume to VM
+ neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ self.assertIsNotNone(nova_utils.attach_volume(
+ self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
+ self.volume_creator.get_volume(), self.os_creds.project_name))
+
+ vol_attach = None
+ vol_detach = None
+ attached = False
+ start_time = time.time()
+ while time.time() < start_time + 120:
+ vol_attach = cinder_utils.get_volume_by_id(
+ self.cinder, self.volume_creator.get_volume().id)
+
+ if len(vol_attach.attachments) > 0:
+ attached = True
+ break
+
+ time.sleep(3)
+
+ self.assertTrue(attached)
+ self.assertIsNotNone(vol_attach)
+
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ vm_attach = nova_utils.get_server_object_by_id(
+ self.nova, neutron, keystone,
+ self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
+
+ # Validate Attachment
+ self.assertIsNotNone(vol_attach)
+ self.assertEqual(self.volume_creator.get_volume().id, vol_attach.id)
+ self.assertEqual(1, len(vol_attach.attachments))
+ self.assertEqual(vm_attach.volume_ids[0]['id'],
+ vol_attach.attachments[0]['volume_id'])
+
+ # Detach volume to VM
+ self.assertIsNotNone(nova_utils.detach_volume(
+ self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
+ self.volume_creator.get_volume(), self.os_creds.project_name))
+
+ start_time = time.time()
+ while time.time() < start_time + 120:
+ vol_detach = cinder_utils.get_volume_by_id(
+ self.cinder, self.volume_creator.get_volume().id)
+ if len(vol_detach.attachments) == 0:
+ attached = False
+ break
+
+ time.sleep(3)
+
+ self.assertFalse(attached)
+ self.assertIsNotNone(vol_detach)
+
+ vm_detach = nova_utils.get_server_object_by_id(
+ self.nova, neutron, keystone,
+ self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
+
+ # Validate Detachment
+ self.assertIsNotNone(vol_detach)
+ self.assertEqual(self.volume_creator.get_volume().id, vol_detach.id)
+
+ self.assertEqual(0, len(vol_detach.attachments))
+ self.assertEqual(0, len(vm_detach.volume_ids))
+
+ def test_attach_volume_nowait(self):
+ """
+ Tests the nova_utils.attach_volume() with a timeout value that is too
+ small to have the volume attachment data to be included on the VmInst
+ object that was supposed to be returned
+ """
+
+ self.assertIsNotNone(self.volume_creator.get_volume())
+ self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
+
+ # Attach volume to VM
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ with self.assertRaises(NovaException):
+ nova_utils.attach_volume(
+ self.nova, neutron, keystone,
+ self.instance_creator.get_vm_inst(),
+ self.volume_creator.get_volume(), self.os_creds.project_name,
+ 0)
+
+ def test_detach_volume_nowait(self):
+ """
+ Tests the nova_utils.detach_volume() with a timeout value that is too
+ small to have the volume attachment data to be included on the VmInst
+ object that was supposed to be returned
+ """
+
+ self.assertIsNotNone(self.volume_creator.get_volume())
+ self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
+
+ # Attach volume to VM
+ neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ nova_utils.attach_volume(
+ self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
+ self.volume_creator.get_volume(), self.os_creds.project_name)
+
+ # Check VmInst for attachment
+ keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ latest_vm = nova_utils.get_server_object_by_id(
+ self.nova, neutron, keystone,
+ self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
+ self.assertEqual(1, len(latest_vm.volume_ids))
+
+ # Check Volume for attachment
+ vol_attach = None
+ attached = False
+ start_time = time.time()
+ while time.time() < start_time + 120:
+ vol_attach = cinder_utils.get_volume_by_id(
+ self.cinder, self.volume_creator.get_volume().id)
+
+ if len(vol_attach.attachments) > 0:
+ attached = True
+ break
+
+ time.sleep(3)
+
+ self.assertTrue(attached)
+ self.assertIsNotNone(vol_attach)
+
+ # Detach volume
+ with self.assertRaises(NovaException):
+ nova_utils.detach_volume(
+ self.nova, neutron, keystone,
+ self.instance_creator.get_vm_inst(),
+ self.volume_creator.get_volume(), self.os_creds.project_name,
+ 0)
diff --git a/snaps/openstack/utils/tests/settings_utils_tests.py b/snaps/openstack/utils/tests/settings_utils_tests.py
index f84e6a0..3d080d4 100644
--- a/snaps/openstack/utils/tests/settings_utils_tests.py
+++ b/snaps/openstack/utils/tests/settings_utils_tests.py
@@ -13,21 +13,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
+import unittest
+
import os
import uuid
+from snaps.config.network import SubnetConfig, NetworkConfig, PortConfig
+from snaps.config.flavor import FlavorConfig
+from snaps.config.keypair import KeypairConfig
+from snaps.config.qos import Consumer
+from snaps.config.security_group import (
+ SecurityGroupRuleConfig, Direction, Protocol, SecurityGroupConfig)
+from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
+from snaps.domain.flavor import Flavor
+from snaps.domain.volume import (
+ Volume, VolumeType, VolumeTypeEncryption, QoSSpec)
from snaps.openstack import (
create_image, create_network, create_router, create_flavor,
create_keypairs, create_instance)
-from snaps.openstack.create_network import (
- NetworkSettings, OpenStackNetwork, SubnetSettings)
-from snaps.openstack.create_security_group import (
- SecurityGroupRuleSettings, Direction, Protocol, OpenStackSecurityGroup,
- SecurityGroupSettings)
+from snaps.openstack.create_qos import Consumer
+from snaps.openstack.create_network import OpenStackNetwork
+from snaps.openstack.create_security_group import OpenStackSecurityGroup
from snaps.openstack.tests import openstack_tests
from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
from snaps.openstack.utils import (
- neutron_utils, settings_utils, nova_utils, glance_utils)
+ neutron_utils, settings_utils, nova_utils, glance_utils, keystone_utils)
__author__ = 'spisarski'
@@ -36,7 +46,7 @@ logger = logging.getLogger('nova_utils_tests')
class SettingsUtilsNetworkingTests(OSComponentTestCase):
"""
- Tests the ability to reverse engineer NetworkSettings objects from existing
+ Tests the ability to reverse engineer NetworkConfig objects from existing
networks deployed to OpenStack
"""
@@ -48,7 +58,8 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase):
self.network_name = guid + '-net'
self.subnet_name = guid + '-subnet'
self.net_creator = None
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
def tearDown(self):
"""
@@ -60,18 +71,20 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase):
except:
pass
+ super(self.__class__, self).__clean__()
+
def test_derive_net_settings_no_subnet(self):
"""
- Validates the utility function settings_utils#create_network_settings
- returns an acceptable NetworkSettings object and ensures that the
+ Validates the utility function settings_utils#create_network_config
+ returns an acceptable NetworkConfig object and ensures that the
new settings object will not cause the new OpenStackNetwork instance
to create another network
"""
- net_settings = NetworkSettings(name=self.network_name)
+ net_settings = NetworkConfig(name=self.network_name)
self.net_creator = OpenStackNetwork(self.os_creds, net_settings)
network = self.net_creator.create()
- derived_settings = settings_utils.create_network_settings(
+ derived_settings = settings_utils.create_network_config(
self.neutron, network)
self.assertIsNotNone(derived_settings)
@@ -89,18 +102,18 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase):
def test_derive_net_settings_two_subnets(self):
"""
- Validates the utility function settings_utils#create_network_settings
- returns an acceptable NetworkSettings object
+ Validates the utility function settings_utils#create_network_config
+ returns an acceptable NetworkConfig object
"""
subnet_settings = list()
- subnet_settings.append(SubnetSettings(name='sub1', cidr='10.0.0.0/24'))
- subnet_settings.append(SubnetSettings(name='sub2', cidr='10.0.1.0/24'))
- net_settings = NetworkSettings(name=self.network_name,
- subnet_settings=subnet_settings)
+ subnet_settings.append(SubnetConfig(name='sub1', cidr='10.0.0.0/24'))
+ subnet_settings.append(SubnetConfig(name='sub2', cidr='10.0.1.0/24'))
+ net_settings = NetworkConfig(
+ name=self.network_name, subnet_settings=subnet_settings)
self.net_creator = OpenStackNetwork(self.os_creds, net_settings)
network = self.net_creator.create()
- derived_settings = settings_utils.create_network_settings(
+ derived_settings = settings_utils.create_network_config(
self.neutron, network)
self.assertIsNotNone(derived_settings)
@@ -135,7 +148,7 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase):
class SettingsUtilsVmInstTests(OSComponentTestCase):
"""
- Tests the ability to reverse engineer VmInstanceSettings objects from
+ Tests the ability to reverse engineer VmInstanceConfig objects from
existing VMs/servers deployed to OpenStack
"""
@@ -144,11 +157,14 @@ class SettingsUtilsVmInstTests(OSComponentTestCase):
Instantiates the CreateImage object that is responsible for downloading
and creating an OS image file within OpenStack
"""
- # super(self.__class__, self).__start__()
-
- self.nova = nova_utils.nova_client(self.os_creds)
- self.glance = glance_utils.glance_client(self.os_creds)
- self.neutron = neutron_utils.neutron_client(self.os_creds)
+ self.nova = nova_utils.nova_client(
+ self.os_creds, self.os_session)
+ self.keystone = keystone_utils.keystone_client(
+ self.os_creds, self.os_session)
+ self.glance = glance_utils.glance_client(
+ self.os_creds, self.os_session)
+ self.neutron = neutron_utils.neutron_client(
+ self.os_creds, self.os_session)
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.keypair_priv_filepath = 'tmp/' + guid
@@ -180,6 +196,7 @@ class SettingsUtilsVmInstTests(OSComponentTestCase):
# First network is public
self.pub_net_config = openstack_tests.get_pub_net_config(
+ project_name=self.os_creds.project_name,
net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
router_name=guid + '-pub-router',
external_net=self.ext_net_name)
@@ -196,13 +213,13 @@ class SettingsUtilsVmInstTests(OSComponentTestCase):
# Create Flavor
self.flavor_creator = create_flavor.OpenStackFlavor(
self.os_creds,
- create_flavor.FlavorSettings(name=guid + '-flavor-name',
- ram=256, disk=1, vcpus=1))
+ FlavorConfig(
+ name=guid + '-flavor-name', ram=256, disk=1, vcpus=1))
self.flavor_creator.create()
# Create Key/Pair
self.keypair_creator = create_keypairs.OpenStackKeypair(
- self.os_creds, create_keypairs.KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name,
public_filepath=self.keypair_pub_filepath,
private_filepath=self.keypair_priv_filepath))
@@ -210,32 +227,30 @@ class SettingsUtilsVmInstTests(OSComponentTestCase):
# Create Security Group
sec_grp_name = guid + '-sec-grp'
- rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.icmp)
- rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.tcp,
- port_range_min=22,
- port_range_max=22)
+ rule1 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.icmp)
+ rule2 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.tcp, port_range_min=22, port_range_max=22)
self.sec_grp_creator = OpenStackSecurityGroup(
self.os_creds,
- SecurityGroupSettings(name=sec_grp_name,
- rule_settings=[rule1, rule2]))
+ SecurityGroupConfig(
+ name=sec_grp_name, rule_settings=[rule1, rule2]))
self.sec_grp_creator.create()
# Create instance
ports_settings = list()
ports_settings.append(
- create_network.PortSettings(
+ PortConfig(
name=self.port_1_name,
network_name=self.pub_net_config.network_settings.name))
- instance_settings = create_instance.VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=ports_settings,
- floating_ip_settings=[create_instance.FloatingIpSettings(
+ floating_ip_settings=[FloatingIpConfig(
name=self.floating_ip_name, port_name=self.port_1_name,
router_name=self.pub_net_config.router_settings.name)])
@@ -308,19 +323,21 @@ class SettingsUtilsVmInstTests(OSComponentTestCase):
if os.path.isfile(self.test_file_local_path):
os.remove(self.test_file_local_path)
- # super(self.__class__, self).__clean__()
+ super(self.__class__, self).__clean__()
- def test_derive_vm_inst_settings(self):
+ def test_derive_vm_inst_config(self):
"""
- Validates the utility function settings_utils#create_vm_inst_settings
- returns an acceptable VmInstanceSettings object
+ Validates the utility function settings_utils#create_vm_inst_config
+ returns an acceptable VmInstanceConfig object
"""
self.inst_creator.create(block=True)
server = nova_utils.get_server(
- self.nova, vm_inst_settings=self.inst_creator.instance_settings)
- derived_vm_settings = settings_utils.create_vm_inst_settings(
- self.nova, self.neutron, server)
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=self.inst_creator.instance_settings)
+ derived_vm_settings = settings_utils.create_vm_inst_config(
+ self.nova, self.keystone, self.neutron, server,
+ self.os_creds.project_name)
self.assertIsNotNone(derived_vm_settings)
self.assertIsNotNone(derived_vm_settings.port_settings)
self.assertIsNotNone(derived_vm_settings.floating_ip_settings)
@@ -328,14 +345,73 @@ class SettingsUtilsVmInstTests(OSComponentTestCase):
def test_derive_image_settings(self):
"""
Validates the utility function settings_utils#create_image_settings
- returns an acceptable ImageSettings object
+ returns an acceptable ImageConfig object
"""
self.inst_creator.create(block=True)
server = nova_utils.get_server(
- self.nova, vm_inst_settings=self.inst_creator.instance_settings)
- derived_image_settings = settings_utils.determine_image_settings(
+ self.nova, self.neutron, self.keystone,
+ vm_inst_settings=self.inst_creator.instance_settings)
+ derived_image_settings = settings_utils.determine_image_config(
self.glance, server, [self.image_creator.image_settings])
self.assertIsNotNone(derived_image_settings)
self.assertEqual(self.image_creator.image_settings.name,
derived_image_settings.name)
+
+
+class SettingsUtilsUnitTests(unittest.TestCase):
+ """
+ Exercises the settings_utils.py functions around volumes
+ """
+
+ def test_vol_settings_from_vol(self):
+ volume = Volume(
+ name='vol-name', volume_id='vol-id', project_id='proj-id',
+ description='desc', size=99, vol_type='vol-type',
+ availability_zone='zone1', multi_attach=True)
+ settings = settings_utils.create_volume_config(volume)
+ self.assertEqual(volume.name, settings.name)
+ self.assertEqual(volume.description, settings.description)
+ self.assertEqual(volume.size, settings.size)
+ self.assertEqual(volume.type, settings.type_name)
+ self.assertEqual(volume.availability_zone, settings.availability_zone)
+ self.assertEqual(volume.multi_attach, settings.multi_attach)
+
+ def test_vol_type_settings_from_vol(self):
+ encryption = VolumeTypeEncryption(
+ volume_encryption_id='vol-encrypt-id', volume_type_id='vol-typ-id',
+ control_location='front-end', provider='FooClass', cipher='1',
+ key_size=1)
+ qos_spec = QoSSpec(name='qos-spec-name', spec_id='qos-spec-id',
+ consumer=Consumer.back_end)
+ volume_type = VolumeType(
+ name='vol-type-name', volume_type_id='vol-type-id', public=True,
+ encryption=encryption, qos_spec=qos_spec)
+
+ settings = settings_utils.create_volume_type_config(volume_type)
+ self.assertEqual(volume_type.name, settings.name)
+ self.assertEqual(volume_type.public, settings.public)
+
+ encrypt_settings = settings.encryption
+ self.assertIsNotNone(encrypt_settings)
+ self.assertEqual(encryption.control_location,
+ encrypt_settings.control_location.value)
+ self.assertEqual(encryption.cipher, encrypt_settings.cipher)
+ self.assertEqual(encryption.key_size, encrypt_settings.key_size)
+
+ self.assertEqual(qos_spec.name, settings.qos_spec_name)
+
+ def test_flavor_settings_from_flavor(self):
+ flavor = Flavor(
+ name='flavor-name', flavor_id='flavor-id', ram=99, disk=101,
+ vcpus=9, ephemeral=3, swap=5, rxtx_factor=7, is_public=False)
+ settings = settings_utils.create_flavor_config(flavor)
+ self.assertEqual(flavor.name, settings.name)
+ self.assertEqual(flavor.id, settings.flavor_id)
+ self.assertEqual(flavor.ram, settings.ram)
+ self.assertEqual(flavor.disk, settings.disk)
+ self.assertEqual(flavor.vcpus, settings.vcpus)
+ self.assertEqual(flavor.ephemeral, settings.ephemeral)
+ self.assertEqual(flavor.swap, settings.swap)
+ self.assertEqual(flavor.rxtx_factor, settings.rxtx_factor)
+ self.assertEqual(flavor.is_public, settings.is_public)
diff --git a/snaps/playbook_runner.py b/snaps/playbook_runner.py
index 4dba550..7b10390 100644
--- a/snaps/playbook_runner.py
+++ b/snaps/playbook_runner.py
@@ -13,10 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
+import ast
import logging
+import os
import re
+import yaml
+from jinja2 import Environment, FileSystemLoader
+
from snaps.openstack.os_credentials import ProxySettings
from snaps.provisioning import ansible_utils
@@ -40,32 +45,59 @@ def main(parsed_args):
ssh_proxy_cmd=parsed_args.ssh_proxy_cmd)
# Ensure can get an SSH client
- ssh = ansible_utils.ssh_client(parsed_args.ip_addr, parsed_args.host_user,
- parsed_args.priv_key, proxy_settings)
+ ssh = ansible_utils.ssh_client(
+ parsed_args.ip_addr, parsed_args.host_user,
+ private_key_filepath=parsed_args.priv_key,
+ proxy_settings=proxy_settings)
if ssh:
ssh.close()
- retval = ansible_utils.apply_playbook(
+ env = Environment(loader=FileSystemLoader(
+ searchpath=os.path.dirname(parsed_args.env_file)))
+ template = env.get_template(os.path.basename(parsed_args.env_file))
+
+ env_dict = dict()
+ if parsed_args.vars:
+ env_dict = ast.literal_eval(parsed_args.vars)
+
+ output = template.render(**env_dict)
+
+ variables = yaml.load(output)
+
+ if not variables.get('env_file'):
+ variables['env_file'] = parsed_args.env_file
+
+ ansible_utils.apply_playbook(
parsed_args.playbook, [parsed_args.ip_addr], parsed_args.host_user,
- parsed_args.priv_key, variables={'name': 'Foo'},
+ ssh_priv_key_file_path=parsed_args.priv_key,
+ password=parsed_args.password, variables=variables,
proxy_setting=proxy_settings)
- exit(retval)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--ip-addr', dest='ip_addr', required=True,
help='The Host IP Address')
- parser.add_argument('-k', '--priv-key', dest='priv_key', required=True,
- help='The location of the private key file')
parser.add_argument('-u', '--host-user', dest='host_user', required=True,
help='Host user account')
+ parser.add_argument('-k', '--priv-key', dest='priv_key', required=False,
+ help='The location of the private key file')
+ parser.add_argument('-pw', '--password', dest='password', required=False,
+ help='The host-user password')
parser.add_argument('-b', '--playbook', dest='playbook', required=True,
help='Playbook Location')
parser.add_argument('-p', '--http-proxy', dest='http_proxy',
required=False, help='<host>:<port>')
parser.add_argument('-s', '--ssh-proxy-cmd', dest='ssh_proxy_cmd',
required=False)
+ parser.add_argument('-e', '--env-file', dest='env_file',
+ help='Yaml file containing playbook substitution vals',
+ required=False)
+ parser.add_argument('-v', '--vars', dest='vars',
+ help='String renditon of a dict to pass into '
+ 'playbook for additional subtitution values not '
+ 'found in env_file',
+ required=False)
args = parser.parse_args()
main(args)
diff --git a/snaps/provisioning/ansible_pb/__init__.py b/snaps/provisioning/ansible_pb/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/snaps/provisioning/ansible_pb/__init__.py
+++ /dev/null
diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/__init__.py b/snaps/provisioning/ansible_pb/centos-network-setup/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/snaps/provisioning/ansible_pb/centos-network-setup/__init__.py
+++ /dev/null
diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py b/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py
+++ /dev/null
diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface b/snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface
deleted file mode 100644
index 47aa3fa..0000000
--- a/snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface
+++ /dev/null
@@ -1,14 +0,0 @@
-DEVICE={{ nic_name }}
-NAME={{ nic_name }}
-IPADDR={{ nic_ip }}
-
-DEFROUTE=no
-NETMASK=255.255.255.0
-NM_CONTROLLED=no
-IPV6INIT=yes
-IPV6_AUTOCONF=yes
-IPV6_DEFROUTE=yes
-IPV6_PEERDNS=yes
-IPV6_PEERROUTES=yes
-IPV6_FAILURE_FATAL=no
-ONBOOT=yes \ No newline at end of file
diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py b/snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py
+++ /dev/null
diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py b/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py
+++ /dev/null
diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg b/snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg
deleted file mode 100644
index 3fa7708..0000000
--- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-auto {{ nic_name }}
-iface {{ nic_name }} inet dhcp
diff --git a/snaps/provisioning/ansible_utils.py b/snaps/provisioning/ansible_utils.py
index 63f26e1..019a8e7 100644
--- a/snaps/provisioning/ansible_utils.py
+++ b/snaps/provisioning/ansible_utils.py
@@ -21,52 +21,80 @@ import paramiko
try:
from ansible.parsing.dataloader import DataLoader
- from ansible.vars import VariableManager
- from ansible.inventory import Inventory
+ from ansible.vars.manager import VariableManager
+ from ansible.inventory.manager import InventoryManager
from ansible.executor.playbook_executor import PlaybookExecutor
except:
pass
__author__ = 'spisarski'
+from warnings import warn
+warn('This utility will be removed in a subsequent release',
+ DeprecationWarning)
+
logger = logging.getLogger('ansible_utils')
-def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
- variables=None, proxy_setting=None):
+def apply_playbook(playbook_path, hosts_inv=None, host_user=None,
+ ssh_priv_key_file_path=None, password=None, variables=None,
+ proxy_setting=None):
"""
Executes an Ansible playbook to the given host
:param playbook_path: the (relative) path to the Ansible playbook
:param hosts_inv: a list of hostnames/ip addresses to which to apply the
- Ansible playbook
+ Ansible playbook (not required when PB is configured for
+ localhost)
:param host_user: A user for the host instances (must be a password-less
- sudo user if playbook has "sudo: yes"
- :param ssh_priv_key_file_path: the file location of the ssh key
+ sudo user if playbook has "sudo: yes") (not required when
+ PB is configured for localhost)
+ :param ssh_priv_key_file_path: the file location of the ssh key. Required
+ if password is None (not required when PB is
+ configured for localhost)
+ :param password: the file location of the ssh key. Required if
+ ssh_priv_key_file_path is None (not required when PB is
+ configured for localhost)
:param variables: a dictionary containing any substitution variables needed
by the Jinga 2 templates
:param proxy_setting: instance of os_credentials.ProxySettings class
- :return: the results
+ :raises AnsibleException when the return code from the Ansible library is
+ not 0
+ :return: the return code from the Ansible library only when 0.
+ Implementation now raises an exception otherwise
"""
if not os.path.isfile(playbook_path):
- raise AnsibleException('Requested playbook not found - ' + playbook_path)
+ raise AnsibleException(
+ 'Requested playbook not found - ' + playbook_path)
+
+ pk_file_path = None
+ if ssh_priv_key_file_path:
+ pk_file_path = os.path.expanduser(ssh_priv_key_file_path)
+ if not password:
+ if not os.path.isfile(pk_file_path):
+ raise AnsibleException(
+ 'Requested private SSH key not found - ' + pk_file_path)
- pk_file_path = os.path.expanduser(ssh_priv_key_file_path)
- if not os.path.isfile(pk_file_path):
- raise AnsibleException('Requested private SSH key not found - ' +
- pk_file_path)
+ passwords = None
+ if password:
+ passwords = {'conn_pass': password, 'become_pass': password}
import ansible.constants
ansible.constants.HOST_KEY_CHECKING = False
- variable_manager = VariableManager()
+ loader = DataLoader()
+ inventory = InventoryManager(loader=loader)
+ if hosts_inv:
+ for host in hosts_inv:
+ inventory.add_host(host=host, group='ungrouped')
+ connection = 'ssh'
+ else:
+ connection = 'local'
+
+ variable_manager = VariableManager(loader=loader, inventory=inventory)
+
if variables:
variable_manager.extra_vars = variables
- loader = DataLoader()
- inventory = Inventory(loader=loader, variable_manager=variable_manager,
- host_list=hosts_inv)
- variable_manager.set_inventory(inventory)
-
ssh_extra_args = None
if proxy_setting and proxy_setting.ssh_proxy_cmd:
ssh_extra_args = '-o ProxyCommand=\'%s\'' % proxy_setting.ssh_proxy_cmd
@@ -76,14 +104,15 @@ def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
'connection', 'module_path', 'forks', 'remote_user',
'private_key_file', 'ssh_common_args', 'ssh_extra_args',
'become', 'become_method', 'become_user', 'verbosity',
- 'check', 'timeout'])
+ 'check', 'timeout', 'diff'])
ansible_opts = options(
listtags=False, listtasks=False, listhosts=False, syntax=False,
- connection='ssh', module_path=None, forks=100, remote_user=host_user,
- private_key_file=pk_file_path, ssh_common_args=None,
- ssh_extra_args=ssh_extra_args, become=None, become_method=None,
- become_user=None, verbosity=11111, check=False, timeout=30)
+ connection=connection, module_path=None, forks=100,
+ remote_user=host_user, private_key_file=pk_file_path,
+ ssh_common_args=None, ssh_extra_args=ssh_extra_args, become=None,
+ become_method=None, become_user=None, verbosity=11111, check=False,
+ timeout=30, diff=None)
logger.debug('Setting up Ansible Playbook Executor for playbook - ' +
playbook_path)
@@ -93,18 +122,28 @@ def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
variable_manager=variable_manager,
loader=loader,
options=ansible_opts,
- passwords=None)
+ passwords=passwords)
logger.debug('Executing Ansible Playbook - ' + playbook_path)
- return executor.run()
+ ret_val = executor.run()
+
+ if ret_val != 0:
+ raise AnsibleException(
+ 'Error applying playbook [{}] with value [{}] using the connection'
+ ' type of [{}]'.format(
+ playbook_path, ret_val, connection))
+ return ret_val
-def ssh_client(ip, user, private_key_filepath, proxy_settings=None):
+
+def ssh_client(ip, user, private_key_filepath=None, password=None,
+ proxy_settings=None):
"""
Retrieves and attemts an SSH connection
:param ip: the IP of the host to connect
:param user: the user with which to connect
- :param private_key_filepath: the path to the private key file
+ :param private_key_filepath: when None, password is required
+ :param password: when None, private_key_filepath is required
:param proxy_settings: instance of os_credentials.ProxySettings class
(optional)
:return: the SSH client if can connect else false
@@ -120,12 +159,17 @@ def ssh_client(ip, user, private_key_filepath, proxy_settings=None):
proxy_cmd_str = proxy_cmd_str.replace("%p", '22')
proxy_cmd = paramiko.ProxyCommand(proxy_cmd_str)
- pk_abs_path = os.path.expanduser(private_key_filepath)
- ssh.connect(ip, username=user, key_filename=pk_abs_path,
- sock=proxy_cmd)
+ pk_abs_path = None
+ if not password and private_key_filepath:
+ pk_abs_path = os.path.expanduser(private_key_filepath)
+
+ ssh.connect(
+ ip, username=user, key_filename=pk_abs_path, password=password,
+ sock=proxy_cmd)
+ logger.info('Obtained SSH connection to %s', ip)
return ssh
except Exception as e:
- logger.warning('Unable to connect via SSH with message - ' + str(e))
+ logger.debug('Unable to connect via SSH with message - ' + str(e))
class AnsibleException(Exception):
diff --git a/snaps/provisioning/tests/ansible_utils_tests.py b/snaps/provisioning/tests/ansible_utils_tests.py
index da056b2..b6ace31 100644
--- a/snaps/provisioning/tests/ansible_utils_tests.py
+++ b/snaps/provisioning/tests/ansible_utils_tests.py
@@ -18,15 +18,20 @@ import uuid
import os
import pkg_resources
from scp import SCPClient
+
+from snaps.config.keypair import KeypairConfig
+from snaps.config.network import PortConfig
+from snaps.config.security_group import (
+ Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
+from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
+
from snaps.openstack import create_flavor
from snaps.openstack import create_image
from snaps.openstack import create_instance
from snaps.openstack import create_keypairs
from snaps.openstack import create_network
from snaps.openstack import create_router
-from snaps.openstack.create_security_group import (
- SecurityGroupRuleSettings, Direction, Protocol, OpenStackSecurityGroup,
- SecurityGroupSettings)
+from snaps.openstack.create_security_group import OpenStackSecurityGroup
from snaps.openstack.tests import openstack_tests
from snaps.openstack.tests.create_instance_tests import check_dhcp_lease
from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
@@ -52,7 +57,7 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
"""
super(self.__class__, self).__start__()
- self.nova = nova_utils.nova_client(self.os_creds)
+ self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
self.keypair_priv_filepath = 'tmp/' + guid
@@ -78,13 +83,15 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
os_image_settings = openstack_tests.ubuntu_image_settings(
name=guid + '-' + '-image',
image_metadata=self.image_metadata)
- self.image_creator = create_image.OpenStackImage(self.os_creds,
- os_image_settings)
+ self.image_creator = create_image.OpenStackImage(
+ self.os_creds, os_image_settings)
self.image_creator.create()
# First network is public
self.pub_net_config = openstack_tests.get_pub_net_config(
- net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet',
+ project_name=self.os_creds.project_name,
+ net_name=guid + '-pub-net',
+ mtu=1442, subnet_name=guid + '-pub-subnet',
router_name=guid + '-pub-router',
external_net=self.ext_net_name)
@@ -98,16 +105,17 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
self.router_creator.create()
# Create Flavor
+ flavor_config = openstack_tests.get_flavor_config(
+ name=guid + '-flavor-name', ram=2048, disk=10,
+ vcpus=2, metadata=self.flavor_metadata)
+
self.flavor_creator = create_flavor.OpenStackFlavor(
- self.admin_os_creds,
- create_flavor.FlavorSettings(name=guid + '-flavor-name',
- ram=2048, disk=10, vcpus=2,
- metadata=self.flavor_metadata))
+ self.admin_os_creds, flavor_config)
self.flavor_creator.create()
# Create Key/Pair
self.keypair_creator = create_keypairs.OpenStackKeypair(
- self.os_creds, create_keypairs.KeypairSettings(
+ self.os_creds, KeypairConfig(
name=self.keypair_name,
public_filepath=self.keypair_pub_filepath,
private_filepath=self.keypair_priv_filepath))
@@ -115,32 +123,30 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
# Create Security Group
sec_grp_name = guid + '-sec-grp'
- rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.icmp)
- rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name,
- direction=Direction.ingress,
- protocol=Protocol.tcp,
- port_range_min=22,
- port_range_max=22)
+ rule1 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.icmp)
+ rule2 = SecurityGroupRuleConfig(
+ sec_grp_name=sec_grp_name, direction=Direction.ingress,
+ protocol=Protocol.tcp, port_range_min=22, port_range_max=22)
self.sec_grp_creator = OpenStackSecurityGroup(
self.os_creds,
- SecurityGroupSettings(name=sec_grp_name,
- rule_settings=[rule1, rule2]))
+ SecurityGroupConfig(
+ name=sec_grp_name, rule_settings=[rule1, rule2]))
self.sec_grp_creator.create()
# Create instance
ports_settings = list()
ports_settings.append(
- create_network.PortSettings(
+ PortConfig(
name=self.port_1_name,
network_name=self.pub_net_config.network_settings.name))
- instance_settings = create_instance.VmInstanceSettings(
+ instance_settings = VmInstanceConfig(
name=self.vm_inst_name,
flavor=self.flavor_creator.flavor_settings.name,
port_settings=ports_settings,
- floating_ip_settings=[create_instance.FloatingIpSettings(
+ floating_ip_settings=[FloatingIpConfig(
name=self.floating_ip_name, port_name=self.port_1_name,
router_name=self.pub_net_config.router_settings.name)])
@@ -237,6 +243,9 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
# Block until VM's ssh port has been opened
self.assertTrue(self.inst_creator.vm_ssh_active(block=True))
+ # Block until cloud-init has completed
+ self.assertTrue(self.inst_creator.cloud_init_complete(block=True))
+
ssh_client = self.inst_creator.ssh_client()
self.assertIsNotNone(ssh_client)
@@ -256,18 +265,19 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
relative_pb_path = pkg_resources.resource_filename(
'snaps.provisioning.tests.playbooks', 'simple_playbook.yml')
- retval = self.inst_creator.apply_ansible_playbook(relative_pb_path)
- self.assertEqual(0, retval)
+ self.inst_creator.apply_ansible_playbook(relative_pb_path)
- ssh = ansible_utils.ssh_client(ip, user, priv_key,
- self.os_creds.proxy_settings)
+ ssh = ansible_utils.ssh_client(
+ ip, user, private_key_filepath=priv_key,
+ proxy_settings=self.os_creds.proxy_settings)
self.assertIsNotNone(ssh)
-
+ scp = None
try:
scp = SCPClient(ssh.get_transport())
scp.get('~/hello.txt', self.test_file_local_path)
finally:
- scp.close()
+ if scp:
+ scp.close()
ssh.close()
self.assertTrue(os.path.isfile(self.test_file_local_path))
@@ -305,6 +315,9 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
# Block until VM's ssh port has been opened
self.assertTrue(self.inst_creator.vm_ssh_active(block=True))
+ # Block until cloud-init has completed
+ self.assertTrue(self.inst_creator.cloud_init_complete(block=True))
+
# Apply Security Group
self.inst_creator.add_security_group(
self.sec_grp_creator.get_security_group())
@@ -318,20 +331,21 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
relative_pb_path = pkg_resources.resource_filename(
'snaps.provisioning.tests.playbooks',
'template_playbook.yml')
- retval = self.inst_creator.apply_ansible_playbook(relative_pb_path,
- variables={
- 'name': 'Foo'})
- self.assertEqual(0, retval)
+ self.inst_creator.apply_ansible_playbook(
+ relative_pb_path, variables={'name': 'Foo'})
- ssh = ansible_utils.ssh_client(ip, user, priv_key,
- self.os_creds.proxy_settings)
+ ssh = ansible_utils.ssh_client(
+ ip, user, private_key_filepath=priv_key,
+ proxy_settings=self.os_creds.proxy_settings)
self.assertIsNotNone(ssh)
+ scp = None
try:
scp = SCPClient(ssh.get_transport())
scp.get('/tmp/hello.txt', self.test_file_local_path)
finally:
- scp.close()
+ if scp:
+ scp.close()
ssh.close()
self.assertTrue(os.path.isfile(self.test_file_local_path))
diff --git a/snaps/test_runner.py b/snaps/test_runner.py
index d3c1fd6..d46fe86 100644
--- a/snaps/test_runner.py
+++ b/snaps/test_runner.py
@@ -13,11 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
-import json
import logging
import unittest
+from concurrencytest import ConcurrentTestSuite, fork_for_tests
-from snaps import test_suite_builder, file_utils
+from snaps import file_utils
+from snaps import test_suite_builder as tsb
from snaps.openstack.tests import openstack_tests
__author__ = 'spisarski'
@@ -30,14 +31,13 @@ LOG_LEVELS = {'FATAL': logging.FATAL, 'CRITICAL': logging.CRITICAL,
'INFO': logging.INFO, 'DEBUG': logging.DEBUG}
-def __create_test_suite(
+def __create_concurrent_test_suite(
source_filename, ext_net_name, proxy_settings, ssh_proxy_cmd,
run_unit_tests, run_connection_tests, run_api_tests,
run_integration_tests, run_staging_tests, flavor_metadata,
- image_metadata, use_keystone, use_floating_ips, continuous_integration,
- log_level):
+ image_metadata, use_floating_ips, continuous_integration, log_level):
"""
- Compiles the tests that should run
+ Compiles the tests that can be run concurrently
:param source_filename: the OpenStack credentials file (required)
:param ext_net_name: the name of the external network to use for floating
IPs (required)
@@ -56,9 +56,6 @@ def __create_test_suite(
created for test VM instance
:param image_metadata: dict() object containing the metadata for overriding
default images within the tests
- :param use_keystone: when true, tests creating users and projects will be
- exercised and must be run on a host that
- has access to the cloud's administrative network
:param use_floating_ips: when true, tests requiring floating IPs will be
executed
:param continuous_integration: when true, tests for CI will be run
@@ -73,43 +70,100 @@ def __create_test_suite(
# Tests that do not require a remote connection to an OpenStack cloud
if run_unit_tests:
- test_suite_builder.add_unit_tests(suite)
+ tsb.add_unit_tests(suite)
# Basic connection tests
if run_connection_tests:
- test_suite_builder.add_openstack_client_tests(
+ tsb.add_openstack_client_tests(
suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
- use_keystone=use_keystone, log_level=log_level)
+ use_keystone=True, log_level=log_level)
# Tests the OpenStack API calls
if run_api_tests:
- test_suite_builder.add_openstack_api_tests(
+ tsb.add_openstack_api_tests(
suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
- use_keystone=use_keystone, image_metadata=image_metadata,
- log_level=log_level)
+ use_keystone=True, flavor_metadata=flavor_metadata,
+ image_metadata=image_metadata, log_level=log_level)
# Long running integration type tests
if run_integration_tests:
- test_suite_builder.add_openstack_integration_tests(
+ tsb.add_openstack_integration_tests(
suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
- use_keystone=use_keystone, flavor_metadata=flavor_metadata,
+ use_keystone=True, flavor_metadata=flavor_metadata,
image_metadata=image_metadata, use_floating_ips=use_floating_ips,
log_level=log_level)
if run_staging_tests:
- test_suite_builder.add_openstack_staging_tests(
+ tsb.add_openstack_staging_tests(
suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
log_level=log_level)
if continuous_integration:
- test_suite_builder.add_openstack_ci_tests(
+ tsb.add_openstack_ci_tests(
suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
- use_keystone=use_keystone, flavor_metadata=flavor_metadata,
+ use_keystone=True, flavor_metadata=flavor_metadata,
image_metadata=image_metadata, use_floating_ips=use_floating_ips,
log_level=log_level)
return suite
+def __create_sequential_test_suite(
+ source_filename, ext_net_name, proxy_settings, ssh_proxy_cmd,
+ run_integration_tests, flavor_metadata, image_metadata,
+ use_floating_ips, log_level):
+ """
+ Compiles the tests that cannot be run in parallel
+ :param source_filename: the OpenStack credentials file (required)
+ :param ext_net_name: the name of the external network to use for floating
+ IPs (required)
+ :param run_integration_tests: when true, the integration tests are executed
+ :param proxy_settings: <host>:<port> of the proxy server (optional)
+ :param ssh_proxy_cmd: the command used to connect via SSH over some proxy
+ server (optional)
+ :param flavor_metadata: dict() object containing the metadata for flavors
+ created for test VM instance
+ :param image_metadata: dict() object containing the metadata for overriding
+ default images within the tests
+ :param use_floating_ips: when true, tests requiring floating IPs will be
+ executed
+ :param log_level: the logging level
+ :return:
+ """
+ if use_floating_ips and run_integration_tests:
+ suite = unittest.TestSuite()
+
+ os_creds = openstack_tests.get_credentials(
+ os_env_file=source_filename, proxy_settings_str=proxy_settings,
+ ssh_proxy_cmd=ssh_proxy_cmd)
+
+ tsb.add_ansible_integration_tests(
+ suite=suite, os_creds=os_creds, ext_net_name=ext_net_name,
+ use_keystone=True, flavor_metadata=flavor_metadata,
+ image_metadata=image_metadata, log_level=log_level)
+
+ return suite
+
+
+def __output_results(results):
+ """
+ Sends the test results to the logger
+ :param results:
+ :return:
+ """
+
+ if results.errors:
+ logger.error('Number of errors in test suite - %s',
+ len(results.errors))
+ for test, message in results.errors:
+ logger.error(str(test) + " ERROR with " + message)
+
+ if results.failures:
+ logger.error('Number of failures in test suite - %s',
+ len(results.failures))
+ for test, message in results.failures:
+ logger.error(str(test) + " FAILED with " + message)
+
+
def main(arguments):
"""
Begins running unit tests.
@@ -123,13 +177,16 @@ def main(arguments):
flavor_metadata = None
if arguments.flavor_metadata:
- flavor_metadata = json.loads(arguments.flavor_metadata)
+ flavor_metadata = {
+ 'metadata': {'hw:mem_page_size': arguments.flavor_metadata}}
image_metadata = None
if arguments.image_metadata_file:
image_metadata = file_utils.read_yaml(arguments.image_metadata_file)
- suite = None
+ concurrent_suite = None
+ sequential_suite = None
+
if arguments.env and arguments.ext_net:
unit = arguments.include_unit != ARG_NOT_SET
connection = arguments.include_connection != ARG_NOT_SET
@@ -144,40 +201,61 @@ def main(arguments):
api = True
integration = True
- suite = __create_test_suite(
+ concurrent_suite = __create_concurrent_test_suite(
arguments.env, arguments.ext_net, arguments.proxy,
arguments.ssh_proxy_cmd, unit, connection, api,
integration, staging, flavor_metadata, image_metadata,
- arguments.use_keystone != ARG_NOT_SET,
arguments.floating_ips != ARG_NOT_SET,
ci, log_level)
+
+ if (arguments.include_integration != ARG_NOT_SET
+ and arguments.floating_ips != ARG_NOT_SET):
+ sequential_suite = __create_sequential_test_suite(
+ arguments.env, arguments.ext_net, arguments.proxy,
+ arguments.ssh_proxy_cmd, integration, flavor_metadata,
+ image_metadata,
+ arguments.floating_ips != ARG_NOT_SET, log_level)
else:
logger.error('Environment file or external network not defined')
exit(1)
i = 0
while i < int(arguments.num_runs):
- result = unittest.TextTestRunner(verbosity=2).run(suite)
i += 1
- if result.errors:
- logger.error('Number of errors in test suite - %s',
- len(result.errors))
- for test, message in result.errors:
- logger.error(str(test) + " ERROR with " + message)
-
- if result.failures:
- logger.error('Number of failures in test suite - %s',
- len(result.failures))
- for test, message in result.failures:
- logger.error(str(test) + " FAILED with " + message)
-
- if ((result.errors and len(result.errors) > 0)
- or (result.failures and len(result.failures) > 0)):
- logger.error('See above for test failures')
- exit(1)
- else:
- logger.info('All tests completed successfully in run #%s', i)
+ if concurrent_suite:
+ logger.info('Running Concurrent Tests')
+ concurrent_runner = unittest.TextTestRunner(verbosity=2)
+ concurrent_suite = ConcurrentTestSuite(
+ concurrent_suite, fork_for_tests(int(arguments.threads)))
+ concurrent_results = concurrent_runner.run(concurrent_suite)
+ __output_results(concurrent_results)
+
+ if ((concurrent_results.errors
+ and len(concurrent_results.errors) > 0)
+ or (concurrent_results.failures
+ and len(concurrent_results.failures) > 0)):
+ logger.error('See above for test failures')
+ exit(1)
+ else:
+ logger.info(
+ 'Concurrent tests completed successfully in run #%s', i)
+
+ if sequential_suite:
+ logger.info('Running Sequential Tests')
+ sequential_runner = unittest.TextTestRunner(verbosity=2)
+ sequential_results = sequential_runner.run(sequential_suite)
+ __output_results(sequential_results)
+
+ if ((sequential_results.errors
+ and len(sequential_results.errors) > 0)
+ or (sequential_results.failures
+ and len(sequential_results.failures) > 0)):
+ logger.error('See above for test failures')
+ exit(1)
+ else:
+ logger.info(
+ 'Sequential tests completed successfully in run #%s', i)
logger.info('Successful completion of %s test runs', i)
exit(0)
@@ -227,15 +305,9 @@ if __name__ == '__main__':
nargs='?', help='When argument is set, all integration tests requiring'
' Floating IPs will be executed')
parser.add_argument(
- '-k', '--use-keystone', dest='use_keystone', default=ARG_NOT_SET,
- nargs='?',
- help='When argument is set, the tests will exercise the keystone APIs '
- 'and must be run on a machine that has access to the admin '
- 'network and is able to create users and groups')
- parser.add_argument(
'-fm', '--flavor-meta', dest='flavor_metadata',
- help='JSON string to be used as flavor metadata for all test instances'
- ' created')
+ help='hw:mem_page_size flavor setting value (i.e. large). '
+ 'Required for DPDK')
parser.add_argument(
'-im', '--image-meta', dest='image_metadata_file', default=None,
help='Location of YAML file containing the image metadata')
@@ -247,6 +319,9 @@ if __name__ == '__main__':
parser.add_argument(
'-r', '--num-runs', dest='num_runs', default=1,
help='Number of test runs to execute (default 1)')
+ parser.add_argument(
+ '-t', '--threads', dest='threads', default=4,
+ help='Number of threads to execute the tests (default 4)')
args = parser.parse_args()
diff --git a/snaps/test_suite_builder.py b/snaps/test_suite_builder.py
index a1b72aa..52008a6 100644
--- a/snaps/test_suite_builder.py
+++ b/snaps/test_suite_builder.py
@@ -16,6 +16,26 @@
import logging
import unittest
+from snaps.config.tests.cluster_template_tests import (
+ ClusterTemplateConfigUnitTests)
+from snaps.config.tests.network_tests import (
+ NetworkConfigUnitTests, SubnetConfigUnitTests, PortConfigUnitTests)
+from snaps.config.tests.security_group_tests import (
+ SecurityGroupConfigUnitTests, SecurityGroupRuleConfigUnitTests)
+from snaps.config.tests.vm_inst_tests import (
+ VmInstanceConfigUnitTests, FloatingIpConfigUnitTests)
+from snaps.config.tests.volume_tests import VolumeConfigUnitTests
+from snaps.config.tests.volume_type_tests import VolumeTypeConfigUnitTests
+from snaps.config.tests.qos_tests import QoSConfigUnitTests
+from snaps.config.tests.stack_tests import StackConfigUnitTests
+from snaps.config.tests.router_tests import RouterConfigUnitTests
+from snaps.config.tests.user_tests import UserConfigUnitTests
+from snaps.config.tests.project_tests import ProjectConfigUnitTests
+from snaps.config.tests.keypair_tests import KeypairConfigUnitTests
+from snaps.config.tests.flavor_tests import FlavorConfigUnitTests
+import snaps.config.tests.image_tests as image_tests
+import snaps.openstack.tests.create_image_tests as creator_tests
+from snaps.domain.test.cluster_template_tests import ClusterTemplateUnitTests
from snaps.domain.test.flavor_tests import FlavorDomainObjectTests
from snaps.domain.test.image_tests import ImageDomainObjectTests
from snaps.domain.test.keypair_tests import KeypairDomainObjectTests
@@ -34,26 +54,30 @@ from snaps.domain.test.vm_inst_tests import (
VmInstDomainObjectTests, FloatingIpDomainObjectTests)
from snaps.domain.test.volume_tests import (
QoSSpecDomainObjectTests, VolumeTypeDomainObjectTests,
- VolumeTypeEncryptionObjectTests)
+ VolumeTypeEncryptionObjectTests, VolumeDomainObjectTests)
+from snaps.openstack.tests.cluster_template_tests import (
+ CreateClusterTemplateTests)
from snaps.openstack.tests.conf.os_credentials_tests import (
ProxySettingsUnitTests, OSCredsUnitTests)
from snaps.openstack.tests.create_flavor_tests import (
CreateFlavorTests, FlavorSettingsUnitTests)
from snaps.openstack.tests.create_image_tests import (
- CreateImageSuccessTests, CreateImageNegativeTests, ImageSettingsUnitTests,
+ CreateImageSuccessTests, CreateImageNegativeTests,
CreateMultiPartImageTests)
from snaps.openstack.tests.create_instance_tests import (
- CreateInstanceSingleNetworkTests, CreateInstancePubPrivNetTests,
- CreateInstanceOnComputeHost, CreateInstanceSimpleTests,
- FloatingIpSettingsUnitTests, InstanceSecurityGroupTests,
- VmInstanceSettingsUnitTests, CreateInstancePortManipulationTests,
- SimpleHealthCheck, CreateInstanceFromThreePartImage,
- CreateInstanceMockOfflineTests, CreateInstanceTwoNetTests)
+ CreateInstanceSingleNetworkTests, CreateInstanceOnComputeHost,
+ CreateInstanceSimpleTests, FloatingIpSettingsUnitTests,
+ InstanceSecurityGroupTests, VmInstanceSettingsUnitTests,
+ CreateInstancePortManipulationTests, SimpleHealthCheck,
+ CreateInstanceFromThreePartImage, CreateInstanceMockOfflineTests,
+ CreateInstanceTwoNetTests, CreateInstanceVolumeTests,
+ CreateInstanceIPv6NetworkTests, CreateInstanceExternalNetTests)
from snaps.openstack.tests.create_keypairs_tests import (
CreateKeypairsTests, KeypairSettingsUnitTests, CreateKeypairsCleanupTests)
from snaps.openstack.tests.create_network_tests import (
CreateNetworkSuccessTests, NetworkSettingsUnitTests, PortSettingsUnitTests,
- SubnetSettingsUnitTests, CreateNetworkTypeTests)
+ SubnetSettingsUnitTests, CreateNetworkTypeTests, CreateNetworkIPv6Tests,
+ CreateMultipleNetworkTests, CreateNetworkGatewayTests)
from snaps.openstack.tests.create_project_tests import (
CreateProjectSuccessTests, ProjectSettingsUnitTests,
CreateProjectUserTests)
@@ -61,15 +85,23 @@ from snaps.openstack.tests.create_qos_tests import (
QoSSettingsUnitTests, CreateQoSTests)
from snaps.openstack.tests.create_router_tests import (
CreateRouterSuccessTests, CreateRouterNegativeTests,
- RouterSettingsUnitTests)
+ RouterSettingsUnitTests, CreateMultipleRouterTests,
+ CreateRouterSecurityGroupTests, CreateRouterSharedNetworksTests)
from snaps.openstack.tests.create_security_group_tests import (
CreateSecurityGroupTests, SecurityGroupRuleSettingsUnitTests,
- SecurityGroupSettingsUnitTests)
+ SecurityGroupSettingsUnitTests, CreateMultipleSecurityGroupTests)
from snaps.openstack.tests.create_stack_tests import (
StackSettingsUnitTests, CreateStackSuccessTests, CreateStackNegativeTests,
- CreateComplexStackTests)
+ CreateStackFlavorTests, CreateStackFloatingIpTests,
+ CreateStackNestedResourceTests, CreateStackKeypairTests,
+ CreateStackVolumeTests, CreateStackSecurityGroupTests,
+ CreateStackUpdateTests)
from snaps.openstack.tests.create_user_tests import (
UserSettingsUnitTests, CreateUserSuccessTests)
+from snaps.openstack.tests.create_volume_tests import (
+ VolumeSettingsUnitTests, CreateSimpleVolumeSuccessTests,
+ CreateVolumeWithTypeTests, CreateVolumeWithImageTests,
+ CreateSimpleVolumeFailureTests, CreateVolMultipleCredsTests)
from snaps.openstack.tests.create_volume_type_tests import (
VolumeTypeSettingsUnitTests, CreateSimpleVolumeTypeSuccessTests,
CreateVolumeTypeComplexTests)
@@ -77,21 +109,27 @@ from snaps.openstack.tests.os_source_file_test import (
OSComponentTestCase, OSIntegrationTestCase)
from snaps.openstack.utils.tests.cinder_utils_tests import (
CinderSmokeTests, CinderUtilsQoSTests, CinderUtilsSimpleVolumeTypeTests,
- CinderUtilsAddEncryptionTests, CinderUtilsVolumeTypeCompleteTests)
+ CinderUtilsAddEncryptionTests, CinderUtilsVolumeTypeCompleteTests,
+ CinderUtilsVolumeTests)
from snaps.openstack.utils.tests.glance_utils_tests import (
GlanceSmokeTests, GlanceUtilsTests)
from snaps.openstack.utils.tests.heat_utils_tests import (
HeatSmokeTests, HeatUtilsCreateSimpleStackTests,
- HeatUtilsCreateComplexStackTests)
+ HeatUtilsCreateComplexStackTests, HeatUtilsFlavorTests,
+ HeatUtilsKeypairTests, HeatUtilsVolumeTests, HeatUtilsSecurityGroupTests)
from snaps.openstack.utils.tests.keystone_utils_tests import (
KeystoneSmokeTests, KeystoneUtilsTests)
from snaps.openstack.utils.tests.neutron_utils_tests import (
NeutronSmokeTests, NeutronUtilsNetworkTests, NeutronUtilsSubnetTests,
NeutronUtilsRouterTests, NeutronUtilsSecurityGroupTests,
- NeutronUtilsFloatingIpTests)
+ NeutronUtilsFloatingIpTests, NeutronUtilsIPv6Tests)
from snaps.openstack.utils.tests.nova_utils_tests import (
NovaSmokeTests, NovaUtilsKeypairTests, NovaUtilsFlavorTests,
- NovaUtilsInstanceTests)
+ NovaUtilsInstanceTests, NovaUtilsInstanceVolumeTests)
+from snaps.openstack.utils.tests.settings_utils_tests import (
+ SettingsUtilsUnitTests)
+from snaps.openstack.utils.tests.magnum_utils_tests import (
+ MagnumSmokeTests, MagnumUtilsClusterTypeTests)
from snaps.provisioning.tests.ansible_utils_tests import (
AnsibleProvisioningTests)
from snaps.tests.file_utils_tests import FileUtilsTests
@@ -107,34 +145,48 @@ def add_unit_tests(suite):
"""
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(FileUtilsTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
- SecurityGroupRuleSettingsUnitTests))
- suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
ProxySettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
OSCredsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ SecurityGroupConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
SecurityGroupSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ SecurityGroupRuleConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ SecurityGroupRuleSettingsUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
SecurityGroupDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
SecurityGroupRuleDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
- ImageSettingsUnitTests))
+ image_tests.ImageConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ creator_tests.ImageSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
ImageDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ FlavorConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
FlavorSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
FlavorDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ KeypairConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
KeypairSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
KeypairDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ UserConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
UserSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
UserDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ ProjectConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
ProjectSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
ProjectDomainObjectTests))
@@ -147,47 +199,77 @@ def add_unit_tests(suite):
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
RoleDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ NetworkConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
NetworkSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
NetworkObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ SubnetConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
SubnetSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
SubnetObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ PortConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
PortSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
PortDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ RouterConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
RouterSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
RouterDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
InterfaceRouterDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ FloatingIpConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
FloatingIpSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ VmInstanceConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
VmInstanceSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
StackDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
ResourceDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ StackConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
StackSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
VolumeTypeDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
VolumeTypeEncryptionObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ VolumeDomainObjectTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
QoSSpecDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
VmInstDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
FloatingIpDomainObjectTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ QoSConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
QoSSettingsUnitTests))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ VolumeTypeConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
VolumeTypeSettingsUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ VolumeConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ VolumeSettingsUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ ClusterTemplateConfigUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ ClusterTemplateUnitTests))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
+ SettingsUtilsUnitTests))
def add_openstack_client_tests(suite, os_creds, ext_net_name,
@@ -236,7 +318,8 @@ def add_openstack_client_tests(suite, os_creds, ext_net_name,
def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True,
- image_metadata=None, log_level=logging.INFO):
+ flavor_metadata=None, image_metadata=None,
+ log_level=logging.INFO):
"""
Adds tests written to exercise all existing OpenStack APIs
:param suite: the unittest.TestSuite object to which to add the tests
@@ -247,6 +330,9 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True,
:param use_keystone: when True, tests requiring direct access to Keystone
are added as these need to be running on a host that
has access to the cloud's private network
+ :param flavor_metadata: dict() object containing the metadata required by
+ your flavor based on your configuration:
+ (i.e. {'hw:mem_page_size': 'any'})
:param image_metadata: dict() object containing metadata for creating an
image with custom config
(see YAML files in examples/image-metadata)
@@ -279,6 +365,9 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True,
NeutronUtilsSubnetTests, os_creds=os_creds, ext_net_name=ext_net_name,
log_level=log_level))
suite.addTest(OSComponentTestCase.parameterize(
+ NeutronUtilsIPv6Tests, os_creds=os_creds, ext_net_name=ext_net_name,
+ log_level=log_level))
+ suite.addTest(OSComponentTestCase.parameterize(
NeutronUtilsRouterTests, os_creds=os_creds, ext_net_name=ext_net_name,
log_level=log_level))
suite.addTest(OSComponentTestCase.parameterize(
@@ -297,6 +386,10 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True,
NovaUtilsInstanceTests, os_creds=os_creds, ext_net_name=ext_net_name,
log_level=log_level, image_metadata=image_metadata))
suite.addTest(OSComponentTestCase.parameterize(
+ NovaUtilsInstanceVolumeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata))
+ suite.addTest(OSComponentTestCase.parameterize(
CreateFlavorTests, os_creds=os_creds, ext_net_name=ext_net_name,
log_level=log_level))
suite.addTest(OSComponentTestCase.parameterize(
@@ -308,10 +401,30 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True,
ext_net_name=ext_net_name, log_level=log_level,
image_metadata=image_metadata))
suite.addTest(OSComponentTestCase.parameterize(
+ HeatUtilsFlavorTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level,
+ image_metadata=image_metadata))
+ suite.addTest(OSComponentTestCase.parameterize(
+ HeatUtilsKeypairTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level,
+ image_metadata=image_metadata))
+ suite.addTest(OSComponentTestCase.parameterize(
+ HeatUtilsSecurityGroupTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level,
+ image_metadata=image_metadata))
+ suite.addTest(OSComponentTestCase.parameterize(
+ HeatUtilsVolumeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level,
+ image_metadata=image_metadata))
+ suite.addTest(OSComponentTestCase.parameterize(
CinderUtilsQoSTests, os_creds=os_creds,
ext_net_name=ext_net_name, log_level=log_level,
image_metadata=image_metadata))
suite.addTest(OSComponentTestCase.parameterize(
+ CinderUtilsVolumeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level,
+ image_metadata=image_metadata))
+ suite.addTest(OSComponentTestCase.parameterize(
CinderUtilsSimpleVolumeTypeTests, os_creds=os_creds,
ext_net_name=ext_net_name, log_level=log_level,
image_metadata=image_metadata))
@@ -362,6 +475,11 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateMultipleSecurityGroupTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
CreateImageSuccessTests, os_creds=os_creds, ext_net_name=ext_net_name,
use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
@@ -393,6 +511,21 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateNetworkGatewayTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateNetworkIPv6Tests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateMultipleNetworkTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
CreateRouterSuccessTests, os_creds=os_creds, ext_net_name=ext_net_name,
use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
@@ -403,6 +536,21 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateMultipleRouterTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateRouterSecurityGroupTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateRouterSharedNetworksTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
CreateQoSTests, os_creds=os_creds,
ext_net_name=ext_net_name, use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
@@ -417,6 +565,31 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
ext_net_name=ext_net_name, use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateSimpleVolumeSuccessTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateSimpleVolumeFailureTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateVolumeWithTypeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateVolumeWithImageTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateVolMultipleCredsTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
# VM Instances
suite.addTest(OSIntegrationTestCase.parameterize(
@@ -435,6 +608,11 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateInstanceExternalNetTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
CreateInstancePortManipulationTests, os_creds=os_creds,
ext_net_name=ext_net_name, use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
@@ -455,11 +633,41 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateInstanceVolumeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateInstanceIPv6NetworkTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
CreateStackSuccessTests, os_creds=os_creds, ext_net_name=ext_net_name,
use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateStackVolumeTests, os_creds=os_creds, ext_net_name=ext_net_name,
+ use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateStackFlavorTests, os_creds=os_creds, ext_net_name=ext_net_name,
+ use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateStackKeypairTests, os_creds=os_creds, ext_net_name=ext_net_name,
+ use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateStackSecurityGroupTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
CreateStackNegativeTests, os_creds=os_creds, ext_net_name=ext_net_name,
use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
@@ -472,17 +680,53 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
- CreateComplexStackTests, os_creds=os_creds,
+ CreateStackFloatingIpTests, os_creds=os_creds,
ext_net_name=ext_net_name, use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
suite.addTest(OSIntegrationTestCase.parameterize(
- AnsibleProvisioningTests, os_creds=os_creds,
+ CreateStackNestedResourceTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ CreateStackUpdateTests, os_creds=os_creds,
ext_net_name=ext_net_name, use_keystone=use_keystone,
flavor_metadata=flavor_metadata, image_metadata=image_metadata,
log_level=log_level))
+def add_ansible_integration_tests(suite, os_creds, ext_net_name,
+ use_keystone=True, flavor_metadata=None,
+ image_metadata=None, log_level=logging.INFO):
+ """
+ Adds tests written to exercise all long-running OpenStack integration tests
+ meaning they will be creating VM instances and potentially performing some
+ SSH functions through floatingIPs
+ :param suite: the unittest.TestSuite object to which to add the tests
+ :param os_creds: and instance of OSCreds that holds the credentials
+ required by OpenStack
+ :param ext_net_name: the name of an external network on the cloud under
+ test
+ :param use_keystone: when True, tests requiring direct access to Keystone
+ are added as these need to be running on a host that
+ has access to the cloud's private network
+ :param image_metadata: dict() object containing metadata for creating an
+ image with custom config
+ (see YAML files in examples/image-metadata)
+ :param flavor_metadata: dict() object containing the metadata required by
+ your flavor based on your configuration:
+ (i.e. {'hw:mem_page_size': 'large'})
+ :param log_level: the logging level
+ :return: None as the tests will be adding to the 'suite' parameter object
+ """
+ suite.addTest(OSIntegrationTestCase.parameterize(
+ AnsibleProvisioningTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, use_keystone=use_keystone,
+ flavor_metadata=flavor_metadata, image_metadata=image_metadata,
+ log_level=log_level))
+
+
def add_openstack_ci_tests(
suite, os_creds, ext_net_name, use_keystone=True, flavor_metadata=None,
image_metadata=None, use_floating_ips=True, log_level=logging.INFO):
@@ -556,11 +800,17 @@ def add_openstack_staging_tests(suite, os_creds, ext_net_name,
:return: None as the tests will be adding to the 'suite' parameter object
"""
suite.addTest(OSComponentTestCase.parameterize(
- CreateNetworkTypeTests, os_creds=os_creds, ext_net_name=ext_net_name,
- log_level=log_level))
+ CreateNetworkTypeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level))
suite.addTest(OSComponentTestCase.parameterize(
CreateInstanceMockOfflineTests, os_creds=os_creds,
ext_net_name=ext_net_name, log_level=log_level))
- suite.addTest(OSIntegrationTestCase.parameterize(
- CreateInstancePubPrivNetTests, os_creds=os_creds,
- ext_net_name=ext_net_name, log_level=log_level))
+ suite.addTest(OSComponentTestCase.parameterize(
+ MagnumSmokeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level))
+ suite.addTest(OSComponentTestCase.parameterize(
+ MagnumUtilsClusterTypeTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level))
+ suite.addTest(OSComponentTestCase.parameterize(
+ CreateClusterTemplateTests, os_creds=os_creds,
+ ext_net_name=ext_net_name, log_level=log_level))
diff --git a/snaps/tests/file_utils_tests.py b/snaps/tests/file_utils_tests.py
index befe37a..e09a9cc 100644
--- a/snaps/tests/file_utils_tests.py
+++ b/snaps/tests/file_utils_tests.py
@@ -31,7 +31,7 @@ class FileUtilsTests(unittest.TestCase):
def setUp(self):
guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
- self.tmp_dir = '.tmp/'
+ self.tmp_dir = 'tmp/'
self.test_dir = self.tmp_dir + str(guid)
if not os.path.exists(self.test_dir):
os.makedirs(self.test_dir)
@@ -44,7 +44,7 @@ class FileUtilsTests(unittest.TestCase):
self.tmp_file_opened.close()
if os.path.exists(self.test_dir) and os.path.isdir(self.test_dir):
- shutil.rmtree(self.tmp_dir)
+ shutil.rmtree(self.test_dir)
def testFileIsDirectory(self):
"""
diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/configure_host.yml b/snaps/thread_utils.py
index 5d43f96..3a3eb4d 100644
--- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/configure_host.yml
+++ b/snaps/thread_utils.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
# and others. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +12,16 @@
# 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.
----
-- name: Configure NIC
- hosts: all
- become: yes
- become_method: sudo
- become_user: root
- tasks:
- - name: Setup /etc/network/interfaces.d/{{nic_name}}.cfg file
- action: template owner=root group=root mode=644 src=../templates/ethN.cfg dest=/etc/network/interfaces.d/{{nic_name}}.cfg
- - name : Restart Network
- command: service networking restart \ No newline at end of file
+from multiprocessing.pool import ThreadPool
+
+_pool = None
+
+
+# Define a thread pool with a limit for how many simultaneous API requests
+# can be in progress at once.
+def worker_pool(size=5):
+ global _pool
+ if _pool is None:
+ _pool = ThreadPool(processes=size)
+ return _pool
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..69aa189
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,17 @@
+[tox]
+minversion = 1.6
+envlist =
+ docs,
+ docs-linkcheck
+skipsdist = true
+
+[testenv:docs]
+deps = -rdocs/requirements.txt
+commands =
+ sphinx-build -b html -n -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/html
+ echo "Generated docs available in {toxinidir}/docs/_build/html"
+whitelist_externals = echo
+
+[testenv:docs-linkcheck]
+deps = -rdocs/requirements.txt
+commands = sphinx-build -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck