From 2ff0b72fc8178f1006114e69e3705626976bf5d2 Mon Sep 17 00:00:00 2001 From: Saumya Dudeja Date: Wed, 9 Sep 2026 15:42:46 +0200 Subject: [PATCH 1/2] Remove deprecated Ruby OpenStack CPI Delete the Ruby CPI implementation, its BOSH packages, job, CI tasks, developer scripts, and docs. The Go CPI is the sole implementation going forward. - Remove src/bosh_openstack_cpi/ (Ruby source + vendored gems) - Remove packages/bosh_openstack_cpi/, packages/openstack-ruby-3.{1,4}/ - Remove jobs/openstack_cpi/ (keep jobs/openstack_cpi_golang/) - Remove ci/tasks/run-unit-specs.{sh,yml} and run-lifecycle.{sh,yml} - Remove scripts/check-ruby-version, rubocop-staged, test-unit, test-unit-erb - Drop rubocop pre-commit hook from git-hooks/pre-commit - Remove LFS filter rule for vendored gems from .gitattributes - Remove Ruby prerequisites and test-unit sections from CONTRIBUTING.md --- .gitattributes | 1 - CONTRIBUTING.md | 22 - ci/tasks/run-lifecycle.sh | 74 - ci/tasks/run-lifecycle.yml | 36 - ci/tasks/run-unit-specs.sh | 12 - ci/tasks/run-unit-specs.yml | 14 - git-hooks/pre-commit | 2 - jobs/openstack_cpi/monit | 0 jobs/openstack_cpi/spec | 134 - jobs/openstack_cpi/templates/cacert.pem.erb | 1 - jobs/openstack_cpi/templates/cpi.erb | 32 - jobs/openstack_cpi/templates/cpi.json.erb | 80 - packages/bosh_openstack_cpi/packaging | 17 - packages/bosh_openstack_cpi/pre_packaging | 13 - packages/bosh_openstack_cpi/spec | 11 - packages/openstack-ruby-3.1/spec.lock | 2 - packages/openstack-ruby-3.4/spec.lock | 4 - scripts/check-ruby-version | 30 - scripts/rubocop-staged | 12 - scripts/test-unit | 10 - scripts/test-unit-erb | 12 - src/bosh_openstack_cpi/.bundle/config | 3 - src/bosh_openstack_cpi/.gitignore | 5 - src/bosh_openstack_cpi/.rspec | 2 - src/bosh_openstack_cpi/.rubocop.yml | 7 - src/bosh_openstack_cpi/.ruby-version | 1 - src/bosh_openstack_cpi/Gemfile | 21 - src/bosh_openstack_cpi/Gemfile.lock | 149 - src/bosh_openstack_cpi/Rakefile | 10 - .../bin/bosh_openstack_console | 69 - src/bosh_openstack_cpi/bin/openstack_cpi | 22 - src/bosh_openstack_cpi/docs/bosh_cpi.md | 104 - src/bosh_openstack_cpi/docs/cpi_config.md | 113 - .../images/openstack_cpi_create_disk.graffle | 1969 ------- .../docs/images/openstack_cpi_create_disk.png | Bin 52964 -> 0 bytes .../images/openstack_cpi_create_vm.graffle | 4813 ----------------- .../docs/images/openstack_cpi_create_vm.png | Bin 92840 -> 0 bytes .../openstack_cpi_createstemcell.graffle | 2231 -------- .../images/openstack_cpi_createstemcell.png | Bin 59759 -> 0 bytes .../images/openstack_cpi_delete_disk.graffle | 2591 --------- .../docs/images/openstack_cpi_delete_disk.png | Bin 54512 -> 0 bytes .../images/openstack_cpi_delete_vm.graffle | 3307 ----------- .../docs/images/openstack_cpi_delete_vm.png | Bin 66265 -> 0 bytes .../openstack_cpi_deletestemcell.graffle | 3939 -------------- .../images/openstack_cpi_deletestemcell.png | Bin 70136 -> 0 bytes .../images/openstack_cpi_initialize.graffle | 4090 -------------- .../docs/images/openstack_cpi_initialize.png | Bin 99921 -> 0 bytes .../lib/bosh_openstack_cpi.rb | 1 - src/bosh_openstack_cpi/lib/cloud/openstack.rb | 55 - .../openstack/availability_zone_provider.rb | 81 - .../lib/cloud/openstack/cloud.rb | 873 --- .../lib/cloud/openstack/cpi_lambda.rb | 33 - .../lib/cloud/openstack/dynamic_network.rb | 9 - .../openstack/excon_logging_instrumentor.rb | 64 - .../lib/cloud/openstack/floating_ip.rb | 66 - .../lib/cloud/openstack/helpers.rb | 72 - .../cloud/openstack/instance_type_mapper.rb | 88 - .../openstack/loadbalancer_configurator.rb | 224 - .../lib/cloud/openstack/manual_network.rb | 98 - .../lib/cloud/openstack/network.rb | 35 - .../cloud/openstack/network_configurator.rb | 263 - .../lib/cloud/openstack/noop_registry.rb | 18 - .../lib/cloud/openstack/openstack.rb | 341 -- .../lib/cloud/openstack/private_network.rb | 18 - .../lib/cloud/openstack/request_message.rb | 26 - .../lib/cloud/openstack/resource_pool.rb | 12 - .../lib/cloud/openstack/response_message.rb | 19 - .../lib/cloud/openstack/security_groups.rb | 61 - .../lib/cloud/openstack/server.rb | 230 - .../lib/cloud/openstack/stemcell.rb | 61 - .../lib/cloud/openstack/stemcell_creator.rb | 138 - .../lib/cloud/openstack/tag_manager.rb | 33 - .../lib/cloud/openstack/vip_network.rb | 20 - .../lib/cloud/openstack/vm_creator.rb | 79 - .../lib/cloud/openstack/vm_factory.rb | 119 - .../cloud/openstack/volume_configurator.rb | 31 - .../spec/assets/sample_config.yml | 15 - .../calculate_vm_cloud_properties_spec.rb | 64 - .../spec/integration/cpi_logging_spec.rb | 48 - .../spec/integration/image_spec.rb | 38 - .../spec/integration/lifecycle_spec.rb | 604 --- .../spec/integration/lifecycle_v2_spec.rb | 28 - .../spec/integration/spec_helper.rb | 19 - .../integration/support/integration_config.rb | 143 - .../integration/support/lifecycle_helpers.rb | 130 - .../spec/integration/volume_spec.rb | 157 - src/bosh_openstack_cpi/spec/spec_helper.rb | 233 - .../spec/unit/attach_disk_spec.rb | 223 - .../unit/availability_zone_provider_spec.rb | 182 - .../spec/unit/bin/openstack_cpi_spec.rb | 144 - .../jobs/cpi/templates/cpi.json.erb_spec.rb | 175 - .../spec/unit/cloud_spec.rb | 370 -- .../spec/unit/configure_networks_spec.rb | 9 - .../spec/unit/cpi_lambda_spec.rb | 93 - .../spec/unit/create_disk_spec.rb | 180 - .../spec/unit/create_stemcell_spec.rb | 266 - .../spec/unit/create_vm_spec.rb | 1055 ---- .../spec/unit/delete_disk_spec.rb | 33 - .../spec/unit/delete_snapshot_spec.rb | 31 - .../spec/unit/delete_stemcell_spec.rb | 29 - .../spec/unit/delete_vm_spec.rb | 211 - .../spec/unit/detach_disk_spec.rb | 118 - .../spec/unit/dynamic_network_spec.rb | 24 - .../unit/excon_logging_instrumentor_spec.rb | 273 - .../spec/unit/floating_ip_spec.rb | 253 - .../spec/unit/has_disk_spec.rb | 27 - .../spec/unit/has_vm_spec.rb | 34 - .../spec/unit/helpers_spec.rb | 125 - .../spec/unit/instance_type_mapper_spec.rb | 169 - .../unit/loadbalancer_configurator_spec.rb | 635 --- .../spec/unit/manual_network_spec.rb | 260 - .../spec/unit/network_configurator_spec.rb | 667 --- .../spec/unit/noop_registry_spec.rb | 22 - .../spec/unit/openstack_spec.rb | 902 --- .../spec/unit/reboot_vm_spec.rb | 28 - .../spec/unit/request_message_spec.rb | 63 - .../spec/unit/resize_disk_spec.rb | 109 - .../spec/unit/resource_pool_spec.rb | 33 - .../spec/unit/response_message_spec.rb | 57 - .../spec/unit/security_groups_spec.rb | 148 - .../spec/unit/set_disk_metadata_spec.rb | 34 - .../spec/unit/set_vm_metadata_spec.rb | 83 - .../spec/unit/snapshot_disk_spec.rb | 201 - .../spec/unit/stemcell_creator_spec.rb | 56 - .../spec/unit/tag_manager_spec.rb | 91 - .../spec/unit/vip_network_spec.rb | 37 - .../spec/unit/vm_creator_spec.rb | 130 - .../spec/unit/volume_configurator_spec.rb | 182 - .../vendor/package/addressable-2.8.7.gem | 3 - .../vendor/package/ast-2.4.3.gem | 3 - .../vendor/package/bigdecimal-3.2.2.gem | 3 - .../vendor/package/bosh-template-2.4.0.gem | 3 - .../vendor/package/bosh_common-2.0.0.gem | 3 - .../vendor/package/bosh_cpi-2.6.0.gem | 3 - .../vendor/package/builder-3.3.0.gem | 3 - .../vendor/package/byebug-12.0.0.gem | 3 - .../vendor/package/coderay-1.1.3.gem | 3 - .../vendor/package/crack-1.0.0.gem | 3 - .../vendor/package/diff-lcs-1.6.2.gem | 3 - .../vendor/package/excon-1.2.8.gem | 3 - .../vendor/package/fog-core-2.6.0.gem | 3 - .../vendor/package/fog-json-1.2.0.gem | 3 - .../vendor/package/fog-openstack-1.1.5.gem | 3 - .../vendor/package/formatador-1.1.1.gem | 3 - .../vendor/package/hashdiff-1.2.0.gem | 3 - .../vendor/package/httpclient-2.8.3.gem | 3 - .../vendor/package/json-2.13.0.gem | 3 - .../language_server-protocol-3.17.0.5.gem | 3 - .../vendor/package/lint_roller-1.1.0.gem | 3 - .../vendor/package/little-plugger-1.1.4.gem | 3 - .../vendor/package/logger-1.7.0.gem | 3 - .../vendor/package/logging-2.2.2.gem | 3 - .../vendor/package/membrane-1.1.0.gem | 3 - .../vendor/package/method_source-1.1.0.gem | 3 - .../vendor/package/mime-types-3.7.0.gem | 3 - .../package/mime-types-data-3.2025.0715.gem | 3 - .../vendor/package/multi_json-1.17.0.gem | 3 - .../vendor/package/netaddr-2.0.6.gem | 3 - .../vendor/package/openssl-3.3.0.gem | 3 - .../vendor/package/parallel-1.27.0.gem | 3 - .../vendor/package/parser-3.3.8.0.gem | 3 - .../vendor/package/prism-1.4.0.gem | 3 - .../vendor/package/pry-0.15.2.gem | 3 - .../vendor/package/pry-byebug-3.11.0.gem | 3 - .../vendor/package/public_suffix-6.0.2.gem | 3 - .../vendor/package/racc-1.8.1.gem | 3 - .../vendor/package/rainbow-3.1.1.gem | 3 - .../vendor/package/rake-13.3.0.gem | 3 - .../vendor/package/rb-readline-0.5.5.gem | 3 - .../vendor/package/regexp_parser-2.10.0.gem | 3 - .../vendor/package/rexml-3.4.1.gem | 3 - .../vendor/package/rspec-3.13.1.gem | 3 - .../vendor/package/rspec-core-3.13.5.gem | 3 - .../package/rspec-expectations-3.13.5.gem | 3 - .../vendor/package/rspec-its-2.0.0.gem | 3 - .../vendor/package/rspec-mocks-3.13.5.gem | 3 - .../vendor/package/rspec-support-3.13.4.gem | 3 - .../vendor/package/rubocop-1.78.0.gem | 3 - .../vendor/package/rubocop-ast-1.46.0.gem | 3 - .../package/ruby-progressbar-1.13.0.gem | 3 - .../vendor/package/semi_semantic-1.2.0.gem | 3 - .../vendor/package/timecop-0.9.10.gem | 3 - .../package/unicode-display_width-3.1.4.gem | 3 - .../vendor/package/unicode-emoji-4.0.4.gem | 3 - .../vendor/package/webmock-3.25.1.gem | 3 - 185 files changed, 36545 deletions(-) delete mode 100755 ci/tasks/run-lifecycle.sh delete mode 100644 ci/tasks/run-lifecycle.yml delete mode 100755 ci/tasks/run-unit-specs.sh delete mode 100644 ci/tasks/run-unit-specs.yml delete mode 100644 jobs/openstack_cpi/monit delete mode 100644 jobs/openstack_cpi/spec delete mode 100644 jobs/openstack_cpi/templates/cacert.pem.erb delete mode 100644 jobs/openstack_cpi/templates/cpi.erb delete mode 100644 jobs/openstack_cpi/templates/cpi.json.erb delete mode 100644 packages/bosh_openstack_cpi/packaging delete mode 100644 packages/bosh_openstack_cpi/pre_packaging delete mode 100644 packages/bosh_openstack_cpi/spec delete mode 100644 packages/openstack-ruby-3.1/spec.lock delete mode 100644 packages/openstack-ruby-3.4/spec.lock delete mode 100755 scripts/check-ruby-version delete mode 100755 scripts/rubocop-staged delete mode 100755 scripts/test-unit delete mode 100755 scripts/test-unit-erb delete mode 100644 src/bosh_openstack_cpi/.bundle/config delete mode 100644 src/bosh_openstack_cpi/.gitignore delete mode 100644 src/bosh_openstack_cpi/.rspec delete mode 100644 src/bosh_openstack_cpi/.rubocop.yml delete mode 100644 src/bosh_openstack_cpi/.ruby-version delete mode 100644 src/bosh_openstack_cpi/Gemfile delete mode 100644 src/bosh_openstack_cpi/Gemfile.lock delete mode 100644 src/bosh_openstack_cpi/Rakefile delete mode 100755 src/bosh_openstack_cpi/bin/bosh_openstack_console delete mode 100755 src/bosh_openstack_cpi/bin/openstack_cpi delete mode 100644 src/bosh_openstack_cpi/docs/bosh_cpi.md delete mode 100644 src/bosh_openstack_cpi/docs/cpi_config.md delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.png delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.png delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.png delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.png delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.png delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.png delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.graffle delete mode 100644 src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.png delete mode 100644 src/bosh_openstack_cpi/lib/bosh_openstack_cpi.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/availability_zone_provider.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/cloud.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/cpi_lambda.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/dynamic_network.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/excon_logging_instrumentor.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/floating_ip.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/helpers.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/instance_type_mapper.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/loadbalancer_configurator.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/manual_network.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/network.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/network_configurator.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/noop_registry.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/openstack.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/private_network.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/request_message.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/resource_pool.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/response_message.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/security_groups.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/server.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/stemcell.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/stemcell_creator.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/tag_manager.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/vip_network.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/vm_creator.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/vm_factory.rb delete mode 100644 src/bosh_openstack_cpi/lib/cloud/openstack/volume_configurator.rb delete mode 100644 src/bosh_openstack_cpi/spec/assets/sample_config.yml delete mode 100644 src/bosh_openstack_cpi/spec/integration/calculate_vm_cloud_properties_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/image_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/lifecycle_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/lifecycle_v2_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/spec_helper.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/support/integration_config.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/support/lifecycle_helpers.rb delete mode 100644 src/bosh_openstack_cpi/spec/integration/volume_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/spec_helper.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/attach_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/availability_zone_provider_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/bin/openstack_cpi_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/bosh_release/jobs/cpi/templates/cpi.json.erb_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/cloud_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/configure_networks_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/cpi_lambda_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/create_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/create_stemcell_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/create_vm_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/delete_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/delete_snapshot_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/delete_stemcell_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/delete_vm_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/detach_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/dynamic_network_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/excon_logging_instrumentor_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/floating_ip_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/has_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/has_vm_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/helpers_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/instance_type_mapper_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/loadbalancer_configurator_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/manual_network_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/network_configurator_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/noop_registry_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/openstack_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/reboot_vm_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/request_message_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/resize_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/resource_pool_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/response_message_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/security_groups_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/set_disk_metadata_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/set_vm_metadata_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/snapshot_disk_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/stemcell_creator_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/tag_manager_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/vip_network_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/vm_creator_spec.rb delete mode 100644 src/bosh_openstack_cpi/spec/unit/volume_configurator_spec.rb delete mode 100644 src/bosh_openstack_cpi/vendor/package/addressable-2.8.7.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/ast-2.4.3.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/bigdecimal-3.2.2.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/bosh-template-2.4.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/bosh_common-2.0.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/bosh_cpi-2.6.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/builder-3.3.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/byebug-12.0.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/coderay-1.1.3.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/crack-1.0.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/diff-lcs-1.6.2.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/excon-1.2.8.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/fog-core-2.6.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/fog-json-1.2.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/fog-openstack-1.1.5.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/formatador-1.1.1.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/hashdiff-1.2.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/httpclient-2.8.3.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/json-2.13.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/language_server-protocol-3.17.0.5.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/lint_roller-1.1.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/little-plugger-1.1.4.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/logger-1.7.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/logging-2.2.2.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/membrane-1.1.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/method_source-1.1.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/mime-types-3.7.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/mime-types-data-3.2025.0715.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/multi_json-1.17.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/netaddr-2.0.6.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/openssl-3.3.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/parallel-1.27.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/parser-3.3.8.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/prism-1.4.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/pry-0.15.2.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/pry-byebug-3.11.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/public_suffix-6.0.2.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/racc-1.8.1.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rainbow-3.1.1.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rake-13.3.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rb-readline-0.5.5.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/regexp_parser-2.10.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rexml-3.4.1.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rspec-3.13.1.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rspec-core-3.13.5.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rspec-expectations-3.13.5.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rspec-its-2.0.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rspec-mocks-3.13.5.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rspec-support-3.13.4.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rubocop-1.78.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/rubocop-ast-1.46.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/ruby-progressbar-1.13.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/semi_semantic-1.2.0.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/timecop-0.9.10.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/unicode-display_width-3.1.4.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/unicode-emoji-4.0.4.gem delete mode 100644 src/bosh_openstack_cpi/vendor/package/webmock-3.25.1.gem diff --git a/.gitattributes b/.gitattributes index 3ebbb9d94..d207b1802 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -/src/bosh_openstack_cpi/vendor/package/*.gem filter=lfs diff=lfs merge=lfs -text *.go text eol=lf diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f72ac8b9c..b32791484 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,28 +18,6 @@ Follow these steps to make a contribution to any of CF open source repositories: ## Development -### Prerequisites: -- ruby 2.x -- bundler - -### Running unit tests - -The CPI Ruby code has unit tests that can be run as follows. - -```bash -./scripts/test-unit -``` - -### Running ERB job templates unit tests - -The ERB templates rendered by the jobs of this Bosh Release have specific unit -tests that are run along with the other unit tests as instructed above. When -required, you can run them separately though, with this command: - -```bash -./scripts/test-unit-erb -``` - ### Running manual tests *Note:* This is not required for opening a pull request. Having green unit tests is good enough from our perspective. diff --git a/ci/tasks/run-lifecycle.sh b/ci/tasks/run-lifecycle.sh deleted file mode 100755 index 182152e48..000000000 --- a/ci/tasks/run-lifecycle.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source bosh-openstack-cpi-release/ci/tasks/utils.sh - -: "${BOSH_OPENSTACK_DOMAIN:?}" -: "${BOSH_OPENSTACK_AUTH_URL_V3:?}" -: "${BOSH_OPENSTACK_USERNAME_V3:?}" -: "${BOSH_OPENSTACK_API_KEY_V3:?}" -: "${BOSH_OPENSTACK_PROJECT:?}" -: "${BOSH_CLI_SILENCE_SLOW_LOAD_WARNING:?}" -: "${BOSH_OPENSTACK_CONNECT_TIMEOUT:?}" -: "${BOSH_OPENSTACK_READ_TIMEOUT:?}" -: "${BOSH_OPENSTACK_WRITE_TIMEOUT:?}" -: "${BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK:?}" -: "${BOSH_OPENSTACK_EXCLUDE_CINDER_V1:-""}" -: "${BOSH_OPENSTACK_AUTH_URL_V2:-""}" -: "${BOSH_OPENSTACK_USERNAME_V2:-""}" -: "${BOSH_OPENSTACK_API_KEY_V2:-""}" -: "${BOSH_OPENSTACK_CA_CERT:-""}" -: "${BOSH_OPENSTACK_VOLUME_TYPE:-""}" - -optional_value BOSH_OPENSTACK_AVAILABILITY_ZONE - -metadata=terraform-cpi/metadata - -BOSH_OPENSTACK_MANUAL_IP=$(jq --raw-output ".manual_ip" "${metadata}") -export BOSH_OPENSTACK_MANUAL_IP -BOSH_OPENSTACK_ALLOWED_ADDRESS_PAIRS=$(jq --raw-output ".allowed_address_pairs" "${metadata}") -export BOSH_OPENSTACK_ALLOWED_ADDRESS_PAIRS -BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_1=$(jq --raw-output ".no_dhcp_manual_ip_1" "${metadata}") -export BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_1 -BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_2=$(jq --raw-output ".no_dhcp_manual_ip_2" "${metadata}") -export BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_2 -BOSH_OPENSTACK_NET_ID=$(jq --raw-output ".net_id" "${metadata}") -export BOSH_OPENSTACK_NET_ID -BOSH_OPENSTACK_NET_ID_NO_DHCP_1=$(jq --raw-output ".net_id_no_dhcp_1" "${metadata}") -export BOSH_OPENSTACK_NET_ID_NO_DHCP_1 -BOSH_OPENSTACK_NET_ID_NO_DHCP_2=$(jq --raw-output ".net_id_no_dhcp_2" "${metadata}") -export BOSH_OPENSTACK_NET_ID_NO_DHCP_2 -BOSH_OPENSTACK_DEFAULT_KEY_NAME=$(jq --raw-output ".default_key_name" "${metadata}") -export BOSH_OPENSTACK_DEFAULT_KEY_NAME -BOSH_OPENSTACK_FLOATING_IP=$(jq --raw-output ".floating_ip" "${metadata}") -export BOSH_OPENSTACK_FLOATING_IP -BOSH_OPENSTACK_SECURITY_GROUP_NAME=$(jq --raw-output ".security_group_name" "${metadata}") -export BOSH_OPENSTACK_SECURITY_GROUP_NAME -BOSH_OPENSTACK_SECURITY_GROUP_ID=$(jq --raw-output ".security_group_id" "${metadata}") -export BOSH_OPENSTACK_SECURITY_GROUP_ID -pool_name=$(jq --raw-output ".loadbalancer_pool_name" "${metadata}") -if [ "${pool_name}" != "" ]; then - export BOSH_OPENSTACK_LBAAS_POOL_NAME=${pool_name} -fi - -mkdir "${PWD}/openstack-lifecycle-stemcell/stemcell" -tar -C "${PWD}/openstack-lifecycle-stemcell/stemcell" -xzf "${PWD}/openstack-lifecycle-stemcell/stemcell.tgz" -export BOSH_OPENSTACK_STEMCELL_PATH="${PWD}/openstack-lifecycle-stemcell/stemcell" - -cd bosh-openstack-cpi-release/src/bosh_openstack_cpi - -bundle install - -rspec_args=() -if [ -n "${BOSH_OPENSTACK_EXCLUDE_CINDER_V1}" ]; then - rspec_args=(--tag ~cinder_v1) -fi - - -if [ -n "${BOSH_OPENSTACK_AUTH_URL_V2}" ]; then - bundle exec rspec -f d "${rspec_args[@]}" spec/integration 2>&1 | tee ../../../output/lifecycle.log -else - echo "Excluding Keystone V2 tests." - bundle exec rspec -f d "${rspec_args[@]}" spec/integration --exclude-pattern spec/integration/lifecycle_v2_spec.rb 2>&1 | tee ../../../output/lifecycle.log -fi diff --git a/ci/tasks/run-lifecycle.yml b/ci/tasks/run-lifecycle.yml deleted file mode 100644 index 6143bc3a3..000000000 --- a/ci/tasks/run-lifecycle.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -platform: linux -image_resource: - type: registry-image - source: - repository: ghcr.io/cloudfoundry/bosh-openstack-cpi-release/openstack-cpi-release - username: ((github_read_write_packages.username)) - password: ((github_read_write_packages.password)) -inputs: - - name: bosh-openstack-cpi-release - - name: openstack-lifecycle-stemcell - - name: terraform-cpi -outputs: - - name: output -run: - path: bosh-openstack-cpi-release/ci/tasks/run-lifecycle.sh -params: - BOSH_OPENSTACK_DOMAIN: "" - BOSH_OPENSTACK_AUTH_URL_V2: "" - BOSH_OPENSTACK_USERNAME_V2: "" - BOSH_OPENSTACK_API_KEY_V2: "" - BOSH_OPENSTACK_AUTH_URL_V3: "" - BOSH_OPENSTACK_USERNAME_V3: "" - BOSH_OPENSTACK_API_KEY_V3: "" - BOSH_OPENSTACK_PROJECT: "" - BOSH_CLI_SILENCE_SLOW_LOAD_WARNING: "" - BOSH_OPENSTACK_VOLUME_TYPE: "" - BOSH_OPENSTACK_CONNECT_TIMEOUT: "" - BOSH_OPENSTACK_READ_TIMEOUT: "" - BOSH_OPENSTACK_WRITE_TIMEOUT: "" - BOSH_OPENSTACK_DEFAULT_KEY_NAME: "" - BOSH_OPENSTACK_CA_CERT: "" - BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK: "" - BOSH_OPENSTACK_EXCLUDE_CINDER_V1: "" - BOSH_OPENSTACK_AVAILABILITY_ZONE: replace-me - diff --git a/ci/tasks/run-unit-specs.sh b/ci/tasks/run-unit-specs.sh deleted file mode 100755 index 1f721f93d..000000000 --- a/ci/tasks/run-unit-specs.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -eu -o pipefail - -pushd bosh-openstack-cpi-release/src/bosh_openstack_cpi - - bundle install - - bundle exec rake rubocop - - bundle exec rake spec:unit - -popd diff --git a/ci/tasks/run-unit-specs.yml b/ci/tasks/run-unit-specs.yml deleted file mode 100644 index 036d94c0d..000000000 --- a/ci/tasks/run-unit-specs.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -platform: linux -image_resource: - type: registry-image - source: - repository: ghcr.io/cloudfoundry/bosh-openstack-cpi-release/openstack-cpi-release - username: ((github_read_write_packages.username)) - password: ((github_read_write_packages.password)) - -inputs: -- name: bosh-openstack-cpi-release - -run: - path: bosh-openstack-cpi-release/ci/tasks/run-unit-specs.sh diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit index 0881be682..f65bb487a 100755 --- a/git-hooks/pre-commit +++ b/git-hooks/pre-commit @@ -13,5 +13,3 @@ else >&2 echo -e "${RED}Not checking whether credentials get committed. cred-alert-cli is not installed.${NC}" fi -unset GIT_DIR -scripts/rubocop-staged diff --git a/jobs/openstack_cpi/monit b/jobs/openstack_cpi/monit deleted file mode 100644 index e69de29bb..000000000 diff --git a/jobs/openstack_cpi/spec b/jobs/openstack_cpi/spec deleted file mode 100644 index d06a8b86f..000000000 --- a/jobs/openstack_cpi/spec +++ /dev/null @@ -1,134 +0,0 @@ ---- -name: openstack_cpi -templates: - cpi.erb: bin/cpi - cpi.json.erb: config/cpi.json - cacert.pem.erb: config/cacert.pem - -packages: -- openstack-ruby-3.4 -- bosh_openstack_cpi - -properties: - openstack.auth_url: - description: URL of the OpenStack Identity endpoint to connect to (required) - examples: - - description: Keystone V2 endpoint - value: http://192.168.0.1:5000/v2.0 - - description: Keystone V3 endpoint - value: http://192.168.0.1:5000/v3 - openstack.username: - description: OpenStack user name (required, if application_credential_id and application_credential_secret are not provided) - openstack.api_key: - description: OpenStack API key (required, if application_credential_id and application_credential_secret are not provided) - openstack.application_credential_id: - description: OpenStack application credential id (required, if username and api_key are not provided) - openstack.application_credential_secret: - description: OpenStack application credential secret (required, if username and api_key are not provided) - openstack.tenant: - description: OpenStack tenant name (required for Keystone API V2) - openstack.project: - description: OpenStack project name (required for Keystone API V3. Also can be used the project_id property ) - openstack.project_id: - description: OpenStack project id (required for Keystone API V3. Also can be used the project property) - openstack.domain: - description: OpenStack domain (required for Keystone API V3) - openstack.region: - description: OpenStack region (optional) - example: nova - openstack.endpoint_type: - description: OpenStack endpoint type - default: publicURL - openstack.state_timeout: - description: Timeout (in seconds) for OpenStack resources desired state - default: 300 - openstack.boot_from_volume: - description: Boot from volume - default: false - openstack.stemcell_public_visibility: - description: Set public visibility for stemcells - default: false - openstack.connection_options: - description: Hash containing additional connection parameters (optional) - examples: - - description: Provide all required custom CA certificates - value: - ca_cert: | - -----BEGIN CERTIFICATE----- - MII... - -----END CERTIFICATE----- - - description: Disable SSL verification for DevStack - value: {ssl_verify_peer: false} - - description: Any other excon [connection option](https://github.com/excon/excon#options), e.g. setting the read timeout - value: {read_timeout: 360} - openstack.default_key_name: - description: Default OpenStack keypair to use when spinning up new VMs (required) - example: bosh - openstack.default_security_groups: - description: Default OpenStack security groups to use when spinning up new VMs (required) - example: [bosh-grp] - openstack.default_volume_type: - description: Default OpenStack volume type to use when creating new disks (optional) - example: SSD - openstack.wait_resource_poll_interval: - description: Changes the delay (in seconds) between each status check to OpenStack when creating a resource (optional, by default 5) - default: 5 - openstack.config_drive: - description: Config drive device (cdrom or disk) to use as metadata service on OpenStack (optional) - example: cdrom - openstack.use_dhcp: - description: Whether to use DHCP when configuring networking on VM (for both manual and dynamic) - default: true - openstack.ignore_server_availability_zone: - description: When creating a disk, do not use the availability zone of the server, fall back to Openstacks default - default: false - openstack.human_readable_vm_names: - description: When creating a VM, use the job name as VM name if true. Otherwise use a generated UUID as name. If this parameter is set to true, the registry.endpoint parameter has to be set. - default: false - openstack.use_nova_networking: - description: 'Use Nova networking APIs instead of Neutron APIs. Note: Nova networking APIs are deprecated with the Newton release, hence this switch will likely not work in future releases.' - default: false - openstack.enable_auto_anti_affinity: - description: No longer supported (see https://www.pivotaltracker.com/story/show/159522260) - openstack.user_domain_name: - description: Defines the specific user domain to be used by the connection to the authentication service. - openstack.project_domain_name: - description: Defines the specific project domain to be used by the connection to the authentication service. - - registry.host: - description: Address of the Registry to connect to (required) - registry.port: - description: Port of the Registry for CPI and agent to connect to - default: 25777 - registry.username: - description: Username to access the Registry - registry.password: - description: Password to access the Registry - registry.endpoint: - description: Full URL for the registry endpoint that may include basic auth credentials - example: http://admin:admin-password@some-ip:port - - ntp: - description: List of NTP servers - example: ["0.us.pool.ntp.org", "1.us.pool.ntp.org"] - - agent.mbus: - description: Message bus endpoint for the agent to start accepting agent requests - example: https://user:password@192.168.0.1:5000 - - nats.user: - description: NATS username used by agent to subscribe to agent requests - nats.password: - description: NATS password used by agent to subscribe to agent requests - nats.address: - description: NATS address used by agent to subscribe to agent requests - nats.port: - description: NATS port used by agent to subscribe to agent requests - default: 4222 - - env.http_proxy: - description: Http proxy to connect to cloud API's - env.https_proxy: - description: Https proxy to connect to cloud API's - env.no_proxy: - description: No proxy environment variable diff --git a/jobs/openstack_cpi/templates/cacert.pem.erb b/jobs/openstack_cpi/templates/cacert.pem.erb deleted file mode 100644 index ab1d15b90..000000000 --- a/jobs/openstack_cpi/templates/cacert.pem.erb +++ /dev/null @@ -1 +0,0 @@ -<%= p('openstack.connection_options.ca_cert', '') %> \ No newline at end of file diff --git a/jobs/openstack_cpi/templates/cpi.erb b/jobs/openstack_cpi/templates/cpi.erb deleted file mode 100644 index f36df957c..000000000 --- a/jobs/openstack_cpi/templates/cpi.erb +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -set -e - -<% if_p('env.http_proxy') do |http_proxy| %> -export HTTP_PROXY="<%= http_proxy %>" -export http_proxy="<%= http_proxy %>" -<% end %> - -<% if_p('env.https_proxy') do |https_proxy| %> -export HTTPS_PROXY="<%= https_proxy %>" -export https_proxy="<%= https_proxy %>" -<% end %> - -<% if_p('env.no_proxy') do |no_proxy| %> -export NO_PROXY="<%= no_proxy %>" -export no_proxy="<%= no_proxy %>" -<% end %> - -BOSH_PACKAGES_DIR=${BOSH_PACKAGES_DIR:-/var/vcap/packages} -BOSH_JOBS_DIR=${BOSH_JOBS_DIR:-/var/vcap/jobs} - -source "${BOSH_PACKAGES_DIR}/openstack-ruby-3.4/bosh/runtime.env" -export HOME=~ - -export BUNDLE_GEMFILE=$BOSH_PACKAGES_DIR/bosh_openstack_cpi/Gemfile - -bundle_cmd="$BOSH_PACKAGES_DIR/openstack-ruby-3.4/bin/bundle" - -exec $bundle_cmd exec $BOSH_PACKAGES_DIR/bosh_openstack_cpi/bin/openstack_cpi \ - $BOSH_JOBS_DIR/openstack_cpi/config/cpi.json \ - $BOSH_JOBS_DIR/openstack_cpi/config/cacert.pem diff --git a/jobs/openstack_cpi/templates/cpi.json.erb b/jobs/openstack_cpi/templates/cpi.json.erb deleted file mode 100644 index 4d864a140..000000000 --- a/jobs/openstack_cpi/templates/cpi.json.erb +++ /dev/null @@ -1,80 +0,0 @@ -<%= - params = { - 'cloud' => { - 'plugin' => 'openstack', - 'properties' => { - 'openstack' => { - 'auth_url' => p('openstack.auth_url'), - 'default_key_name' => p('openstack.default_key_name'), - 'default_security_groups' => p('openstack.default_security_groups'), - 'default_volume_type' => p('openstack.default_volume_type', nil), - 'wait_resource_poll_interval' => p('openstack.wait_resource_poll_interval'), - 'ignore_server_availability_zone' => p('openstack.ignore_server_availability_zone'), - 'use_nova_networking' => p('openstack.use_nova_networking') - }, - } - } - } - - openstack_params = params['cloud']['properties']['openstack'] - if_p('openstack.application_credential_id') { |value| openstack_params['application_credential_id'] = value } - if_p('openstack.application_credential_secret') { |value| openstack_params['application_credential_secret'] = value } - if_p('openstack.username') { |value| openstack_params['username'] = value } - if_p('openstack.api_key') { |value| openstack_params['api_key'] = value } - if_p('openstack.region') { |value| openstack_params['region'] = value } - if_p('openstack.endpoint_type') { |value| openstack_params['endpoint_type'] = value } - if_p('openstack.state_timeout') { |value| openstack_params['state_timeout'] = value } - if_p('openstack.stemcell_public_visibility') { |value| openstack_params['stemcell_public_visibility'] = value } - if_p('openstack.connection_options') { |value| openstack_params['connection_options'] = value } - if_p('openstack.boot_from_volume') { |value| openstack_params['boot_from_volume'] = value } - if_p('openstack.config_drive') { |value| openstack_params['config_drive'] = value } - if_p('openstack.use_dhcp') { |value| openstack_params['use_dhcp'] = value } - if_p('openstack.domain') { |value| openstack_params['domain'] = value } - if_p('openstack.user_domain_name') { |value| openstack_params['user_domain_name'] = value } - if_p('openstack.project_domain_name') { |value| openstack_params['project_domain_name'] = value } - if_p('openstack.project') { |value| openstack_params['project'] = value } - if_p('openstack.project_id') { |value| openstack_params['project_id'] = value } - if_p('openstack.tenant') { |value| openstack_params['tenant'] = value } - if_p('openstack.human_readable_vm_names') { |value| openstack_params['human_readable_vm_names'] = value } - - if_p('openstack.enable_auto_anti_affinity') do - raise "Property 'enable_auto_anti_affinity' is no longer supported. Please remove it from your configuration." - end - - if_p('ntp') do |ntp| - params['cloud']['properties']['agent'] ||= {} - params['cloud']['properties']['agent']['ntp'] = ntp - end - - if p(['registry.username', 'registry.password', 'registry.endpoint', 'registry.host'], false) - params['cloud']['properties']['registry'] = { - 'user' => p('registry.username'), - 'password' => p('registry.password'), - 'address' => p('registry.host') - } - - if_p('registry.endpoint') do |endpoint| - params['cloud']['properties']['registry']['endpoint'] = endpoint - end.else do - # 'registry.host==127.0.0.1' && registry.port==6901 is a special bosh-init case. Don't raise exception in this case. - if p('openstack.human_readable_vm_names') == true && p('registry.host') != '127.0.0.1' && p('registry.port') != 6901 - raise "Property 'human_readable_vm_names' can only be used together with 'registry.endpoint'. " + - "Please refer to http://bosh.io/docs/openstack-registry.html." - else - params['cloud']['properties']['registry']['endpoint'] = "http://#{p('registry.host')}:#{p('registry.port')}" - end - end - end - - if_p('agent.mbus') do |mbus| - params['cloud']['properties']['agent'] ||= {} - params['cloud']['properties']['agent']['mbus'] = mbus - end.else do - if p(['nats.user', 'nats.password', 'nats.address', 'agent.nats.address'], false) - params['cloud']['properties']['agent'] ||= {} - params['cloud']['properties']['agent']['mbus'] = "nats://#{p('nats.user')}:#{p('nats.password')}@#{p(['agent.nats.address', 'nats.address'])}:#{p('nats.port')}" - end - end - - JSON.dump(params) -%> diff --git a/packages/bosh_openstack_cpi/packaging b/packages/bosh_openstack_cpi/packaging deleted file mode 100644 index affe7fc84..000000000 --- a/packages/bosh_openstack_cpi/packaging +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -set -e -x - -BOSH_PACKAGES_DIR=${BOSH_PACKAGES_DIR:-/var/vcap/packages} -# shellcheck disable=SC1090 -source "${BOSH_PACKAGES_DIR}/openstack-ruby-3.4/bosh/compile.env" - -cp -a bosh_openstack_cpi/* "${BOSH_INSTALL_TARGET}" -cd "${BOSH_INSTALL_TARGET}" - -export BUNDLER_VERSION="$(bundle -v | grep -o -e '[0-9.]*')" -bundle config set --local cache_path 'vendor/package' - -bosh_bundle_local - - diff --git a/packages/bosh_openstack_cpi/pre_packaging b/packages/bosh_openstack_cpi/pre_packaging deleted file mode 100644 index bb37e76f4..000000000 --- a/packages/bosh_openstack_cpi/pre_packaging +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -export BUNDLE_APP_CONFIG=$(mktemp -d $TMPDIR/bundler_config_XXXXXX) -bundle config set --local cache_path 'vendor/package' -bundle config set --local without 'development test' - -pushd ${BUILD_DIR}/bosh_openstack_cpi - bundle package --all-platforms --no-install -popd - -rm -rf $BUNDLE_APP_CONFIG diff --git a/packages/bosh_openstack_cpi/spec b/packages/bosh_openstack_cpi/spec deleted file mode 100644 index 0bff2848e..000000000 --- a/packages/bosh_openstack_cpi/spec +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: bosh_openstack_cpi -dependencies: -- openstack-ruby-3.4 -files: -- bosh_openstack_cpi/Gemfile -- bosh_openstack_cpi/Gemfile.lock -- bosh_openstack_cpi/bin/**/* -- bosh_openstack_cpi/lib/**/* -- bosh_openstack_cpi/.bundle/* - diff --git a/packages/openstack-ruby-3.1/spec.lock b/packages/openstack-ruby-3.1/spec.lock deleted file mode 100644 index 23ba98c97..000000000 --- a/packages/openstack-ruby-3.1/spec.lock +++ /dev/null @@ -1,2 +0,0 @@ -name: openstack-ruby-3.1 -fingerprint: 13c5d59dc656712a7da88fd6c19aa273dc8fd9c7297156313e61d35a31e7d629 diff --git a/packages/openstack-ruby-3.4/spec.lock b/packages/openstack-ruby-3.4/spec.lock deleted file mode 100644 index 287498983..000000000 --- a/packages/openstack-ruby-3.4/spec.lock +++ /dev/null @@ -1,4 +0,0 @@ -name: openstack-ruby-3.4 -fingerprint: bd92d8beb74c60f3d618e26ffda3525f2a1d2e1aacabd628a5b4b32883a05482 -dependencies: -- openstack-rust-1-linux diff --git a/scripts/check-ruby-version b/scripts/check-ruby-version deleted file mode 100755 index 448a7dc43..000000000 --- a/scripts/check-ruby-version +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -PROJECT_RUBY_VERSION=$(< "${script_dir}/../src/bosh_openstack_cpi/.ruby-version") - -if [[ ! -f /usr/local/share/chruby/chruby.sh ]]; then - brew install chruby -fi -source /usr/local/share/chruby/chruby.sh - -chruby "${PROJECT_RUBY_VERSION}" -if [[ $? -eq 1 ]]; then - echo "Ruby version ${PROJECT_RUBY_VERSION} not found. Installing..." - - which "ruby-install" > /dev/null 2>&1 - if [[ $? -eq 1 ]]; then - brew install ruby-install - fi - - echo "Installing Ruby version ${PROJECT_RUBY_VERSION} ..." - ruby-install "ruby" "${PROJECT_RUBY_VERSION}" - chruby "${PROJECT_RUBY_VERSION}" - - echo "Ruby version ${PROJECT_RUBY_VERSION} successfully installed." -fi -echo "Using ruby version ${PROJECT_RUBY_VERSION}" - -echo "Installing bundler..." -gem install "bundler" diff --git a/scripts/rubocop-staged b/scripts/rubocop-staged deleted file mode 100755 index 69328bc82..000000000 --- a/scripts/rubocop-staged +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'English' - -Dir.chdir(File.expand_path(__dir__ + '/../src/bosh_openstack_cpi')) do - command = 'bundle exec rubocop-git --cached' - puts("Running '#{command}'") - system(command) -end - -exit $CHILD_STATUS.exitstatus diff --git a/scripts/test-unit b/scripts/test-unit deleted file mode 100755 index e1a1c131d..000000000 --- a/scripts/test-unit +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) - -"${script_dir}/install-git-hooks" - -pushd "${script_dir}/../src/bosh_openstack_cpi" - bundle install - bundle exec rspec "spec/unit" -popd diff --git a/scripts/test-unit-erb b/scripts/test-unit-erb deleted file mode 100755 index 79d67cddb..000000000 --- a/scripts/test-unit-erb +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) - -"${script_dir}/install-git-hooks" - -pushd "${script_dir}/../src/bosh_openstack_cpi" - source "${script_dir}/check-ruby-version" - - bundle install - bundle exec rspec "spec/unit/bosh_release" -popd diff --git a/src/bosh_openstack_cpi/.bundle/config b/src/bosh_openstack_cpi/.bundle/config deleted file mode 100644 index cd449e577..000000000 --- a/src/bosh_openstack_cpi/.bundle/config +++ /dev/null @@ -1,3 +0,0 @@ ---- -BUNDLE_CACHE_ALL: "false" -BUNDLE_NO_INSTALL: "true" diff --git a/src/bosh_openstack_cpi/.gitignore b/src/bosh_openstack_cpi/.gitignore deleted file mode 100644 index a2d0b265f..000000000 --- a/src/bosh_openstack_cpi/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -coverage -.bundle -.idea -/_* -vendor/cache diff --git a/src/bosh_openstack_cpi/.rspec b/src/bosh_openstack_cpi/.rspec deleted file mode 100644 index 5f1647637..000000000 --- a/src/bosh_openstack_cpi/.rspec +++ /dev/null @@ -1,2 +0,0 @@ ---color ---format progress diff --git a/src/bosh_openstack_cpi/.rubocop.yml b/src/bosh_openstack_cpi/.rubocop.yml deleted file mode 100644 index 9c569c6b4..000000000 --- a/src/bosh_openstack_cpi/.rubocop.yml +++ /dev/null @@ -1,7 +0,0 @@ -AllCops: - NewCops: enable - DisabledByDefault: true - SuggestExtensions: false - -Lint: - Enabled: true # equivalent to `--lint` diff --git a/src/bosh_openstack_cpi/.ruby-version b/src/bosh_openstack_cpi/.ruby-version deleted file mode 100644 index 2f4b60750..000000000 --- a/src/bosh_openstack_cpi/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.4 diff --git a/src/bosh_openstack_cpi/Gemfile b/src/bosh_openstack_cpi/Gemfile deleted file mode 100644 index cd0cc29d1..000000000 --- a/src/bosh_openstack_cpi/Gemfile +++ /dev/null @@ -1,21 +0,0 @@ -source 'https://rubygems.org' - -gem 'bosh_common' -gem 'bosh_cpi' -gem 'excon' -gem 'fog-openstack' -gem 'httpclient' -gem 'membrane' -gem 'netaddr' - -group :development, :test do - gem 'pry-byebug' - gem 'rb-readline' - gem 'rake' - gem 'rspec' - gem 'rspec-its' - gem 'rubocop' - gem 'timecop' - gem 'webmock' - gem 'bosh-template' -end diff --git a/src/bosh_openstack_cpi/Gemfile.lock b/src/bosh_openstack_cpi/Gemfile.lock deleted file mode 100644 index 0fee74c67..000000000 --- a/src/bosh_openstack_cpi/Gemfile.lock +++ /dev/null @@ -1,149 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.9.0) - public_suffix (>= 2.0.2, < 8.0) - ast (2.4.3) - base64 (0.3.0) - bigdecimal (4.1.2) - bosh-template (2.4.0) - semi_semantic (~> 1.2.0) - bosh_common (2.0.0) - logging (~> 2.2.2) - openssl (>= 3.2.0) - semi_semantic (~> 1.2.0) - bosh_cpi (3.0.0) - base64 - httpclient - logger - membrane - builder (3.3.0) - byebug (13.0.0) - reline (>= 0.6.0) - coderay (1.1.3) - crack (1.0.1) - bigdecimal - rexml - diff-lcs (1.6.2) - excon (1.7.1) - logger - fog-core (2.6.0) - builder - excon (~> 1.0) - formatador (>= 0.2, < 2.0) - mime-types - fog-json (1.4.0) - fog-core - json (~> 2.19) - fog-openstack (1.1.5) - fog-core (~> 2.1) - fog-json (>= 1.0) - formatador (1.2.3) - reline - hashdiff (1.2.1) - httpclient (2.9.0) - mutex_m - io-console (0.9.2) - json (2.21.2) - language_server-protocol (3.17.0.6) - lint_roller (1.1.0) - little-plugger (1.1.4) - logger (1.7.0) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) - membrane (1.1.0) - method_source (1.1.0) - mime-types (3.7.0) - logger - mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2026.0701) - multi_json (1.21.1) - mutex_m (0.3.0) - netaddr (2.0.6) - openssl (4.0.2) - parallel (2.1.0) - parser (3.3.12.0) - ast (~> 2.4.1) - racc - prism (1.9.0) - pry (0.16.0) - coderay (~> 1.1) - method_source (~> 1.0) - reline (>= 0.6.0) - pry-byebug (3.12.0) - byebug (~> 13.0) - pry (>= 0.13, < 0.17) - public_suffix (7.0.5) - racc (1.8.1) - rainbow (3.1.1) - rake (13.4.2) - rb-readline (0.5.5) - regexp_parser (2.12.0) - reline (0.7.0) - io-console (~> 0.5) - rexml (3.4.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-its (2.0.0) - rspec-core (>= 3.13.0) - rspec-expectations (>= 3.13.0) - rspec-mocks (3.13.8) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.7) - rubocop (1.90.0) - json (>= 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (>= 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.49.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.50.0) - parser (>= 3.3.7.2) - prism (~> 1.7) - ruby-progressbar (1.13.0) - semi_semantic (1.2.0) - timecop (0.9.11) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - webmock (3.26.4) - addressable (>= 2.8.0) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - -PLATFORMS - ruby - -DEPENDENCIES - bosh-template - bosh_common - bosh_cpi - excon - fog-openstack - httpclient - membrane - netaddr - pry-byebug - rake - rb-readline - rspec - rspec-its - rubocop - timecop - webmock - -BUNDLED WITH - 2.6.9 diff --git a/src/bosh_openstack_cpi/Rakefile b/src/bosh_openstack_cpi/Rakefile deleted file mode 100644 index 34b5d0074..000000000 --- a/src/bosh_openstack_cpi/Rakefile +++ /dev/null @@ -1,10 +0,0 @@ -require 'rspec/core/rake_task' -require 'rubocop/rake_task' - -namespace :spec do - RSpec::Core::RakeTask.new(:unit) do |t| - t.pattern = 'spec/unit/**{,/*/**}/*_spec.rb' - end -end - -RuboCop::RakeTask.new \ No newline at end of file diff --git a/src/bosh_openstack_cpi/bin/bosh_openstack_console b/src/bosh_openstack_cpi/bin/bosh_openstack_console deleted file mode 100755 index 70472bd83..000000000 --- a/src/bosh_openstack_cpi/bin/bosh_openstack_console +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -# Usage example: -# irb(main):001:0> cpi.create_vm("test", -# "natty-server-cloudimg-amd64", -# {"instance_type" => "m1.tiny"}, -# {"default" => {"type" => "dynamic", "cloud_properties" => {"security_groups" => ["name" => "default"]}}}, -# [], -# {"foo" =>"bar"}) -$LOAD_PATH.unshift File.expand_path('../lib', __dir__) - -require 'bosh_openstack_cpi' -require 'irb' -require 'irb/completion' -require 'ostruct' -require 'optparse' - -config_file = nil - -opts_parser = OptionParser.new do |opts| - opts.on('-c', '--config FILE') { |file| config_file = file } -end - -opts_parser.parse! - -unless config_file - puts opts_parser - exit(1) -end - -@config = Psych.load_file(config_file) - -module ConsoleHelpers - def cpi - @cpi ||= Bosh::OpenStackCloud::Cloud.new(@config, 2) - end - - def openstack - cpi.compute - end - - def glance - cpi.glance - end - - def registry - cpi.registry - end -end - -cloud_config = OpenStruct.new(logger: Logger.new(STDOUT)) - -Bosh::Clouds::Config.configure(cloud_config) - -include ConsoleHelpers - -begin - require 'ruby-debug' - puts '=> Debugger enabled' -rescue LoadError - puts '=> ruby-debug not found, debugger disabled' -end - -puts '=> Welcome to BOSH OpenStack CPI console' -puts "You can use 'cpi' to access CPI methods" -puts "You can use 'openstack' to access Fog::Compute::OpenStack methods" -puts "You can use 'glance' to access Fog::Image::OpenStack methods" - -IRB.start diff --git a/src/bosh_openstack_cpi/bin/openstack_cpi b/src/bosh_openstack_cpi/bin/openstack_cpi deleted file mode 100755 index 21d9d4fb1..000000000 --- a/src/bosh_openstack_cpi/bin/openstack_cpi +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env ruby -$LOAD_PATH.unshift File.expand_path('../lib', __dir__) - -require 'yaml' -require 'ostruct' -require 'bosh/cpi' -require 'cloud/openstack' - -cpi_config = YAML.load_file(ARGV.shift) -ssl_ca_file = ARGV.shift - -cloud_config = OpenStruct.new(logger: Bosh::Cpi::Logger.new(STDERR)) - -Bosh::Clouds::Config.configure(cloud_config) - -cpi_log = StringIO.new -Dir.mktmpdir do |dir| - cpi_lambda = Bosh::OpenStackCloud::CpiLambda.create(cpi_config, cpi_log, ssl_ca_file, File.join(dir, 'cacert_context.pem')) - cli = Bosh::Cpi::Cli.new(cpi_lambda, cpi_log, STDOUT) - - cli.run(ARGF.read) -end diff --git a/src/bosh_openstack_cpi/docs/bosh_cpi.md b/src/bosh_openstack_cpi/docs/bosh_cpi.md deleted file mode 100644 index 1a178b5fc..000000000 --- a/src/bosh_openstack_cpi/docs/bosh_cpi.md +++ /dev/null @@ -1,104 +0,0 @@ -# OpenStack CPI Implementation # -OpenStack CPI is an implementation of BOSH CPI. It allows BOSH to interface with various services in OpenStack like glance, controller and registry. - -The OpenStack CPI is configured in the BOSH deployment manifest. Read the [OpenStack CPI Config docs](bosh_openstack_cpi/cpi_config.md) for more details. - -In the sections below we outline the implementation details for each of the method in CPI interface. - -## Initialize ## - -Implementation of `def initialize(options)` method. - -1. Validate the `options` passed to this method -2. Instantiate `Registry_Client`. -3. Extract required properties from `options` object. -4. Instantiate `Bosh::OpenStackCloud::Openstack` with the provided openstack options. It is a `Fog` wrapper and manages all connections to OpenStack services. Connections are created lazy. First access to a service triggers the connection setup. `Bosh::OpenStackCloud::Openstack` manges the connections to the follwoing services: - + Nova Service - 1. Instantiate `Fog::Compute` instance - 2. `Fog::Compute::OpenStack` instance is created - 3. A New `Fog::Connection` object connects with the remote Nova Compute Service - + Glance service - 1. Instantiate `Fog::Image` instance - 2. `Fog::Image::OpenStack` instance is created - 3. A New `Fog::Connection` object connects with the remote Glance Service - + Cinder service - 1. Instantiate `Fog::Volume` instance - 2. `Fog::Volume::OpenStack` instance is created - 3. A New `Fog::Connection` object connects with the remote Cinder Service - + Neutron service - 1. Instantiate `Fog::Network` instance - 2. `Fog::OpenStack::Network` instance is created - 3. A New `Fog::Connection` object connects with the remote Neutron Service - -![openstack_cpi_createstemcell](images/openstack_cpi_initialize.png) - - -## Create Stemcell ## - -Implementation of method `create_stemcell(image_path, cloud_properties)` -Steps outlined below are the flow control implemented to extract and upload kernel image, ramdisk and stem cell image. - -1. Extract parameters from the `cloud_properties`. Check if `kernel_file` parameter exists, instantiate `kernel_image` object -2. Construct `kernel_params` -3. Upload `kernel_image` to glance service by calling the method `upload_image(kernel_params)` -4. If params contain `ramdisk_file` -5. Instantiate ramdisk_image object and populate `ramdisk_parama` -6. Upload the `ramdisk_image` to glance service by calling `upload(ramdisk_params)` -7. Populate `image_params` for the stem cell to be uploaded to glance service -8. Call the method `upload_image(image_params)` - -Figure below shows the flow control for the method `create_stemcell(image_path, cloud_properties)` - -![openstack_cpi_createstemcell](images/openstack_cpi_createstemcell.png) - -## Delete Stemcell ## - -![openstack_cpi_deletestemcell](images/openstack_cpi_deletestemcell.png) - -## Create VM ## - -Implementation of -`def create_vm(agent_id, stemcell_id, resource_pool, network_spec = nil, disk_locality = nil, environment = nil)` method. - -1. Retrieve `security_groups` available on OpenStack and validate them against configured. -2. If option `config_drive` and multiple manual networks are set, create network ports for manual networks. -3. Find stemcell image for passed `stemcell_id`. -4. Find OpenStack flavor for configured `instance_type`. -5. Create VM. -6. If `loadbalancer_pools` are specified, add VM as member to each pool and tag VM with pool and membership ID. -7. If option `human_readable_vm_names` is provided, tag the created VM with it's BOSH registry key. -8. Update BOSH registry settings for the new VM. - -![openstack_cpi_create_vm](images/openstack_cpi_create_vm.png) - -## Delete VM ## - -Implementation of `delete_vm(server_id)`. This method deletes the VM created in Nova Compute. - -1. Get the `server` of the VM to be deleted -2. If Neutron exists, get network port ids for server. -3. Call `server.destroy`. This will send `delete_server` request to Nova Compute. -4. If Neutron exists, delete network ports -5. If VM was tagged with LBaaS pools, remove VM membership from load balancer pools -6. Delete the settings from Registry by calling `delete_settings` method. - -Figure below shows the flow control for `delete_vm` method - -![openstack_cpi_delete_vm](images/openstack_cpi_delete_vm.png) - -## Create Disk ## - -1. Check if size passed is integer, is greater than 1024 and less than 1024*1000, else throw an error -2. Create `volume_params` -3. Call `create()volume_params)` on `Fog::Compute` service - 1. `create_volume` request on `Fog::Volume::OpenStack` - 2. Opens a `Fog::Connection` request to access the remote service and create a volume. - -Figure below shows the flow control of `create_disk` method - -![openstack_cpi_create_disk](images/openstack_cpi_create_disk.png) - -## Delete Disk ## - -This method deletes the volume created in OpenStack Nova Volume -![openstack_cpi_delete_disk](images/openstack_cpi_delete_disk.png) diff --git a/src/bosh_openstack_cpi/docs/cpi_config.md b/src/bosh_openstack_cpi/docs/cpi_config.md deleted file mode 100644 index 810e2a76f..000000000 --- a/src/bosh_openstack_cpi/docs/cpi_config.md +++ /dev/null @@ -1,113 +0,0 @@ -#OpenStack CPI Configuration - -CPI configuration is specified in both the microBOSH deployment manifest and the BOSH release deployment manifest. - -microBOSH deployment manifest example: - -``` ---- -name: my-micro - -... - -cloud: - plugin: openstack - properties: - openstack: - auth_url: http://0.0.0.0:5000/v2.0 - username: openstack-user - api_key: openstack-password - tenant: dev - region: west-coast - endpoint_type: publicURL - state_timeout: 300 - boot_from_volume: false - stemcell_public_visibility: false - connection_options: {} - default_key_name: - default_security_groups: - wait_resource_poll_interval: 5 - config_drive: disk - registry: - endpoint: http://0.0.0.0:25777 - user: registry-user - password: registry-password - agent: {} # optional agent config - -``` - -BOSH deployment manifest example: - -``` ---- -name: my-bosh - -... - -properties: - openstack: - auth_url: http://0.0.0.0:5000/v3.0 - username: openstack-user - api_key: openstack-password - tenant: dev - domain: domain1 - region: west-coast - endpoint_type: publicURL - state_timeout: 300 - boot_from_volume: false - stemcell_public_visibility: false - connection_options: {} - default_key_name: - default_security_groups: - wait_resource_poll_interval: 5 - config_drive: disk - registry: - address: 0.0.0.0 - http: - port: 25777 - user: registry-user - password: registry-password - agent: {} # optional agent config - -``` - -## OpenStack Properties - -* `auth_url` - URL of the OpenStack Identity endpoint to connect to -* `username` - OpenStack user name -* `api_key` - OpenStack API key -* `tenant` - OpenStack tenant name -* `domain` - (optional) OpenStack domain (defaults to 'Default') -* `region` - (optional) OpenStack region -* `endpoint_type` - (optional) OpenStack endpoint type (defaults to 'publicURL') -* `state_timeout` - (optional) Timeout (in seconds) for OpenStack resources desired state (defaults to 300) -* `boot_from_volume` - (optional) Boot from volume -* `stemcell_public_visibility` - (optional) Set public visibility for stemcells (defaults to false) -* `connection_options` - Hash containing optional connection parameters to the OpenStack API -* `default_key_name` - Default OpenStack keypair to use when spinning up new vms -* `default_security_groups` - Default OpenStack security groups to use when spinning up new vms -* `wait_resource_poll_interval` - (optional) Changes the delay (in seconds) between each status check to OpenStack when creating a resource -* `config_drive` - (optional) Config drive device (cdrom or disk) to use as metadata service on OpenStack - - -## Registry Properties - -Registry properties are directly set in the microBOSH deployment manifest, but full BOSH has some slight indirection. - -microBOSH properties: - -* `endpoint` - URI (including scheme, host and port) of the Registry service -* `user` - User to access the Registry -* `password` - Password to access the Registry - -BOSH properties: - -* `address` - Address of the Registry service -* `http.port` - Port of the Registry to connect to -* `http.user` - User to access the Registry -* `http.password` - Password to access the Registry - -## Agent Properties - -Agent properties is a hash passed directly to the agent. - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.graffle deleted file mode 100644 index 12e4f79e0..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.graffle +++ /dev/null @@ -1,1969 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-20 10:10:00 +0000 - Creator - rajdeepd - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Bounds - {{378, 403.7253532409668}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 360 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{304, 124.54520702362061}, {143, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 357 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Bosh OpenStack CPI} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 352 - Info - 8 - - ID - 353 - Points - - {300.01228332519565, 608.98254741887342} - {233.03073883056641, 608.9825803205315} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 315 - - - - Bounds - {{63, 590.5560302734375}, {170.03073883056641, 36.85308837890625}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 352 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.973627 - g - 0.992442 - r - 0.834 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.850363 - g - 0.883832 - r - 0.488493 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Volume} - - - - Bounds - {{341.3319575753456, 570.2429509286485}, {54, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 320 - Line - - ID - 318 - Position - 0.59569668769836426 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -request } - VerticalPad - 3 - - Wrap - NO - - - Bounds - {{321.03288241839817, 473.45068418304118}, {93, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 319 - Line - - ID - 317 - Position - 0.47598496079444885 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -create_volume } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 315 - - ID - 318 - Points - - {367.5092134475708, 553.36860656738281} - {368.89035949851444, 598.4827475660544} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 314 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 314 - - ID - 317 - Points - - {367.5543794631958, 459} - {367.50921617744456, 510.36860656738281} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 316 - Info - 13 - - - - Bounds - {{298.8548583984375, 430}, {137.3990421295166, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 316 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Compute} - - - - Bounds - {{300.51228332519531, 598.98251342773438}, {137.3990421295166, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 315 - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{298.8096923828125, 510.36860656738281}, {137.3990421295166, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 314 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Volume::\ -OpenStack} - - - - Bounds - {{297.02395568167469, 351.29157276299429}, {141, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 308 - Line - - ID - 258 - Position - 0.73766463994979858 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -create(volume_params) } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 258 - Position - 0.38146933913230896 - - ID - 307 - Points - - {345.99999982329541, 267.98217960429497} - {367.48264298796443, 268.00022651177414} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - Pattern - 1 - TailArrow - 0 - - - Tail - - ID - 285 - - - - Bounds - {{357.2591976275948, 318.48035579224052}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 306 - Line - - ID - 258 - Position - 0.61047923564910889 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 3} - - - - Bounds - {{205.75322617648635, 191.5}, {47, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 301 - Line - - ID - 302 - Position - 0.5864027738571167 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -create } - VerticalPad - 3 - - Wrap - NO - - - Bounds - {{226.5, 217.47472131252289}, {119, 100.91410827636719}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 285 - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Align - 0 - Pad - 15 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\b\fs24 \cf0 \expnd0\expndtw0\kerning0 -volme_params -\b0 \expnd0\expndtw0\kerning0 -\ -\ -:name \ -:description\ -:size\ -:availablity_zone } - - - - Class - LineGraphic - Head - - ID - 299 - - ID - 300 - Points - - {112, 345} - {112.43748368985048, 358.09725976452785} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{75.938514709472656, 358.59698104858398}, {74, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 299 - Shape - Rectangle - Style - - fill - - Color - - a - 0.26 - b - 0 - g - 0 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Error} - - - - Bounds - {{75.938514709472656, 293.13648483043426}, {69, 21.863536106721138}} - Class - ShapedGraphic - ID - 293 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Align - 0 - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -< 1024 OR\ -> 1024*1000} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{50.938514709472656, 256.99999306948121}, {119, 21.863536106721138}} - Class - ShapedGraphic - ID - 294 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 If \ - size is not integer \ -OR} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{31.938514709472656, 224.71001052856445}, {162, 118.17697143554688}} - Class - ShapedGraphic - ID - 296 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Align - 0 - Pad - 10 - VerticalPad - 0 - - - - Bounds - {{173.6102647359512, 189.99999618530273}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 303 - Line - - ID - 302 - Offset - 2 - Position - 0.33448684215545654 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 2} - - - - Bounds - {{123.7019228041172, 176.63347372237627}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 298 - Line - - ID - 297 - Position - 0.23375198245048523 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 1} - - - - Class - LineGraphic - Head - - ID - 296 - - ID - 297 - Points - - {133, 168.08934020996094} - {135, 206} - {115, 206} - {114.48778005344181, 225.3306941226887} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 316 - Info - 3 - - ID - 258 - Points - - {367.43839892878884, 168.08934020996094} - {367.55438219306956, 430} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 269 - Info - 11 - - - - Class - LineGraphic - Head - - ID - 285 - - ID - 302 - Points - - {157.19203287972036, 168.08934020996094} - {157, 201.5} - {288.29541015625, 201.5} - {287.7607034326499, 216.97501952863024} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 269 - Info - 15 - - - - Bounds - {{104.63043196870763, 150.08934020996094}, {315.36956787109375, 18}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 269 - Magnets - - {-0.73960024176586936, -1.1094003915786743} - {-0.42163697454145743, -1.2649110555648804} - {1.9868216701487083e-08, -1.3333333730697632} - {0.4216371064819644, -1.2649110555648804} - {0.73960031615696409, -1.1094003915786743} - {1.1094003915786752, -0.73960025003154684} - {1.2649110555648815, -0.4216368426009538} - {1.3333333730697643, 1.5894572413799324e-07} - {1.2649110555648815, 0.42163714417925036} - {1.1094003915786752, 0.73960034921967122} - {0.73960021696883971, 1.1094003915786743} - {0.42163699339010319, 1.2649110555648804} - {1.1842379282265398e-15, 1.3333333730697632} - {-0.42163733062650866, 1.2649109363555908} - {-0.73960021696883826, 1.1094003915786743} - {-1.1094003915786739, 0.73960034921967122} - {-1.2649110555648797, 0.42163676720637966} - {-1.3333333730697625, -6.3578289655197295e-07} - {-1.2649109363555902, -0.4216371798373757} - {-1.1094005107879634, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - VerticalPad - 3 - - - - Bounds - {{23.90777587890625, 116.88748168945312}, {74, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 233 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Create Disk} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{14.253219604492188, 111.68505859375}, {444.74676513671875, 239.31494140625}} - Class - ShapedGraphic - ID - 4 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - Bounds - {{264, 392.68506050109863}, {194.99998474121094, 239.31494140625}} - Class - ShapedGraphic - ID - 321 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-01-07 11:25:38 +0000 - Modifier - rajdeepd - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{963, 535}, {710, 811}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{0, 61}, {575, 672}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.png deleted file mode 100644 index 7c2aa77b3ab778d297709987ee7f84a77dcec991..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52964 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�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 diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.graffle deleted file mode 100644 index 9bc70e813..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.graffle +++ /dev/null @@ -1,4813 +0,0 @@ - - - - - ApplicationVersion - - com.omnigroup.OmniGraffle7 - 177.6.0.286920 - - CreationDate - 2012-12-26 07:23:41 +0000 - Creator - rajdeepd - CurrentSheet - 0 - GraphDocumentVersion - 13 - GuidesLocked - NO - GuidesVisible - YES - ImageCounter - 1 - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2017-05-09 15:48:23 +0000 - Modifier - the openstack cpi user - MovementHandleVisible - NO - NotesVisible - NO - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - ReadOnly - NO - Sheets - - - ActiveLayerIndex - 0 - AutoAdjust - 6 - BackgroundGraphic - - Bounds - {{0, 0}, {2448.0769230769229, 1530.1282051282051}} - Class - ShapedGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - DisplayScale - 1 in = 1.00000 in - GraphicsList - - - Bounds - {{137.9592262231638, 154.38420923887975}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 31 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 2} - - - - Class - LineGraphic - Head - - ID - 69 - - ID - 118 - Points - - {274.41113056241045, 154.38420923887975} - {274.41113056241045, 189.84917342084017} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 21 - Info - 1 - - ID - 116 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {274.41113233781994, 291.08280254777088} - {356.10233440883803, 329.78198716130788} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 77 - Position - 0.4625809125961135 - - - - Class - LineGraphic - Head - - ID - 21 - - ID - 115 - Points - - {385.97947378309135, 265.65521793255164} - {385.88945108108362, 329.28198767509019} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 24 - - - - Class - LineGraphic - ID - 114 - Points - - {386.03828374177249, 431.10102977489856} - {386.03828374177249, 473.75998941063881} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 103 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 39 - - ID - 117 - Points - - {158.29509041049988, 154.38420923887975} - {158.29509041049988, 189.84917342084017} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 103 - Info - 3 - - ID - 113 - Points - - {385.88873251973132, 372.78198716130782} - {386.03828579958292, 410.60102977489851} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 21 - Info - 13 - - - - Bounds - {{581.88444547870176, 154.25304666162859}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 112 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 5} - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - ID - 111 - Points - - {602.79731440712351, 437.66793103355212} - {602.6724762533911, 473.62882683338762} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 110 - Info - 13 - - - - Bounds - {{561.59998744726181, 403.66793103355212}, {82.394653919723396, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 110 - Magnets - - {-0.73960026163363846, -1.1094003924504587} - {-0.42163702135578457, -1.2649110640673515} - {0, -1.3333333333333333} - {0.42163702135578457, -1.2649110640673515} - {0.73960026163363846, -1.1094003924504587} - {1.1094003924504596, -0.73960026163363712} - {1.2649110640673522, -0.42163702135578257} - {1.3333333333333333, 0} - {1.2649110640673509, 0.42163702135578662} - {1.1094003924504576, 0.73960026163363979} - {0.73960026163363846, 1.1094003924504587} - {0.42163702135578257, 1.2649110640673522} - {0, 1.3333333333333333} - {-0.42163702135578257, 1.2649110640673522} - {-0.73960026163363846, 1.1094003924504587} - {-1.1094003924504576, 0.73960026163363979} - {-1.2649110640673509, 0.42163702135578662} - {-1.3333333333333333, 0} - {-1.2649110640673522, -0.42163702135578257} - {-1.1094003924504596, -0.73960026163363712} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -create(server_params)} - VerticalPad - 3 - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - ID - 109 - Points - - {549.36410392963262, 240.95801016767234} - {549.36410392963239, 473.62882683338762} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 107 - Info - 13 - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 107 - - ID - 108 - Points - - {549.36410392963239, 154.07999655604362} - {549.36410392963239, 206.45801016767234} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{516.18444694721074, 206.95801016767234}, {66.35931396484375, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 107 - Magnets - - {-0.73960026163363846, -1.1094003924504587} - {-0.42163702135578457, -1.2649110640673515} - {0, -1.3333333333333333} - {0.42163702135578457, -1.2649110640673515} - {0.73960026163363846, -1.1094003924504587} - {1.1094003924504596, -0.73960026163363712} - {1.2649110640673522, -0.42163702135578257} - {1.3333333333333333, 0} - {1.2649110640673509, 0.42163702135578662} - {1.1094003924504576, 0.73960026163363979} - {0.73960026163363846, 1.1094003924504587} - {0.42163702135578257, 1.2649110640673522} - {0, 1.3333333333333333} - {-0.42163702135578257, 1.2649110640673522} - {-0.73960026163363846, 1.1094003924504587} - {-1.1094003924504576, 0.73960026163363979} - {-1.2649110640673509, 0.42163702135578662} - {-1.3333333333333333, 0} - {-1.2649110640673522, -0.42163702135578257} - {-1.1094003924504596, -0.73960026163363712} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -find flavour} - VerticalPad - 3 - - - - Bounds - {{528.78444666557857, 154.25304666162859}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 106 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 4} - - - - Class - LineGraphic - ID - 105 - Points - - {491.32880367205831, 430.60102977489851} - {491.32880367205831, 473.62882683338762} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 102 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 102 - Info - 3 - - ID - 104 - Points - - {491.32880367205831, 327.64322585771379} - {491.32880556985072, 410.60102977489856} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 98 - Info - 13 - - - - Bounds - {{334.25179014698818, 410.60102977489851}, {103.57298718956832, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 103 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -metadata.update} - VerticalPad - 3 - - - - Bounds - {{443.56929481936578, 410.60102977489851}, {95.51901770538484, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 102 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -security_groups} - VerticalPad - 3 - - - - Bounds - {{470.68096347524477, 154.25304666162859}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 101 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 1} - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 110 - Info - 3 - - ID - 100 - Points - - {602.6724762533911, 155.5140517244738} - {602.79706287340116, 403.16793109682129} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - AllowConnections - NO - AllowLabelDrop - - AllowToConnect - - Class - LineGraphic - Head - - ID - 98 - Info - 3 - - ID - 99 - Points - - {491.12916904555044, 154.07999655604362} - {491.3288054474678, 260.16322720954713} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{446.64916194842539, 260.16322720954713}, {89.359283447265625, 67.479998648166656}} - Class - ShapedGraphic - ID - 98 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -retrieve and validate security groups} - VerticalPad - 3 - - - - Bounds - {{632.73303703422766, 154.38420923887975}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 89 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 8} - - - - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 87 - - ID - 77 - Points - - {274.41113233781994, 258.32917318495737} - {274.41113233781994, 329.13544579049909} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 69 - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - ID - 76 - Points - - {274.41113056241056, 437.60102977489856} - {274.41113056241056, 473.75998941063881} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 74 - Info - 13 - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 74 - - ID - 75 - Points - - {274.41113233782005, 373.13544579049903} - {274.41113233782005, 403.60102977489856} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 87 - - - - Bounds - {{229.73148883877764, 403.60102977489851}, {89.359283447265625, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 74 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -create_lbaas_pool_member} - VerticalPad - 3 - - - - Bounds - {{229.73148883877764, 329.63544579049903}, {89.359283447265625, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 87 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431599999999998 - g - 0.93638699999999997 - r - 0.85887400000000003 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.81430899999999995 - g - 0.60083900000000001 - r - 0.54992300000000005 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -LoadbalancerConfigurator} - - - - Bounds - {{113.61544868686707, 329.78198716130782}, {89.359283447265625, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 88 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431556463241577 - g - 0.93638718128204346 - r - 0.85887354612350464 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.814308762550354 - g - 0.60083860158920288 - r - 0.54992306232452393 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Network\ -Configurator} - - - - Bounds - {{220.73283971844683, 190.34917342084017}, {107.35658168792725, 67.479998648166656}} - Class - ShapedGraphic - ID - 69 - Shape - Diamond - Style - - fill - - Color - - b - 0.86940200000000001 - g - 0.98219100000000004 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.69062800000000002 - g - 0.78484600000000004 - r - 1 - space - srgb - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 if loadbalancerpools} - VerticalPad - 0.0 - - - - Bounds - {{253.88869003562775, 154.38420923887975}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 68 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 6} - - - - Class - LineGraphic - Head - - ID - 5 - - ID - 3 - Points - - {158.29509041049988, 372.78198716130782} - {158.29509206989331, 410.60102977489856} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 88 - Info - 13 - - - - Class - LineGraphic - ID - 4 - Points - - {158.29509041049997, 430.60102977489856} - {158.29509041049997, 473.75998941063881} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 5 - Info - 13 - - - - Bounds - {{116.53509134390862, 410.60102977489851}, {83.519998133182526, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 5 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -ports.create} - VerticalPad - 3 - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 36 - - ID - 13 - Points - - {69.169223244818525, 444.60102977489851} - {69.169225113126416, 473.75998941063881} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 30 - Info - 13 - - - - AllowConnections - NO - AllowLabelDrop - - AllowToConnect - - Class - LineGraphic - FontInfo - - Font - Helvetica - Size - 12 - - Head - - ID - 30 - - ID - 14 - Points - - {70.443109744649647, 152.72576335072517} - {69.169224394331437, 396.60102977489845} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 88 - Info - 3 - - ID - 15 - Points - - {158.29509041050002, 310.9032265336304} - {158.2950921859094, 329.78198716130788} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 37 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 37 - Info - 3 - - ID - 16 - Points - - {158.29509263080408, 258.3291734646001} - {158.29509263080408, 276.90322653363046} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 39 - - - - Bounds - {{365.40882378185194, 154.38420923887975}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 18 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 7} - - - - Bounds - {{341.2091341882799, 329.78198716130782}, {89.359196662902832, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 21 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431556463241577 - g - 0.93638718128204346 - r - 0.85887354612350464 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.814308762550354 - g - 0.60083860158920288 - r - 0.54992306232452393 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Tag\ -Manager} - - - - AllowConnections - NO - Class - LineGraphic - Head - - ID - 24 - - ID - 23 - Points - - {386.03828374177232, 154.38420923887975} - {386.03828374177232, 182.47862812297907} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{332.3599928978087, 182.97862812297907}, {107.35658168792725, 82.221089243888855}} - Class - ShapedGraphic - ID - 24 - Shape - Diamond - Style - - fill - - Color - - b - 0.86940175294876099 - g - 0.98219138383865356 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.69062763452529907 - g - 0.78484636545181274 - r - 1 - space - srgb - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 if human readable vm names} - VerticalPad - 0.0 - - - - Bounds - {{49.679998889565468, 154.38420923887975}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 29 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 3} - - - - Bounds - {{40.240784395793099, 396.60102977489851}, {57.85687769805071, 48}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 30 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -find stem cell image} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 33 - - ID - 32 - Points - - {69.169225465122565, 590.73998725414276} - {69.169225465122565, 637.91998574137676} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 35 - - - - Bounds - {{13.29344334784389, 637.91998574137688}, {111.75155979394913, 36.85308837890625}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 33 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97923249006271362 - g - 0.99126559495925903 - r - 0.86211967468261719 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.838662 - g - 0.902049 - r - 0.624939 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.87963306903839111 - g - 0.90069097280502319 - r - 0.54882305860519409 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Glance} - - - - Class - LineGraphic - Head - - ID - 35 - - ID - 34 - Points - - {69.169223244818454, 517.25998941063881} - {69.169223244818454, 569.73998725414276} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 36 - - - - Bounds - {{15.490932400854831, 570.23998725414276}, {107.35658168792725, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 35 - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{22.151715360527945, 473.75998941063881}, {94.035015768581019, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 36 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Image::\ -OpenStack} - - - - Bounds - {{102.41931051352532, 276.90322653363046}, {111.75155979394913, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 37 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -prepare ports for manual networks} - VerticalPad - 3 - - - - Bounds - {{104.61679956653626, 190.34917342084017}, {107.35658168792725, 67.479998648166656}} - Class - ShapedGraphic - ID - 39 - Shape - Diamond - Style - - fill - - Color - - b - 0.86940175294876099 - g - 0.98219138383865356 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.69062763452529907 - g - 0.78484636545181274 - r - 1 - space - srgb - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 if manual port creation} - VerticalPad - 0.0 - - - - Bounds - {{554.28134519614605, 99.900384902954102}, {143, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 40 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.79315453767776489 - g - 0.79316467046737671 - r - 0.79314583539962769 - space - srgb - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Bosh OpenStack CPI} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{628.28134519614605, 473.75998941063881}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 41 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.79315453767776489 - g - 0.79316467046737671 - r - 0.79314583539962769 - space - srgb - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0.0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 43 - Info - 3 - - ID - 42 - Points - - {472.63559326941231, 590.73998725414276} - {472.63559326941231, 637.91998574137688} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 45 - - - - Bounds - {{416.75981115213364, 637.91998574137688}, {111.75155979394913, 36.85308837890625}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 43 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97923249006271362 - g - 0.99126559495925903 - r - 0.86211967468261719 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.838662 - g - 0.902049 - r - 0.624939 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.87963306903839111 - g - 0.90069097280502319 - r - 0.54882305860519409 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Compute} - - - - Class - LineGraphic - Head - - ID - 45 - - ID - 44 - Points - - {472.6355910491082, 516.75998941063881} - {472.6355910491082, 570.23998725414276} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 46 - - - - Bounds - {{418.95730020514458, 570.23998725414276}, {107.35658168792725, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 45 - Magnets - - {-0.73960026163363979, -1.1094003924504576} - {-0.42163702135578457, -1.2649110640673515} - {0, -1.3333333333333333} - {0.42163702135578457, -1.2649110640673515} - {0.73960026163363846, -1.1094003924504587} - {0.5, -0.33333333333333215} - {1.2649110640673509, -0.42163702135578662} - {1.3333333333333333, 0} - {1.2649110640673509, 0.42163702135578662} - {0.5, 0.33333333333333215} - {0.73960026163363846, 1.1094003924504587} - {0.42163702135578457, 1.2649110640673515} - {0, 1.3333333333333333} - {-0.42163702135578457, 1.2649110640673515} - {-0.73960026163363846, 1.1094003924504587} - {-0.5, 0.33333333333333215} - {-1.2649110640673509, 0.42163702135578662} - {-1.3333333333333333, 0} - {-1.2649110640673509, -0.42163702135578662} - {-0.5, -0.33333333333333215} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{333.27119577748238, 473.75998941063881}, {278.72879054325165, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 46 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Compute::OpenStack} - - - - Class - LineGraphic - Head - - ID - 48 - - ID - 47 - Points - - {224.04206472182511, 590.73998725414276} - {224.04206472182511, 637.91998574137676} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 50 - - - - Bounds - {{168.16628260454644, 637.91998574137688}, {111.75155979394913, 36.85308837890625}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 48 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97923249006271362 - g - 0.99126559495925903 - r - 0.86211967468261719 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.838662 - g - 0.902049 - r - 0.624939 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.87963306903839111 - g - 0.90069097280502319 - r - 0.54882305860519409 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Neutron} - - - - Class - LineGraphic - Head - - ID - 50 - - ID - 49 - Points - - {224.042062501521, 516.75998941063881} - {224.042062501521, 569.73998725414276} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 51 - Info - 13 - - - - Bounds - {{170.36377165755738, 570.23998725414276}, {107.35658168792725, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 50 - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{130.96102272814659, 473.75998941063881}, {186.16207954674883, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 51 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::OpenStack::Network} - - - - Class - LineGraphic - Head - - ID - 56 - - ID - 53 - Points - - {652.47672429780766, 240.95801016767234} - {652.47672607321533, 329.78198716130788} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 55 - Info - 13 - - - - AllowConnections - NO - AllowLabelDrop - - Class - LineGraphic - Head - - ID - 55 - Info - 3 - - ID - 54 - Points - - {653.10239799263036, 154.21115913329479} - {652.47672575318109, 206.95801016767234} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{615.85105205835816, 206.95801016767234}, {73.251344478898659, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 55 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 update_\ -settings} - VerticalPad - 3 - - - - Bounds - {{607.79712596635613, 329.78198716130782}, {89.359196662902832, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 56 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431556463241577 - g - 0.93638718128204346 - r - 0.85887354612350464 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.814308762550354 - g - 0.60083860158920288 - r - 0.54992306232452393 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Registry\ -Client} - - - - Bounds - {{23.150572588585533, 99.900384902954102}, {67, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 61 - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0.0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Create VM} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{23.425600710846197, 134.72576335072517}, {673.73072191841277, 18}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 62 - Magnets - - {-0.73960026163363879, -1.1094003924504581} - {-0.42163702135578413, -1.2649110640673518} - {0, -1.3333333333333333} - {0.42163702135578357, -1.2649110640673518} - {0.73960026163363857, -1.1094003924504585} - {1.1094003924504587, -0.73960026163363846} - {1.2649110640673522, -0.42163702135578257} - {1.3333333333333333, 0} - {1.2649110640673522, 0.42163702135578257} - {1.1094003924504576, 0.73960026163363979} - {0.73960026163363846, 1.1094003924504587} - {0.42163702135578357, 1.2649110640673518} - {0, 1.3333333333333333} - {-0.42163702135578368, 1.2649110640673518} - {-0.73960026163363857, 1.1094003924504585} - {-1.1094003924504576, 0.73960026163363979} - {-1.2649110640673522, 0.42163702135578257} - {-1.3333333333333333, 0} - {-1.2649110640673522, -0.42163702135578257} - {-1.1094003924504587, -0.73960026163363846} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Align - 0 - VerticalPad - 3 - - - - Bounds - {{9.6799996942281723, 88.559998020529747}, {693.75998458266258, 367.04937744140625}} - Class - ShapedGraphic - ID - 63 - IsLocked - YES - Style - - fill - - Color - - b - 0.96036380529403687 - g - 0.96037584543228149 - r - 0.96035343408584595 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.56205445528030396 - g - 0.56206178665161133 - r - 0.56204819679260254 - space - srgb - - CornerRadius - 9 - Pattern - 1 - - - - - Bounds - {{9.6799996942281723, 467.2799895554781}, {693.75998458266258, 138.64578247070312}} - Class - ShapedGraphic - ID - 64 - IsLocked - YES - Style - - fill - - Color - - b - 0.96036380529403687 - g - 0.96037584543228149 - r - 0.96035343408584595 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.56205445528030396 - g - 0.56206178665161133 - r - 0.56204819679260254 - space - srgb - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - HPages - 2 - KeepToScale - - Layers - - - Artboards - - Lock - - Name - Layer 1 - Print - - View - - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoLineLength - 0.20000000298023224 - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - Orientation - 2 - PrintOnePage - - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - UniqueID - 1 - VPages - 1 - VisibleVoidKey - 1 - - - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UseEntirePage - - WindowInfo - - Frame - {{-0, -0}, {2555, 1417}} - ShowInfo - - ShowRuler - - Sidebar - - SidebarWidth - 200 - Sidebar_Tab - 0 - VisibleRegion - {{-235.25641025641042, 59.615384615384677}, {1296.153846153846, 797.43589743589746}} - ZoomValues - - - Canvas 1 - 1.5600000000000001 - 1 - - - - compressOnDiskKey - - copyLinkedImagesKey - - createSinglePDFKey - - exportAreaKey - 0 - exportQualityKey - 100 - exportSizesKey - - 1 - - - fileFormatKey - 0 - htmlImageTypeKey - 0 - includeBackgroundGraphicKey - - includeNonPrintingLayersKey - - lastExportTypeKey - 0 - marginWidthKey - 0.0 - previewTypeKey - 0 - readOnlyKey - - resolutionForBMPKey - 1 - resolutionForGIFKey - 1 - resolutionForHTMLKey - 1 - resolutionForJPGKey - 1 - resolutionForPNGKey - 1 - resolutionForTIFFKey - 1 - resolutionUnitsKey - 0 - saveAsFlatFileOptionKey - 3 - useArtboardsKey - - useMarginKey - - useNotesKey - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.png deleted file mode 100644 index a10bad97da4591decc5173f20b33b932c90213ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92840 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 - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-26 07:23:41 +0000 - Creator - rajdeepd - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Bounds - {{401.65958404541016, 452}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 360 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{327.65956497192383, 106}, {143, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 357 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Bosh OpenStack CPI} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 352 - Info - 3 - - ID - 355 - Points - - {252.46617384869853, 622.76139010036434} - {252.98184974988337, 650.80740356445312} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 298 - - - - Bounds - {{58.981842041015625, 650.80740356445312}, {388, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 352 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.973627 - g - 0.992442 - r - 0.834 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.850363 - g - 0.883832 - r - 0.488493 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Compute API Server} - - - - Bounds - {{203.70276768046395, 565.95412171075532}, {97.195475716511652, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 314 - Line - - ID - 311 - Position - 0.37672871351242065 - RotationType - 0 - - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -create_request} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 298 - - ID - 311 - Points - - {252.31236126271, 560.3551825125935} - {252.28089107324737, 601.76147472941307} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 293 - Info - 13 - - - - Bounds - {{183.57359313964844, 602.261474609375}, {137.3990421295166, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 298 - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{183.61322021484375, 516.85518264770508}, {137.3990421295166, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 293 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Image::\ -OpenStack} - - - - Bounds - {{115.04947044698037, 471.97082045378124}, {275.1773681640625, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 307 - Line - - ID - 305 - Position - 0.73913860321044922 - RotationType - 0 - - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -upload image} - VerticalPad - 3 - - - - Bounds - {{45, 106}, {100, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 291 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Create Stemcell} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{317.84771013259888, 177.31831693649292}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 290 - Line - - ID - 289 - Position - 0.45820209383964539 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 7} - - - - Class - LineGraphic - ID - 289 - Points - - {288.73332227470399, 156.22575759887695} - {288, 186} - {386, 188} - {386, 212.725830078125} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 245 - Info - 11 - - - - Bounds - {{388.90956406588481, 393.89758717366936}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 309 - Line - - ID - 308 - Position - 0.21028764545917511 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 8} - - - - Class - LineGraphic - Head - - ID - 293 - Info - 5 - - ID - 308 - Points - - {399.31677581556318, 345.66397422293471} - {399, 462} - {296.66823577880859, 461} - {298.11242540178307, 516.85518264770508} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 285 - - - - Bounds - {{337, 216.36292016506195}, {124.98699188232422, 128.80105590820312}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 285 - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Align - 0 - Pad - 15 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\b\fs24 \cf0 \expnd0\expndtw0\kerning0 -image_params -\b0 \expnd0\expndtw0\kerning0 -\ -\ -:name \ -:disk_format\ -:container_format\ -:location \ -:is_public } - - - - Bounds - {{227.21947259934504, 410.01180685131385}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 306 - Line - - ID - 305 - Position - 0.1533905416727066 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 6} - - - - Bounds - {{229.99612809630355, 327.14381898028546}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 281 - Line - - ID - 277 - Position - 0.46183508634567261 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 5} - - - - Class - LineGraphic - Head - - ID - 293 - - ID - 305 - Points - - {237.72635707566332, 398.99997098324775} - {237, 457} - {252.87108891984059, 457} - {252.31274400947584, 516.85518264770508} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 278 - - - - Bounds - {{179.16017913818359, 369.50001019239426}, {117.508056640625, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 278 - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - Draws - NO - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -ramdisk_params} - - - - Class - LineGraphic - Head - - ID - 278 - - ID - 277 - Points - - {241.58969342455336, 309.84999033833589} - {241.58951226416718, 309.36285400390625} - {238.6528120756206, 369.0006152892019} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 270 - - - - Bounds - {{232.37107556840016, 171.20798611367846}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 276 - Line - - ID - 272 - Position - 0.40752935409545898 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 4} - - - - Bounds - {{93.354824964226111, 413.11479065578635}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 304 - Line - - ID - 303 - Position - 0.099847592413425446 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 3} - - - - Bounds - {{94.311386000978771, 326.16396556630718}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 274 - Line - - ID - 252 - Position - 0.73561257123947144 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 2} - - - - Class - LineGraphic - Head - - ID - 270 - - ID - 272 - Points - - {242.93364429473894, 156.22575759887695} - {242.16667610587723, 216.30052787852679} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 245 - Info - 13 - - - - Bounds - {{128.0944339632988, 176.49999618530273}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 271 - Line - - ID - 269 - Position - 0.55914962291717529 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 1} - - - - Bounds - {{179.07872352048093, 216.3629150390625}, {124.98699188232422, 92.99993896484375}} - Class - ShapedGraphic - ID - 270 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 if params contain ramdisk image} - VerticalPad - 0 - - - - Class - LineGraphic - Head - - ID - 6 - - ID - 269 - Points - - {197.13396631477369, 156.22575759887695} - {197, 186} - {104, 186} - {103.45606667166638, 221.37232895155793} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 245 - Info - 15 - - - - Class - LineGraphic - Head - - ID - 293 - Info - 18 - - ID - 303 - Points - - {104.16697046179047, 400.88026370732462} - {103, 446} - {148.59444731473923, 446} - {148.59444731473923, 538} - {183.61322021484403, 538.35517239570618} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 267 - - - - Bounds - {{45.800895690917969, 371.38043087720871}, {117.508056640625, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 267 - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - Draws - NO - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -kernel_params} - - - - Bounds - {{45, 221.36299133300781}, {115.55921422462984, 87.999862670898438}} - Class - ShapedGraphic - ID - 6 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 if params contain kernel image} - VerticalPad - 0 - - - - Class - LineGraphic - Head - - ID - 267 - - ID - 252 - Points - - {102.77960711231492, 265.36292266845703} - {104.57222405615937, 251.57961654663086} - {104.55685624578476, 370.8804308665118} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 6 - - - - Bounds - {{174.23412322998047, 136.22575759887695}, {137.3990421295166, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 245 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -unpack stem cell } - VerticalPad - 3 - - - - Bounds - {{32.07110595703125, 442.85516357421875}, {447.82147216796875, 191}} - Class - ShapedGraphic - ID - 4 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - Bounds - {{32.07110595703125, 97}, {447.82147216796875, 328.9029541015625}} - Class - ShapedGraphic - ID - 316 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-01-07 08:53:37 +0000 - Modifier - rajdeepd - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{306, 383}, {1144, 1035}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{-217, -81}, {1009, 896}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.png deleted file mode 100644 index fa1a3d4b6e1bc2e691ff574d8b4fff073b964d14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59759 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 PAYZy#5Y4&=4zd3SEuWWJ diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.graffle deleted file mode 100644 index 27e082d52..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.graffle +++ /dev/null @@ -1,2591 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-20 10:10:00 +0000 - Creator - rajdeepd - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - Head - - ID - 352 - Info - 5 - - ID - 361 - Points - - {401.17249798774725, 590.87644577026367} - {401.17251533269882, 625.12625503540039} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 327 - Info - 11 - - - - Bounds - {{411.4788818359375, 371.12105178833008}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 360 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{329.8382568359375, 117.69060134887695}, {143, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 357 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Bosh OpenStack CPI} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 327 - - ID - 355 - Points - - {271.83918007214896, 625.12625503540039} - {271.83917236328136, 590.87644577026367} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 352 - Info - 3 - - - - Class - LineGraphic - Head - - ID - 352 - Info - 1 - - ID - 353 - Points - - {142.50584673881531, 590.87644577026367} - {142.5058424025774, 625.12625503540039} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 327 - Info - 15 - - - - Bounds - {{77.83917236328125, 625.12625503540039}, {388, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 352 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.973627 - g - 0.992442 - r - 0.834 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.850363 - g - 0.883832 - r - 0.488493 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Volume} - - - - Bounds - {{391.23943129815996, 514.38333824785764}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 349 - Line - - ID - 347 - Position - 0.22854259610176086 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 4.1} - - - - Bounds - {{356.83825420336211, 536.5622626444831}, {89, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 348 - Line - - ID - 347 - Position - 0.59655618667602539 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -delete_volume} - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 327 - Info - 5 - - ID - 347 - Points - - {401.58332562446594, 510.60985517501831} - {401.17251533269882, 570.87644577026367} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 326 - Info - 11 - - - - Class - LineGraphic - Head - - ID - 326 - Info - 13 - - ID - 346 - Points - - {271.83918007214896, 570.87644577026367} - {272.25, 510.60985517501831} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 327 - Info - 3 - - - - Bounds - {{357.47110287626765, 319.62958296221007}, {95, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 345 - Line - - ID - 320 - Position - 0.22867006063461304 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -volume.destroy } - VerticalPad - 3 - - Wrap - NO - - - Bounds - {{213.74999332427979, 298.62216384394895}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 344 - Line - - ID - 313 - Position - 0.5941430926322937 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 1.3} - - - - Bounds - {{261.5891530555046, 424.13121193142706}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 343 - Line - - ID - 313 - Position - 0.095241762697696686 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 1.2} - - - - Bounds - {{115.45372619019474, 431.78460850724548}, {53, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 329 - Line - - ID - 332 - Position - 0.51718384027481079 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -request } - VerticalPad - 3 - - - - Bounds - {{85.726853482120134, 528.45544369499726}, {114, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 342 - Line - - ID - 328 - Position - 0.46204021573066711 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -get_volume_details} - VerticalPad - 3 - - Wrap - NO - - - Bounds - {{130.8573104004146, 409.08471362464661}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 340 - Line - - ID - 332 - Position - 0.092797726392745972 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 1.1} - - - - Class - LineGraphic - Head - - ID - 326 - Info - 1 - - ID - 332 - Points - - {140.92223834991455, 414.12107658386236} - {142.91667003929621, 467.60985517501831} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 330 - Info - 13 - - - - Bounds - {{72.22271728515625, 371.1210765838623}, {137.3990421295166, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 330 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Compute::\ -OpenStack} - - - - Class - LineGraphic - Head - - ID - 327 - Info - 1 - - ID - 328 - Points - - {142.91667437553411, 510.60985517501831} - {142.5058424025774, 570.87644577026367} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 326 - Info - 15 - - - - Bounds - {{77.83917236328125, 570.87644577026367}, {388, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 327 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{78.25, 467.60985517501831}, {388, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 326 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Volume::\ -OpenStack} - - - - Bounds - {{395.94126176417643, 290.76525660863206}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 324 - Line - - ID - 320 - Position - 0.08749021589756012 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 4} - - - - Bounds - {{396.92659498116797, 187.56078838154738}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 323 - Line - - ID - 319 - Position - 0.47858265042304993 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 3} - - - - Bounds - {{291.00121830013126, 183.48807290482765}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 322 - Line - - ID - 316 - Position - 0.38864663243293762 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 2} - - - - Class - LineGraphic - Head - - ID - 326 - Info - 5 - - ID - 320 - Points - - {406.94741427204025, 282.06796756131979} - {404, 353} - {391.23943129815996, 353} - {391.23943129815996, 394} - {402, 395} - {401.58334296941757, 467.60985517501831} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 318 - - - - Class - LineGraphic - Head - - ID - 318 - - ID - 319 - Points - - {406.74353715284985, 168.08934020996094} - {407.64842678202399, 228.62527931381771} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{355.79937744140625, 228.92437165484625}, {104.50219710542638, 53.194953918457031}} - Class - ShapedGraphic - ID - 318 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Align - 0 - Pad - 10 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 if available} - VerticalPad - 0 - - - - Class - LineGraphic - Head - - ID - 267 - - ID - 317 - Points - - {301.25822042059082, 282.09660497491285} - {301.25855807370016, 301.60726223382159} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 315 - - - - Bounds - {{275.70430755615234, 302.10726224885184}, {51.106880187988281, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 267 - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - Draws - NO - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -state} - - - - Class - LineGraphic - Head - - ID - 315 - - ID - 316 - Points - - {301.24888911796552, 170.79415314898688} - {301.25489935371962, 227.89981602737984} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{254.00885009765625, 228.40139191851813}, {94.497802734375, 53.194953918457031}} - Class - ShapedGraphic - ID - 315 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Align - 0 - Pad - 10 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 if volume} - VerticalPad - 0 - - - - Bounds - {{197.5, 267.72855802716356}, {53, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 314 - Line - - ID - 313 - Position - 0.68335646390914917 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -volume } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 269 - Info - 14 - - ID - 313 - Points - - {272.24387219948841, 467.10989280255512} - {271, 365.75086008631865} - {224, 366} - {224, 208.8050479243775} - {224, 206.30566164409609} - {223.08690516849305, 168.08934020996094} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 326 - - - - Bounds - {{125.97498379313146, 209.03911102896791}, {76, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 308 - Line - - ID - 258 - Position - 0.2244144082069397 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -get(disk_id) } - VerticalPad - 3 - - Wrap - NO - - - Bounds - {{130.2283156898869, 293.69275312051423}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 306 - Line - - ID - 258 - Position - 0.70079588890075684 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 1} - - - - Class - LineGraphic - Head - - ID - 330 - - ID - 258 - Points - - {163.85870034111562, 168.08934020996094} - {164, 230} - {140, 230} - {140.92224107978834, 371.1210765838623} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 269 - Info - 15 - - - - Bounds - {{104.63043196870763, 150.08934020996094}, {355.36956787109375, 18}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 269 - Magnets - - {-0.73960024176586936, -1.1094003915786743} - {-0.42163697454145743, -1.2649110555648804} - {1.9868216701487083e-08, -1.3333333730697632} - {0.4216371064819644, -1.2649110555648804} - {0.73960031615696409, -1.1094003915786743} - {1.1094003915786752, -0.73960025003154684} - {1.2649110555648815, -0.4216368426009538} - {1.3333333730697643, 1.5894572413799324e-07} - {1.2649110555648815, 0.42163714417925036} - {1.1094003915786752, 0.73960034921967122} - {0.73960021696883971, 1.1094003915786743} - {0.42163699339010319, 1.2649110555648804} - {1.1842379282265398e-15, 1.3333333730697632} - {-0.42163733062650866, 1.2649109363555908} - {-0.73960021696883826, 1.1094003915786743} - {-1.1094003915786739, 0.73960034921967122} - {-1.2649110555648797, 0.42163676720637966} - {-1.3333333730697625, -6.3578289655197295e-07} - {-1.2649109363555902, -0.4216371798373757} - {-1.1094005107879634, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - VerticalPad - 3 - - - - Bounds - {{56, 113.61789512634277}, {72, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 233 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Delete Disk} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{52, 106.99999237060547}, {435, 240}} - Class - ShapedGraphic - ID - 4 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - Bounds - {{52, 360}, {435, 240}} - Class - ShapedGraphic - ID - 351 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-01-07 09:06:44 +0000 - Modifier - rajdeepd - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{457, 433}, {738, 896}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{-14, -12}, {603, 757}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.png deleted file mode 100644 index 1383407c4823f0988a4416af1196b79e0fcf4695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54512 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 - - - - ApplicationVersion - - com.omnigroup.OmniGraffle7 - 177.6.0.286920 - - CreationDate - 2012-12-20 10:10:00 +0000 - Creator - rajdeepd - CurrentSheet - 0 - GraphDocumentVersion - 13 - GuidesLocked - NO - GuidesVisible - YES - ImageCounter - 1 - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2017-05-10 08:52:59 +0000 - Modifier - the openstack cpi user - MovementHandleVisible - NO - NotesVisible - NO - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - ReadOnly - NO - Sheets - - - ActiveLayerIndex - 0 - AutoAdjust - 6 - BackgroundGraphic - - Bounds - {{0, 0}, {2362.2754491017963, 1477.8443113772455}} - Class - ShapedGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - DisplayScale - 1 in = 1.00000 in - GraphicsList - - - Class - LineGraphic - Head - - ID - 67 - Info - 3 - - ID - 68 - Points - - {581.84509602405478, 248.47556370438667} - {581.84509783168482, 292.46091196601253} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 21 - Info - 1 - - - - Bounds - {{536.35459843494345, 292.46091196601253}, {90.980995178222656, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 67 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431599999999998 - g - 0.93638699999999997 - r - 0.85887400000000003 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.81430899999999995 - g - 0.60083900000000001 - r - 0.54992300000000005 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -RegistryClient} - - - - Bounds - {{560.77295653676038, 173.69504708450529}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 65 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 6} - - - - Class - LineGraphic - ID - 64 - Points - - {470.49467563065025, 407.53063163139001} - {470.49467563065025, 460.07998931407928} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 43 - Info - 1 - - - - Class - LineGraphic - Head - - ID - 43 - - ID - 63 - Points - - {470.49467563065025, 335.46091196601247} - {470.49467563065025, 373.03063163139001} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 59 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 59 - Info - 3 - - ID - 62 - Points - - {470.4946878878157, 258.57304653460614} - {470.4946878878157, 291.96091196601253} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 60 - - - - Class - LineGraphic - Head - - ID - 60 - - ID - 61 - Points - - {470.49467563065025, 168.08934020996094} - {470.49467563065025, 204.37808674515816} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{414.96577444853517, 204.87808674515816}, {111.05780236423016, 53.194953918457031}} - Class - ShapedGraphic - ID - 60 - Shape - Diamond - Style - - fill - - Color - - b - 0.86940200000000001 - g - 0.98219100000000004 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.69062800000000002 - g - 0.78484600000000004 - r - 1 - space - srgb - - Pattern - 1 - - - Text - - Pad - 10 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 if lbaas pool tags} - VerticalPad - 0.0 - - - - Bounds - {{425.00417804153892, 292.46091196601253}, {90.980995178222656, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 59 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431599999999998 - g - 0.93638699999999997 - r - 0.85887400000000003 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.81430899999999995 - g - 0.60083900000000001 - r - 0.54992300000000005 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -LoadbalancerConfigurator} - - - - Class - LineGraphic - Head - - ID - 14 - - ID - 58 - Points - - {356.73876678261661, 335.46091196601253} - {356.85805944728719, 380.03063163138995} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - ID - 57 - Points - - {356.85805944728719, 400.53063163138995} - {356.85805944728719, 460.07998931407928} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 14 - Info - 1 - - - - Class - LineGraphic - Head - - ID - 24 - - ID - 56 - Points - - {137.66272781872229, 168.08934020996094} - {137.66272781872229, 380.03063163138995} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - ID - 55 - Points - - {246.44650554665913, 400.53063163138995} - {246.44650554665913, 460.07998931407928} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 53 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 53 - Info - 3 - - ID - 54 - Points - - {246.44650554665913, 335.46091196601253} - {246.44650554665918, 380.03063163138995} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{199.19760036477442, 380.03063163138995}, {94.497810363769531, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 53 - Magnets - - {-0.73960026163363846, -1.1094003924504587} - {-0.42163702135578457, -1.2649110640673515} - {0, -1.3333333333333333} - {0.42163702135578457, -1.2649110640673515} - {0.73960026163363846, -1.1094003924504587} - {1.1094003924504556, -0.73960026163364234} - {1.2649110640673535, -0.42163702135577852} - {1.3333333333333333, 0} - {1.2649110640673509, 0.42163702135578662} - {1.1094003924504556, 0.73960026163364234} - {0.73960026163363846, 1.1094003924504587} - {0.42163702135578257, 1.2649110640673522} - {0, 1.3333333333333333} - {-0.42163702135578257, 1.2649110640673522} - {-0.73960026163363846, 1.1094003924504587} - {-1.1094003924504556, 0.73960026163364234} - {-1.2649110640673509, 0.42163702135578662} - {-1.3333333333333333, 0} - {-1.2649110640673535, -0.42163702135577852} - {-1.1094003924504556, -0.73960026163364234} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -ports.all(server)} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 51 - - ID - 52 - Points - - {301.65228656815663, 258.57304261364851} - {301.65228656815663, 292.46091196601253} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 47 - - - - Bounds - {{199.19760036477442, 292.46091196601253}, {204.9093642643976, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 51 - Magnets - - {-0.73960024176586991, -1.1094003915786763} - {-0.42163697454145854, -1.2649110555648826} - {1.9868215517249155e-08, -1.3333333730697656} - {0.42163710648196329, -1.2649110555648826} - {0.73960031615696287, -1.1094003915786763} - {1.1094003915786741, -0.73960025003154883} - {1.2649110555648801, -0.42163684260095602} - {1.333333373069763, 1.5894572413799324e-07} - {1.2649110555648801, 0.42163714417925036} - {1.1094003915786741, 0.73960034921967122} - {0.73960021696883849, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883882, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.42163717983737792} - {-1.1094005107879639, -0.73960016419084895} - - Style - - fill - - Color - - b - 0.99431599999999998 - g - 0.93638699999999997 - r - 0.85887400000000003 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.996609 - g - 0.75748 - r - 0.483699 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.81430899999999995 - g - 0.60083900000000001 - r - 0.54992300000000005 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -NetworkConfigurator} - - - - Class - LineGraphic - ID - 46 - Points - - {68.186345486850087, 248.4755637043867} - {68.186345486850087, 460.07998931407928} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 45 - - - - Bounds - {{31.466346307606045, 214.47556370438667}, {73.439998358488083, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 45 - Magnets - - {-0.73960026163363879, -1.1094003924504581} - {-0.42163702135578413, -1.2649110640673518} - {0, -1.3333333333333333} - {0.42163702135578357, -1.2649110640673518} - {0.73960026163363857, -1.1094003924504581} - {1.1094003924504596, -0.73960026163363712} - {1.2649110640673509, -0.42163702135578662} - {1.3333333333333333, 0} - {1.2649110640673509, 0.42163702135578662} - {1.1094003924504596, 0.73960026163363712} - {0.73960026163363846, 1.1094003924504587} - {0.42163702135578357, 1.2649110640673518} - {0, 1.3333333333333333} - {-0.42163702135578357, 1.2649110640673518} - {-0.73960026163363846, 1.1094003924504587} - {-1.1094003924504596, 0.73960026163363712} - {-1.2649110640673509, 0.42163702135578662} - {-1.3333333333333333, 0} - {-1.2649110640673509, -0.42163702135578662} - {-1.1094003924504596, -0.73960026163363712} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -servers.get\ -(server_id) } - VerticalPad - 3 - - - - Bounds - {{47.427543542251726, 173.69504708450529}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 44 - Shape - Circle - Style - - fill - - Color - - b - 0.57326100000000002 - g - 0.573268 - r - 0.57325499999999996 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 1} - - - - Bounds - {{425.00417804153892, 373.03063163138995}, {90.980995178222656, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 43 - Magnets - - {0, 1} - {0, -1} - - Style - - fill - - Color - - b - 0.86827900000000002 - g - 0.98061799999999999 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250200000000003 - g - 0.74964200000000003 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -delete_lbaas_pool_member} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 20 - Info - 3 - - ID - 5 - Points - - {357.59138679226805, 565.81194040134687} - {357.59138999800012, 611.97192305281897} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 19 - Info - 13 - - - - Class - LineGraphic - Head - - ID - 19 - - ID - 6 - Points - - {357.591386792268, 503.07998931407923} - {357.59138999800012, 545.81194040134687} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 18 - Info - 13 - - - - Bounds - {{336.46604092806689, 344.15999230742455}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 8 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 4} - - - - Bounds - {{116.93262445268985, 173.69504708450529}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 9 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 3} - - - - Class - LineGraphic - ID - 12 - Points - - {137.66272781872229, 400.53063163138995} - {137.66272781872229, 460.07998931407928} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 24 - - - - Bounds - {{309.60915426540242, 380.03063163138995}, {94.497810363769531, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 14 - Magnets - - {0, 1} - {0, -1} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -port.destroy} - VerticalPad - 3 - - - - Bounds - {{225.58604340642808, 344.15999230742455}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 15 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 2} - - - - Bounds - {{199.19760036477442, 460.07998931407928}, {316.78757285498716, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 18 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::OpenStack::Network} - - - - Bounds - {{276.91650044287871, 545.81194040134687}, {161.34977269877859, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 19 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{276.91650044287871, 611.97192305281897}, {161.34977269877859, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 20 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97923249006271362 - g - 0.99126559495925903 - r - 0.86211967468261719 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.838662 - g - 0.902049 - r - 0.624939 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.87963306903839111 - g - 0.90069097280502319 - r - 0.54882305860519409 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Neutron Network} - - - - Bounds - {{536.35459843494345, 214.47556370438667}, {90.980995178222656, 34}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 21 - Magnets - - {0, 1} - {0, -1} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -delete_settings(server.name) } - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 31 - - ID - 23 - Points - - {108.1247879488476, 503.07998931407928} - {108.1247911545797, 545.81194040134687} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 30 - Info - 13 - - - - Bounds - {{90.413822636837523, 380.03063163138995}, {94.497810363769531, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 24 - Magnets - - {0, 1} - {0, -1} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -server.destroy } - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 29 - Info - 3 - - ID - 26 - Points - - {108.12478794884764, 565.81194040134687} - {108.1247911545797, 611.97192305281897} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 31 - Info - 13 - - - - Bounds - {{558.37774695592179, 446.39999002218246}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 27 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.79315453767776489 - g - 0.79316467046737671 - r - 0.79314583539962769 - space - srgb - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{484.33559361316611, 117.35999737679958}, {143, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 28 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.79315453767776489 - g - 0.79316467046737671 - r - 0.79314583539962769 - space - srgb - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Bosh OpenStack CPI} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{27.4499015994583, 611.97192305281897}, {161.34977269877859, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 29 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97923249006271362 - g - 0.99126559495925903 - r - 0.86211967468261719 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.838662 - g - 0.902049 - r - 0.624939 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.87963306903839111 - g - 0.90069097280502319 - r - 0.54882305860519409 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Compute} - - - - Bounds - {{31.929580017568242, 460.07998931407928}, {152.39041586255871, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 30 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Compute::OpenStack} - - - - Bounds - {{27.4499015994583, 545.81194040134687}, {161.34977269877859, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 31 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.84499651193618774 - g - 0.97522002458572388 - r - 0.99423378705978394 - space - srgb - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.624103 - g - 0.822736 - r - 0.999357 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.67316055297851562 - g - 0.76121300458908081 - r - 1 - space - srgb - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{449.75739032987252, 172.79999613761902}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 32 - Shape - Circle - Style - - fill - - Color - - b - 0.57326096296310425 - g - 0.57326847314834595 - r - 0.57325458526611328 - space - srgb - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 5} - - - - Class - LineGraphic - Head - - ID - 21 - Info - 2 - - ID - 49 - Points - - {581.84509602405478, 168.08934020996094} - {581.84509602405478, 213.97556370438667} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 47 - - ID - 48 - Points - - {301.65228249697321, 168.08934020996094} - {301.65228249697321, 204.37808674515816} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{246.12338131485814, 204.87808674515816}, {111.05780236423016, 53.194953918457031}} - Class - ShapedGraphic - ID - 47 - Shape - Diamond - Style - - fill - - Color - - b - 0.86940175294876099 - g - 0.98219138383865356 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.69062763452529907 - g - 0.78484636545181274 - r - 1 - space - srgb - - Pattern - 1 - - - Text - - Pad - 10 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 if neutron networking} - VerticalPad - 0.0 - - - - Class - LineGraphic - Head - - ID - 45 - Info - 3 - - ID - 38 - Points - - {68.186345486850087, 168.08934020996094} - {68.186345486850087, 213.9755637043867} - - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{31.929580017568242, 150.08934020996094}, {595.40601359559787, 18}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 39 - Magnets - - {-0.73960024176586936, -1.1094003915786743} - {-0.42163697454145743, -1.2649110555648804} - {1.9868216701487083e-08, -1.3333333730697632} - {0.4216371064819644, -1.2649110555648804} - {0.73960031615696409, -1.1094003915786743} - {1.1094003915786752, -0.73960025003154684} - {1.2649110555648815, -0.4216368426009538} - {1.3333333730697643, 1.5894572413799324e-07} - {1.2649110555648815, 0.42163714417925036} - {1.1094003915786752, 0.73960034921967122} - {0.73960021696883971, 1.1094003915786743} - {0.42163699339010319, 1.2649110555648804} - {1.1842379282265398e-15, 1.3333333730697632} - {-0.42163733062650866, 1.2649109363555908} - {-0.73960021696883826, 1.1094003915786743} - {-1.1094003915786739, 0.73960034921967122} - {-1.2649110555648797, 0.42163676720637966} - {-1.3333333730697625, -6.3578289655197295e-07} - {-1.2649109363555902, -0.4216371798373757} - {-1.1094005107879634, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.8682788610458374 - g - 0.9806181788444519 - r - 1 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.65250247716903687 - g - 0.74964249134063721 - r - 1 - space - srgb - - Pattern - 2 - - - Text - - Align - 0 - VerticalPad - 3 - - - - Bounds - {{31.929580017568242, 113.75999745726585}, {66, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 40 - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0.0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Delete VM} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{12.959999710321426, 105.07173349007826}, {629.99998591840267, 312.27630473673344}} - Class - ShapedGraphic - ID - 41 - IsLocked - YES - Style - - fill - - Color - - b - 0.96036380529403687 - g - 0.96037584543228149 - r - 0.96035343408584595 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.56205445528030396 - g - 0.56206178665161133 - r - 0.56204819679260254 - space - srgb - - CornerRadius - 9 - Pattern - 1 - - - - - Bounds - {{12.959999710321426, 439.21112973988056}, {629.99998591840267, 138.64607906341553}} - Class - ShapedGraphic - ID - 42 - IsLocked - YES - Style - - fill - - Color - - b - 0.96036380529403687 - g - 0.96037584543228149 - r - 0.96035343408584595 - space - srgb - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.56205445528030396 - g - 0.56206178665161133 - r - 0.56204819679260254 - space - srgb - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - HPages - 2 - KeepToScale - - Layers - - - Artboards - - Lock - - Name - Layer 1 - Print - - View - - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoLineLength - 0.20000000298023224 - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - Orientation - 2 - PrintOnePage - - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - UniqueID - 1 - VPages - 1 - VisibleVoidKey - 1 - - - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UseEntirePage - - WindowInfo - - Frame - {{-0, -0}, {2555, 1417}} - ShowInfo - - ShowRuler - - Sidebar - - SidebarWidth - 200 - Sidebar_Tab - 0 - VisibleRegion - {{-201.79640718562874, 65.269461077844227}, {1210.7784431137725, 744.91017964071864}} - ZoomValues - - - Canvas 1 - 1.6699999999999999 - 1 - - - - compressOnDiskKey - - copyLinkedImagesKey - - createSinglePDFKey - - exportAreaKey - 0 - exportQualityKey - 100 - exportSizesKey - - 1 - - - fileFormatKey - 0 - htmlImageTypeKey - 0 - includeBackgroundGraphicKey - - includeNonPrintingLayersKey - - lastExportTypeKey - 0 - marginWidthKey - 0.0 - previewTypeKey - 0 - readOnlyKey - - resolutionForBMPKey - 1 - resolutionForGIFKey - 1 - resolutionForHTMLKey - 1 - resolutionForJPGKey - 1 - resolutionForPNGKey - 1 - resolutionForTIFFKey - 1 - resolutionUnitsKey - 0 - saveAsFlatFileOptionKey - 3 - useArtboardsKey - - useMarginKey - - useNotesKey - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.png deleted file mode 100644 index 5aa411ab46d5d720f1e51bad6d9447bbc5304d39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66265 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 z 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 diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.graffle deleted file mode 100644 index cdae6d791..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.graffle +++ /dev/null @@ -1,3939 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-26 07:23:41 +0000 - Creator - rajdeepd - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - Head - - ID - 352 - - ID - 361 - Points - - {259.2294129214248, 572.2193717956543} - {259.49494179089896, 601.49496459960938} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 335 - Info - 13 - - - - Bounds - {{425.70345497131348, 506.87995052337646}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 345 - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 6.1} - - - - Bounds - {{393.99837303161621, 502.87994861602783}, {27.579854965209961, 28}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 344 - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 10.1} - - - - Bounds - {{360.77123069763184, 502.87994861602783}, {27.579854965209961, 28}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 343 - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 11.1} - - - - Bounds - {{101.00509071350098, 547.43933773040771}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 342 - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 8.1} - - - - Bounds - {{101.00509071350098, 527.43934345245361}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 341 - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 4.1} - - - - Bounds - {{101.00509071350098, 506.71967506408691}, {20.50001335144043, 20}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - Font - Helvetica - Size - 11 - - ID - 340 - Shape - Circle - Style - - fill - - Color - - b - 0.499609 - g - 0.499609 - r - 0.499609 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf1 1.1} - - - - Bounds - {{260.84931208787555, 506.87994081245938}, {87.159675598144531, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 339 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -delete_image} - VerticalPad - 3 - - - - Class - LineGraphic - ID - 338 - Points - - {301.49801275322432, 493.719970703125} - {300.72002753284085, 552.21936416625977} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{121.50510432420367, 506.87994081245938}, {118.38997052015668, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 337 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -get_image_by_id} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 335 - Info - 1 - - ID - 336 - Points - - {188.2380029875992, 493.71997833251953} - {187.46001776721582, 552.2193717956543} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 293 - Info - 14 - - - - Bounds - {{151.57531738281239, 552.2193717956543}, {215.30819107722482, 20}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 335 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145854, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696264, -1.1094003915786743} - {0.5, -0.3333333283662796} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {0.5, 0.33333337306976318} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.5, 0.33333337306976318} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.4216371798373757} - {-0.5, -0.33333325386047363} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{59.724312253463722, 461.71997833251953}, {385.54123306274414, 32}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 293 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145854, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786745, -0.73960025003154684} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {1.1094003915786745, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.4216371798373757} - {-1.1094005107879639, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.809665 - g - 0.973391 - r - 0.992442 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.610953 - g - 0.706552 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Image::OpenStack} - - - - Bounds - {{150.00381925746689, 246.70429252876107}, {20.50001335144043, 20.89453125}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 334 - Line - - ID - 332 - Position - 0.561756432056427 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 11\ -} - VerticalPad - 0 - - TextPlacement - 0 - Wrap - NO - - - Bounds - {{123.11460257140507, 136.32182998258111}, {72.418045043945312, 19}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - FontInfo - - Color - - b - 0.0197765 - g - 0.0245039 - r - 0.5 - - Font - Helvetica - Size - 11 - - ID - 333 - Line - - ID - 332 - Position - 0.10683052241802216 - RotationType - 0 - - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red128\green6\blue5;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf2 destroy} - VerticalPad - 3 - - - - Class - LineGraphic - ID - 332 - Points - - {159.10518547592551, 119.67820358276367} - {161.14991588169363, 364.39877414703369} - - Style - - stroke - - Color - - a - 0.78 - b - 0.0803854 - g - 0.00920142 - r - 0.5 - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{424.76553196476749, 243.46534448559515}, {20.50001335144043, 20.89453125}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 331 - Line - - ID - 329 - Position - 0.54129225015640259 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 10\ -} - VerticalPad - 0 - - TextPlacement - 0 - Wrap - NO - - - Bounds - {{397.91815901366584, 137.14362639981744}, {72.418045043945312, 19}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - FontInfo - - Color - - b - 0.0197765 - g - 0.0245039 - r - 0.5 - - Font - Helvetica - Size - 11 - - ID - 330 - Line - - ID - 329 - Position - 0.10683052241802216 - RotationType - 0 - - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red128\green6\blue5;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf2 destroy} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 326 - Info - 3 - - ID - 329 - Points - - {433.90874191818625, 120.5} - {435.95347232395437, 365.22057056427002} - - Style - - stroke - - Color - - a - 0.78 - b - 0.0803854 - g - 0.00920142 - r - 0.5 - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{401.0782065987587, 420.22027267906503}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 328 - Line - - ID - 327 - Position - 0.57514631748199463 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 9} - - - - Class - LineGraphic - Head - - ID - 326 - Info - 13 - - ID - 327 - Points - - {381.00854704543423, 461.21997834212675} - {381, 429.7202764937623} - {436, 429.7202764937623} - {435.95455317129966, 408.72056940309682} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 293 - Info - 5 - - - - Bounds - {{404.93446245489508, 365.22057056427002}, {62.038017272949219, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 326 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145854, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.4216371798373757} - {-1.1094005107879639, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -ramdisk} - - - - Bounds - {{281.22001016230564, 245.35986736114256}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 325 - Line - - ID - 323 - Position - 0.54129225015640259 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 6} - - - - Bounds - {{254.88374094237963, 137.51753322828051}, {72.418045043945312, 19}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - FontInfo - - Color - - b - 0.0197765 - g - 0.0245039 - r - 0.5 - - Font - Helvetica - Size - 11 - - ID - 324 - Line - - ID - 323 - Position - 0.10683052241802216 - RotationType - 0 - - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red128\green6\blue5;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs22 \cf2 destroy} - VerticalPad - 3 - - - - Class - LineGraphic - Head - - ID - 308 - Info - 3 - - ID - 323 - Points - - {291, 120.5} - {291.8683236050212, 368.72057056427002} - - Style - - stroke - - Color - - a - 0.78 - b - 0.0803854 - g - 0.00920142 - r - 0.5 - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - ID - 320 - Points - - {359.90933374647898, 227.00019342994034} - {359.90889600180054, 256.75954694873474} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{323.3911497497665, 265.61307203436911}, {73, 28}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 319 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Find ramdisk \ -Image} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{323.39115250309965, 301.70122909545898}, {72.418045043945312, 32}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - FontInfo - - Font - Helvetica - Size - 11 - - ID - 318 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs22 \cf0 find_by_id(\ -ramdisk_id)} - VerticalPad - 3 - - - - Bounds - {{327.22814711286901, 434.01422825908537}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 317 - Line - - ID - 315 - Offset - 1 - Position - 0.76141887903213501 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 8} - - - - Bounds - {{350.60865380000303, 133.36906015872955}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 316 - Line - - ID - 321 - Position - 0.434273362159729 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 7} - - - - Class - LineGraphic - Head - - ID - 293 - Info - 4 - - ID - 315 - Points - - {359.89663658065717, 345.39045970338992} - {359.90889600180054, 444} - {316.31131362915039, 445} - {316.75181436698813, 461.71997833251947} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 314 - - - - Bounds - {{316.31131362915039, 257.65001934766769}, {87.159675598144531, 87.240440368652344}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 314 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - - - Bounds - {{306.99999809265137, 169.90017604827881}, {105, 56.99029541015625}} - Class - ShapedGraphic - ID - 313 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 if image and ramdisk_id} - VerticalPad - 0 - - - - Bounds - {{274.93479272081339, 429.7202764937623}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 312 - Line - - ID - 311 - Position - 0.57514631748199463 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 5} - - - - Class - LineGraphic - Head - - ID - 308 - Info - 13 - - ID - 311 - Points - - {260.70902741154691, 461} - {260.70902741154691, 440} - {292, 439.00316984854919} - {291.87073556064973, 412.22056474124122} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{192.05619262696376, 265.61307203436911}, {65, 28}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 310 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Find Kernel \ -Image} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{61.104372963309288, 226.73976909989153}, {73, 28}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 309 - Line - - ID - 296 - Position - 0.32375586032867432 - RotationType - 0 - - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Find Stemcell\ - Image} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{260.84931373596191, 368.72057056427002}, {62.038017272949219, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 308 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145854, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.4216371798373757} - {-1.1094005107879639, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -kernel} - - - - Bounds - {{188.2909823676016, 301.70123672485352}, {72.418045043945312, 32}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - FontInfo - - Font - Helvetica - Size - 11 - - ID - 307 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs22 \cf0 find_by_id(\ -kernel_id)} - VerticalPad - 3 - - - - Bounds - {{141.78015182421683, 425.04688819690057}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 306 - Line - - ID - 305 - Position - 0.47938805818557739 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 2} - - - - Class - LineGraphic - Head - - ID - 304 - Info - 13 - - ID - 305 - Points - - {151.57531738281239, 458} - {152.52411270141602, 409.07698440551758} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{121.50510406494141, 366.07698440551758}, {62.038017272949219, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 304 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145854, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650977, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-1.1094003915786743, 0.73960034921967122} - {-1.2649110555648804, 0.42163676720637966} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.4216371798373757} - {-1.1094005107879639, -0.73960016419084695} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.992442 - g - 0.919661 - r - 0.828059 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -image} - - - - Bounds - {{61.247077211737633, 269.05370825361683}, {72.418045043945312, 32}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - FontInfo - - Font - Helvetica - Size - 11 - - ID - 302 - Line - - ID - 296 - Position - 0.44509318470954895 - RotationType - 0 - - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural - -\f0\fs22 \cf0 find_by_id(\ -stemcell_id)} - VerticalPad - 3 - - - - Bounds - {{50.672644689723043, 220.97900705650756}, {93.686903804452839, 92.287269592285156}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 303 - Line - - ID - 296 - Position - 0.3959956169128418 - RotationType - 0 - - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - - - Bounds - {{87.529612325131893, 178.86455051711718}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 297 - Line - - ID - 296 - Position - 0.18034569919109344 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 1} - - - - Class - LineGraphic - Head - - ID - 293 - Info - 1 - - ID - 296 - Points - - {98, 122.5} - {97, 421.36675614722321} - {123.11460257140507, 421} - {123.97054940393325, 461.2200915206455} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{47, 67.630931854248047}, {99, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 291 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Delete Stemcell} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{182.36217166700192, 420.33999396768849}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 275 - Line - - ID - 268 - Offset - 1 - Position - 0.76795053482055664 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 4} - - - - Bounds - {{215.25509738922119, 131.01610314846039}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 299 - Line - - ID - 298 - Position - 0.40462815761566162 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50101 - g - 0.50101 - r - 0.50101 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf1 3} - - - - Class - LineGraphic - Head - - ID - 6 - - ID - 298 - Points - - {225.50510406494141, 121.5} - {225.50510406494141, 165.5} - {225.40955501725654, 168.49497769492311} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 293 - Info - 2 - - ID - 268 - Points - - {224.02739493545792, 343.99995141391793} - {223, 429.7202764937623} - {188.2909823676016, 431} - {188.2380633102257, 461.71997833251947} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 267 - - - - Bounds - {{180.97635650634766, 256.25954693555832}, {87.159675598144531, 87.240440368652344}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 267 - Shape - Rectangle - Style - - fill - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGHyBYJHZl - cnNpb25YJG9iamVjdHNZJGFyY2hp - dmVyVCR0b3ASAAGGoKUHCBEVHFUk - bnVsbNQJCgsMDQ4PEF8QEk5TQ3Vz - dG9tQ29sb3JTcGFjZVYkY2xhc3Nc - TlNDb2xvclNwYWNlV05TV2hpdGWA - AoAEEANCMQDSChITFFROU0lEgAMQ - AtIWFxgZWiRjbGFzc25hbWVYJGNs - YXNzZXNcTlNDb2xvclNwYWNlohob - XE5TQ29sb3JTcGFjZVhOU09iamVj - dNIWFx0eV05TQ29sb3KiHRtfEA9O - U0tleWVkQXJjaGl2ZXLRISJUcm9v - dIABCBEaIy0yNz1DTGFodX1/gYOG - i5CSlJmkrbq9ytPY4OP1+P0AAAAA - AAABAQAAAAAAAAAjAAAAAAAAAAAA - AAAAAAAA/w== - - b - 1 - g - 1 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.771739 - g - 0.521768 - r - 0.477113 - - CornerRadius - 9 - - - - - Bounds - {{172.00000190734863, 168.50970363616943}, {105, 56.99029541015625}} - Class - ShapedGraphic - ID - 6 - Shape - Diamond - Style - - fill - - Color - - b - 0.838968 - g - 0.981553 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.630487 - g - 0.735237 - r - 0.99551 - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 if image and kernel_id} - VerticalPad - 0 - - - - Class - LineGraphic - Head - - ID - 267 - - ID - 252 - Points - - {224.51964580161635, 225.98952084505945} - {224.53982195872041, 255.7595469685505} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 6 - - - - Bounds - {{66.458778381347656, 99.999996185302734}, {385.54123306274414, 18}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 295 - Magnets - - {-0.7396002417658688, -1.1094003915786723} - {-0.42163697454145743, -1.2649110555648782} - {1.9868216701487083e-08, -1.3333333730697607} - {0.4216371064819644, -1.2649110555648782} - {0.73960031615696409, -1.1094003915786723} - {1.1094003915786752, -0.73960025003154484} - {1.2649110555648815, -0.42163684260095158} - {1.3333333730697643, 1.5894572650646909e-07} - {1.2649110555648815, 0.42163714417925258} - {1.1094003915786752, 0.73960034921967321} - {0.73960021696883971, 1.1094003915786763} - {0.42163699339010319, 1.2649110555648826} - {1.1842379282265398e-15, 1.3333333730697656} - {-0.42163733062650866, 1.264910936355593} - {-0.73960021696883771, 1.1094003915786763} - {-1.1094003915786739, 0.73960034921967321} - {-1.2649110555648797, 0.42163676720638188} - {-1.3333333730697625, -6.357828941834971e-07} - {-1.2649109363555902, -0.42163717983737348} - {-1.1094005107879634, -0.73960016419084496} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.837612 - g - 0.979516 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.588068 - g - 0.693116 - r - 0.99798 - - Pattern - 2 - - - Text - - Align - 0 - VerticalPad - 3 - - - - Bounds - {{31.584197998046875, 63.71966552734375}, {447.82147216796875, 352.28033447265625}} - Class - ShapedGraphic - ID - 4 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - Class - LineGraphic - ID - 321 - Points - - {360.85866047572324, 122.5} - {360.85866047572324, 166.5} - {360.77124049304501, 169.40246908201695} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{389, 553.71937942504883}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 360 - Shape - Rectangle - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.747526 - g - 0.747526 - r - 0.747526 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{65.49493408203125, 601.49496459960938}, {388, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 352 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.973627 - g - 0.992442 - r - 0.834 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.850363 - g - 0.883832 - r - 0.488493 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Volume} - - - - Bounds - {{34.99493408203125, 454.00000190734863}, {435, 127}} - Class - ShapedGraphic - ID - 351 - Shape - Rectangle - Style - - fill - - Color - - b - 0.950215 - g - 0.950215 - r - 0.950215 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913 - g - 0.48913 - r - 0.48913 - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-01-07 08:55:29 +0000 - Modifier - rajdeepd - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{976, 553}, {710, 811}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{0, 61}, {575, 672}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.png deleted file mode 100644 index 413ae9ec061826206ced84a0da3c793ada65359e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70136 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%0D=3 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 diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.graffle deleted file mode 100644 index 166e0188e..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.graffle +++ /dev/null @@ -1,4090 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGraffle6 - 169.8.0.255486 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-20 10:10:00 +0000 - Creator - rajdeepd - DisplayScale - 1 in = 1.00000 in - GraphDocumentVersion - 12 - GraphicsList - - - Bounds - {{157.06841206800038, 391.37573056252666}, {182.15492635980138, 20}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 260 - Line - - ID - 258 - Offset - -24 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 - new(openstack_params) #lazy} - VerticalPad - 3 - - - - Class - LineGraphic - ID - 400 - Points - - {236.39453956627611, 380.44952427238013} - {435.24657810315597, 444.23999007046223} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 259 - - - - Class - LineGraphic - Head - - ID - 371 - - ID - 398 - Points - - {236.39453956627611, 380.44952427238013} - {298.14082457468044, 442.44572338732161} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 259 - - - - Class - LineGraphic - ID - 397 - Points - - {427.48817350427794, 639.35998570919037} - {427.03999063372612, 678.2408717724287} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Class - LineGraphic - ID - 396 - Points - - {313.04896725709159, 638.21995349192048} - {312.60078438653977, 677.10083955515881} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 383 - - - - Bounds - {{407.6312342448316, 560.1934517417626}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 395 - Line - - ID - 394 - Offset - 2 - Position - 0.36649477481842041 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 386 - - ID - 394 - Points - - {427.74710913279938, 556.31998743116856} - {427.43074838862378, 594.22000412518878} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{410.00421559513603, 479.70592042476795}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 393 - Line - - ID - 392 - Position - 0.393485426902771 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 385 - Info - 3 - - ID - 392 - Points - - {428.12198438501133, 474.35998933017254} - {427.82268793424134, 513.35998852550972} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{296.97577273845565, 560.67725539472758}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 391 - Line - - ID - 390 - Offset - 2 - Position - 0.36649477481842041 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 383 - - ID - 390 - Points - - {312.87813929560133, 557.07998850941658} - {313.14467773807888, 594.21999958231743} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 381 - Info - 13 - - - - Bounds - {{294.9036112060287, 478.73980970945445}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 389 - Line - - ID - 388 - Position - 0.393485426902771 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 381 - Info - 3 - - ID - 388 - Points - - {312.92013517875472, 472.29998985008297} - {312.87814131481673, 514.07998850941658} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 371 - - - - Bounds - {{377.43999151885509, 676.79998487234116}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 387 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97362700000000002 - g - 0.99244200000000005 - r - 0.83399999999999996 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.85036299999999998 - g - 0.88383199999999995 - r - 0.48849300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 - Neutron} - - - - Bounds - {{376.85631441882072, 594.71998670697212}, {100.78158942795733, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 386 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{377.00746798327384, 513.35998852550983}, {101.6304358635042, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 385 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Network::\ -OpenStack} - - - - Bounds - {{262.11999402940273, 676.79998487234116}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 384 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97362700000000002 - g - 0.99244200000000005 - r - 0.83399999999999996 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.85036299999999998 - g - 0.88383199999999995 - r - 0.48849300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 - Cinder} - - - - Bounds - {{262.9117677993961, 594.71998670697212}, {100.78158942795733, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 383 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{262.06292136384923, 514.07998850941658}, {101.6304358635042, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 381 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Volume::\ -OpenStack} - - - - Bounds - {{376.95999166369438, 445.15998989343643}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 380 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Network} - - - - Bounds - {{184.00224352598181, 559.52410302831254}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 379 - Line - - ID - 378 - Offset - 2 - Position - 0.3664947824952256 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 364 - - ID - 378 - Points - - {199.93359267617672, 555.77555847167969} - {200.12097805286706, 593.31641247866526} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 288 - Info - 13 - - - - Bounds - {{68.954029433389735, 558.8473030055585}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 377 - Line - - ID - 376 - Offset - -2 - Position - 0.35040513273679408 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 283 - - ID - 376 - Points - - {84.989046056752102, 555.63998854160309} - {84.889134303836954, 593.31640800805167} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 279 - Info - 13 - - - - Bounds - {{66.989048075967503, 477.50303937947604}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 375 - Line - - ID - 374 - Position - 0.40348854660987854 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 279 - Info - 3 - - ID - 374 - Points - - {84.989048075967503, 470.50006103515619} - {84.989048075967503, 512.63998854160309} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 256 - - - - Bounds - {{181.89925629788752, 476.93811263989164}, {36, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 373 - Line - - ID - 372 - Position - 0.39348542524682073 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 288 - - ID - 372 - Points - - {199.87541569839962, 470.50006050929278} - {199.93600396310407, 512.27556427628429} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 287 - - - - Bounds - {{262.11999402940273, 442.79999010264874}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 371 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Volume} - - - - Bounds - {{221.47999502718449, 290.55999359488487}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 370 - Shape - Circle - Style - - fill - - Color - - b - 0.50100999999999996 - g - 0.50100999999999996 - r - 0.50100999999999996 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 4} - - - - Bounds - {{174.18267784622296, 310.24270584444582}, {145, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 369 - Line - - ID - 368 - Offset - -3 - Position - 0.47992599010467529 - RotationType - 0 - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -new(openstack_params) } - VerticalPad - 3 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 366 - - ID - 368 - Points - - {243.68850708007812, 292.3199934661389} - {243.67636110627058, 350.49997699736502} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 231 - - - - Bounds - {{175.99566818505082, 350.99997698646928}, {135.35512287720917, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 366 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Bosh::OpenStackCloud::OpenStack} - - - - Class - LineGraphic - Head - - ID - 361 - Info - 3 - - ID - 365 - Points - - {200.12097879895487, 637.31640002141933} - {199.93359469539215, 674.85725402832031} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 364 - - - - Class - LineGraphic - Head - - ID - 352 - Info - 3 - - ID - 362 - Points - - {85.044245600218204, 637.31638271770714} - {85.406372678571444, 674.63998492062092} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 283 - - - - Bounds - {{149.11837474442461, 674.85725402832031}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 361 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97362700000000002 - g - 0.99244200000000005 - r - 0.83399999999999996 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.85036299999999998 - g - 0.88383199999999995 - r - 0.48849300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 - Glance} - - - - Bounds - {{445.67999003827572, 421.55999037623405}, {69, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 360 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.74752600000000002 - g - 0.74752600000000002 - r - 0.74752600000000002 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Fog Gem} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{371.67999164760113, 156.23999650776386}, {143, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 357 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.74752600000000002 - g - 0.74752600000000002 - r - 0.74752600000000002 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 Bosh OpenStack CPI} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{34.591152727603912, 674.63998492062092}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 352 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215813308637e-08, -1.3333333730697632} - {0.42163710648196356, -1.2649110555648804} - {0.73960031615696331, -1.1094003915786743} - {0.50000000000000011, -0.3333333283662796} - {1.2649110555648806, -0.4216368426009538} - {1.3333333730697634, 1.5894572413799324e-07} - {1.2649110555648806, 0.42163714417925036} - {0.50000000000000011, 0.33333337306976318} - {0.73960021696883893, 1.1094003915786743} - {0.42163699339010235, 1.2649110555648804} - {2.9605948205663494e-16, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883871, 1.1094003915786743} - {-0.49999999999999994, 0.33333337306976318} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-0.49999999999999994, -0.33333325386047363} - - Style - - fill - - Color - - b - 0.97362700000000002 - g - 0.99244200000000005 - r - 0.83399999999999996 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.80124 - g - 0.886111 - r - 0.566755 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.85036299999999998 - g - 0.88383199999999995 - r - 0.48849300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Nova Compute} - - - - Bounds - {{149.83999642729759, 593.81640625}, {100.78158942795733, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 364 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{149.11837474442461, 512.77555847167969}, {101.6304358635042, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 288 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Image::\ -OpenStack} - - - - Bounds - {{149.03844281801122, 441.00006103515625}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 287 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Image} - - - - Bounds - {{34.439999163150787, 593.81640625}, {100.78158942795733, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 283 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Connection} - - - - Bounds - {{34.173828125, 512.63998854160309}, {101.6304358635042, 43}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 279 - Magnets - - {-0.73960024176586958, -1.1094003915786743} - {-0.42163697454145826, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696309, -1.1094003915786743} - {1.1094003915786743, -0.73960025003154684} - {1.2649110555648804, -0.4216368426009538} - {1.3333333730697632, 1.5894572413799324e-07} - {1.2649110555648804, 0.42163714417925036} - {1.1094003915786743, 0.73960034921967122} - {0.73960021696883871, 1.1094003915786743} - {0.42163699339010208, 1.2649110555648804} - {0, 1.3333333730697632} - {-0.42163733062650949, 1.2649109363555908} - {-0.73960021696883849, 1.1094003915786743} - {-1.1094003915786741, 0.73960034921967122} - {-1.2649110555648801, 0.42163676720637966} - {-1.333333373069763, -6.3578289655197295e-07} - {-1.2649109363555906, -0.4216371798373757} - {-1.1094005107879636, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Compute::\ -OpenStack} - - - - Bounds - {{294.3040312786415, 223.04465866088867}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 276 - Line - - ID - 266 - Position - 0.37499326467514038 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50100999999999996 - g - 0.50100999999999996 - r - 0.50100999999999996 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 2} - - - - Bounds - {{157.11837474442461, 197.9001387883261}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 273 - Line - - ID - 270 - Offset - 2 - Position - 0.15934100547108523 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50100999999999996 - g - 0.50100999999999996 - r - 0.50100999999999996 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 3} - - - - Bounds - {{103.85809152883793, 217.790003720481}, {20.50001335144043, 19.000007629394531}} - Class - ShapedGraphic - FontInfo - - Color - - b - 1 - g - 1 - r - 1 - - - ID - 272 - Line - - ID - 271 - Position - 0.58284473419189453 - RotationType - 0 - - Shape - Circle - Style - - fill - - Color - - b - 0.50100999999999996 - g - 0.50100999999999996 - r - 0.50100999999999996 - - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf1 1} - - - - Class - LineGraphic - Head - - ID - 268 - - ID - 271 - Points - - {145.17097500847333, 193.45905766468556} - {144, 212.54466247558594} - {114, 212.54466247558594} - {114.44319850558085, 273.00001343536854} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 269 - Info - 15 - - - - Bounds - {{63.737945556640625, 273.5}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 268 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.77173899999999995 - g - 0.52176800000000001 - r - 0.47711300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -validate_options} - - - - Class - LineGraphic - Head - - ID - 264 - - ID - 267 - Points - - {390.16638102803455, 310.50002911765671} - {390.16562181729756, 332.49999058353137} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 265 - - - - Class - LineGraphic - Head - - ID - 265 - - ID - 266 - Points - - {275.12634198092138, 193.45999347706302} - {275, 232.54466247558594} - {372, 232.54466247558594} - {390, 232.54466247558594} - {390.09058221361596, 261.5000315645629} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 269 - Info - 11 - - - - Bounds - {{331.4131981938873, 262.00002911795445}, {117.508056640625, 48}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 265 - Style - - shadow - - Draws - NO - - stroke - - Color - - b - 0.77173899999999995 - g - 0.52176800000000001 - r - 0.47711300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -registry_endpoint\ -registry_user\ -registry_password} - - - - Bounds - {{331.41319274902344, 332.99999058246613}, {117.508056640625, 48}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 264 - Style - - fill - - Color - - b - 0.99244200000000005 - g - 0.91966099999999995 - r - 0.82805899999999999 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.77173899999999995 - g - 0.52176800000000001 - r - 0.47711300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Registry_Client} - - - - Class - LineGraphic - Head - - ID - 287 - - ID - 259 - Points - - {236.39453956627611, 380.44952427238013} - {207.13235080714259, 440.5505137492454} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 366 - - - - Class - LineGraphic - ID - 258 - Points - - {236.39453956627611, 380.44952427238013} - {123.22694730758667, 444} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 259 - - - - Bounds - {{34.173828125, 441.00006103515625}, {101.6304358635042, 29}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 256 - Style - - fill - - Color - - b - 0.80966499999999997 - g - 0.97339100000000001 - r - 0.99244200000000005 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.557168 - g - 0.784505 - r - 0.994565 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.61095299999999997 - g - 0.70655199999999996 - r - 1 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Fog::Compute} - - - - Class - LineGraphic - Head - - ID - 231 - - ID - 255 - Points - - {242.64560076118829, 233.04418933065318} - {243.68850963070031, 257} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - Tail - - ID - 249 - - - - Class - LineGraphic - Head - - ID - 249 - - ID - 270 - Points - - {165.36838142014483, 195} - {165.36838142014483, 222} - {216.18851964698857, 222.36031992994629} - - Style - - shadow - - Draws - NO - - stroke - - HeadArrow - FilledArrow - Legacy - - TailArrow - 0 - - - - - Bounds - {{216.68850708007812, 212.54466247558594}, {51, 20}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 249 - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -Extract } - VerticalPad - 3 - - Wrap - NO - - - Bounds - {{112.71999756991863, 174.95999608933926}, {194.8895574417021, 18}} - Class - ShapedGraphic - FitText - Vertical - Flow - Resize - ID - 269 - Magnets - - {-0.73960024176586936, -1.1094003915786743} - {-0.42163697454145743, -1.2649110555648804} - {1.9868216701487083e-08, -1.3333333730697632} - {0.4216371064819644, -1.2649110555648804} - {0.73960031615696409, -1.1094003915786743} - {1.1094003915786752, -0.73960025003154684} - {1.2649110555648815, -0.4216368426009538} - {1.3333333730697643, 1.5894572413799324e-07} - {1.2649110555648815, 0.42163714417925036} - {1.1094003915786752, 0.73960034921967122} - {0.73960021696883971, 1.1094003915786743} - {0.42163699339010319, 1.2649110555648804} - {1.1842379282265398e-15, 1.3333333730697632} - {-0.42163733062650866, 1.2649109363555908} - {-0.73960021696883826, 1.1094003915786743} - {-1.1094003915786739, 0.73960034921967122} - {-1.2649110555648797, 0.42163676720637966} - {-1.3333333730697625, -6.3578289655197295e-07} - {-1.2649109363555902, -0.4216371798373757} - {-1.1094005107879634, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.83761200000000002 - g - 0.97951600000000005 - r - 1 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.58806800000000004 - g - 0.69311599999999995 - r - 0.99797999999999998 - - Pattern - 2 - - - Text - - Align - 0 - VerticalPad - 3 - - - - Bounds - {{44.173828125, 155}, {50, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 14 - - ID - 233 - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0.0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs28 \cf0 initialize} - VerticalPad - 0.0 - - Wrap - NO - - - Bounds - {{179.5, 257}, {128.37701416015625, 35.31999346613884}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 14 - - ID - 231 - Magnets - - {-0.7396002417658698, -1.1094003915786743} - {-0.42163697454145854, -1.2649110555648804} - {1.9868215517249155e-08, -1.3333333730697632} - {0.42163710648196329, -1.2649110555648804} - {0.73960031615696264, -1.1094003915786743} - {1.1094003915786739, -0.73960025003154684} - {1.2649110555648797, -0.4216368426009538} - {1.3333333730697625, 1.5894572176951738e-07} - {1.2649110555648797, 0.42163714417924814} - {1.1094003915786739, 0.73960034921966922} - {0.73960021696883826, 1.1094003915786723} - {0.42163699339010208, 1.2649110555648782} - {0, 1.3333333730697607} - {-0.42163733062650977, 1.2649109363555886} - {-0.73960021696883871, 1.1094003915786723} - {-1.1094003915786743, 0.73960034921966922} - {-1.2649110555648804, 0.42163676720637744} - {-1.3333333730697632, -6.3578289655197295e-07} - {-1.2649109363555908, -0.4216371798373757} - {-1.1094005107879639, -0.73960016419084695} - - Style - - fill - - Color - - b - 0.99244200000000005 - g - 0.91966099999999995 - r - 0.82805899999999999 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.994565 - g - 0.699277 - r - 0.416525 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.77173899999999995 - g - 0.52176800000000001 - r - 0.47711300000000001 - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf340 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 - -\f0\fs24 \cf0 \expnd0\expndtw0\kerning0 -agent properties\ -openstack_properties} - - - - Bounds - {{23, 148.40000857412815}, {496.83998838067055, 247}} - Class - ShapedGraphic - ID - 4 - Style - - fill - - Color - - b - 0.95021500000000003 - g - 0.95021500000000003 - r - 0.95021500000000003 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913000000000001 - g - 0.48913000000000001 - r - 0.48913000000000001 - - CornerRadius - 9 - Pattern - 1 - - - - - Bounds - {{23, 419.03999063372612}, {496.83998838067055, 231.96001222729683}} - Class - ShapedGraphic - ID - 296 - Style - - fill - - Color - - b - 0.95021500000000003 - g - 0.95021500000000003 - r - 0.95021500000000003 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.48913000000000001 - g - 0.48913000000000001 - r - 0.48913000000000001 - - CornerRadius - 9 - Pattern - 1 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoLineLength - 0.20000000298023224 - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2016-03-09 14:24:06 +0000 - Modifier - Veli, Beyhan - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - Expanded_Canvases - - Frame - {{114, 72}, {1326, 805}} - ShowInfo - - ShowRuler - - Sidebar - - SidebarWidth - 200 - TopSlabHeight - 250 - VisibleRegion - {{-110, 86}, {797, 647}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.png b/src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.png deleted file mode 100644 index abe7fc82b9debc5c4f4676036fedc3a603351574..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99921 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ͶS(8F9#j`e|&q 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 U?VJ@)LP0*?#pS+Mi-H3FABoRZl>h($ diff --git a/src/bosh_openstack_cpi/lib/bosh_openstack_cpi.rb b/src/bosh_openstack_cpi/lib/bosh_openstack_cpi.rb deleted file mode 100644 index 37a2ad00a..000000000 --- a/src/bosh_openstack_cpi/lib/bosh_openstack_cpi.rb +++ /dev/null @@ -1 +0,0 @@ -require 'cloud/openstack' diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack.rb b/src/bosh_openstack_cpi/lib/cloud/openstack.rb deleted file mode 100644 index 269a553d2..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Bosh - module OpenStackCloud; end -end - -require 'fog/openstack' -require 'httpclient' -require 'json' -require 'tmpdir' -require 'securerandom' -require 'membrane' -require 'netaddr' - -require 'common/common' -require 'common/exec' -require 'common/thread_pool' -require 'common/thread_formatter' - -require 'bosh/cpi/registry_client' -require 'bosh/cpi/redactor' -require 'cloud' -require 'cloud/openstack/helpers' -require 'cloud/openstack/cloud' -require 'cloud/openstack/cpi_lambda' -require 'cloud/openstack/openstack' -require 'cloud/openstack/tag_manager' - -require 'cloud/openstack/network_configurator' -require 'cloud/openstack/loadbalancer_configurator' -require 'cloud/openstack/resource_pool' -require 'cloud/openstack/security_groups' -require 'cloud/openstack/floating_ip' -require 'cloud/openstack/network' -require 'cloud/openstack/private_network' -require 'cloud/openstack/dynamic_network' -require 'cloud/openstack/manual_network' -require 'cloud/openstack/vip_network' -require 'cloud/openstack/volume_configurator' -require 'cloud/openstack/response_message' -require 'cloud/openstack/request_message' -require 'cloud/openstack/excon_logging_instrumentor' -require 'cloud/openstack/availability_zone_provider' -require 'cloud/openstack/stemcell' -require 'cloud/openstack/stemcell_creator' -require 'cloud/openstack/instance_type_mapper' -require 'cloud/openstack/server' -require 'cloud/openstack/vm_factory' -require 'cloud/openstack/vm_creator' -require 'cloud/openstack/noop_registry' - -module Bosh - module Clouds - OpenStack = Bosh::OpenStackCloud::Cloud - Openstack = OpenStack # Alias needed for Bosh::Clouds::Provider.create method - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/availability_zone_provider.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/availability_zone_provider.rb deleted file mode 100644 index a954baa2c..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/availability_zone_provider.rb +++ /dev/null @@ -1,81 +0,0 @@ -module Bosh::OpenStackCloud - class AvailabilityZoneProvider - include Helpers - - def initialize(openstack, ignore_server_availability_zone) - @openstack = openstack - @ignore_server_availability_zone = ignore_server_availability_zone - end - - def use_multiple_azs?(cloud_properties) - single_az = cloud_properties.key?('availability_zone') - multiple_azs = cloud_properties.key?('availability_zones') - cloud_error('Invalid cloud_properties: only one property of "availability_zone" and "availability_zones" allowed.') if single_az && multiple_azs - cloud_error('Cannot use multiple azs without openstack.ignore_server_availability_zone') if use_server_availability_zone? && multiple_azs - return true if multiple_azs - false - end - - def select_azs(cloud_properties) - multiple_azs = cloud_properties['availability_zones'] - multiple_azs.shuffle - end - - def select(volume_ids, resource_pool_az) - return resource_pool_az if array_empty?(volume_ids) || @ignore_server_availability_zone - - volumes = volumes(volume_ids) - volume_azs = volumes.map(&:availability_zone) - if resource_pool_az - all_azs = volume_azs + [resource_pool_az] - same_availability_zone?(all_azs) ? resource_pool_az : fail_for_different_azs(resource_pool_az, volumes) - else - same_availability_zone?(volume_azs) ? first_volume_az(volume_azs) : fail_for_different_azs(resource_pool_az, volumes) - end - end - - def use_server_availability_zone? - !@ignore_server_availability_zone - end - - private - - def first_volume_az(volume_azs) - volume_azs.first.nil? || volume_azs.first.empty? ? nil : volume_azs.first - end - - def fail_for_different_azs(resource_pool_az, volumes) - cloud_error format("can't use multiple availability zones: %s, %s. " + - "Enable 'openstack.ignore_server_availability_zone' to allow VMs and disks to be in different AZs, or use the same AZ for both.", - resource_pool_az_description(resource_pool_az), disk_az_description(volumes)) - end - - def array_empty?(array) - array.nil? || array.empty? - end - - def same_availability_zone?(azs) - uniq_azs = azs.uniq - uniq_azs.size == 1 - end - - def volumes(volume_ids) - fog_volume_map = @openstack.with_openstack { @openstack.volume.volumes } - volume_ids.map { |vid| @openstack.with_openstack(retryable: true) { fog_volume_map.get(vid) } } - end - - def resource_pool_az_description(resource_pool_az) - if resource_pool_az - format("VM is created in AZ '%s'", resource_pool_az) - else - 'VM is created in default AZ' - end - end - - def disk_az_description(volumes) - volumes.map do |volume| - format("disk '%s' is in AZ '%s'", volume.id, volume.availability_zone) - end.join(', ') - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/cloud.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/cloud.rb deleted file mode 100644 index 0fed01bbb..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/cloud.rb +++ /dev/null @@ -1,873 +0,0 @@ -module Bosh::OpenStackCloud - ## - # BOSH OpenStack CPI - class Cloud < Bosh::Cloud - include Helpers - - OPTION_KEYS = %w[openstack registry agent use_dhcp].freeze - - BOSH_APP_DIR = '/var/vcap/bosh'.freeze - FIRST_DEVICE_NAME_LETTER = 'b'.freeze - REGISTRY_KEY_TAG = :registry_key - - attr_reader :state_timeout - attr_reader :openstack - attr_accessor :logger - - ## - # Creates a new BOSH OpenStack CPI - # - # @param [Hash] options CPI options - # @option options [Hash] openstack OpenStack specific options - # @option options [Hash] agent agent options - # @option options [Hash] registry agent options - # @option cpi_api_version CPI API Version as requested by BOSH director - def initialize(options, cpi_api_version) - super() - - @cpi_api_version = cpi_api_version || 1 - @options = normalize_options(options) - - validate_options - - @logger = Bosh::Clouds::Config.logger - - @agent_properties = @options.fetch('agent', {}) - openstack_properties = @options['openstack'] - @default_key_name = openstack_properties['default_key_name'] - @default_security_groups = openstack_properties['default_security_groups'] - @default_volume_type = openstack_properties['default_volume_type'] - @stemcell_public_visibility = openstack_properties['stemcell_public_visibility'] - @boot_from_volume = openstack_properties['boot_from_volume'] - @use_dhcp = openstack_properties['use_dhcp'] - @human_readable_vm_names = openstack_properties['human_readable_vm_names'] - @use_config_drive = !!openstack_properties.fetch('config_drive', false) - @config_drive = openstack_properties['config_drive'] - - @openstack = Bosh::OpenStackCloud::Openstack.new(@options['openstack']) - - @az_provider = Bosh::OpenStackCloud::AvailabilityZoneProvider.new( - @openstack, - openstack_properties['ignore_server_availability_zone'], - ) - - @metadata_lock = Mutex.new - - @instance_type_mapper = Bosh::OpenStackCloud::InstanceTypeMapper.new - end - - def compute - @openstack.compute - end - - def glance - @openstack.image - end - - def volume - @openstack.volume - end - - def auth_url - @openstack.auth_url - end - - def network - @openstack.network - end - - ## - # Creates a new OpenStack Image using stemcell image. It requires access - # to the OpenStack Glance service. - # - # @param [String] image_path Local filesystem path to a stemcell image - # @param [Hash] cloud_properties CPI-specific properties - # @option cloud_properties [String] name Stemcell name - # @option cloud_properties [String] version Stemcell version - # @option cloud_properties [String] infrastructure Stemcell infrastructure - # @option cloud_properties [String] disk_format Image disk format - # @option cloud_properties [String] container_format Image container format - # @option cloud_properties [optional, String] kernel_file Name of the - # kernel image file provided at the stemcell archive - # @option cloud_properties [optional, String] ramdisk_file Name of the - # ramdisk image file provided at the stemcell archive - # @return [String] OpenStack image UUID of the stemcell - def create_stemcell(image_path, cloud_properties) - with_thread_name("create_stemcell(#{image_path}...)") do - stemcell_creator = StemcellCreator.new(@logger, @openstack, cloud_properties) - stemcell = stemcell_creator.create(image_path, @stemcell_public_visibility) - stemcell.id - end - end - - ## - # Deletes a stemcell - # - # @param [String] stemcell_id OpenStack image UUID of the stemcell to be - # deleted - # @return [void] - def delete_stemcell(stemcell_id) - with_thread_name("delete_stemcell(#{stemcell_id})") do - @logger.info("Deleting stemcell `#{stemcell_id}'...") - - stemcell = Stemcell.create(@logger, @openstack, stemcell_id) - stemcell.delete - end - end - - ## - # Creates an OpenStack server and waits until it's in running state - # - # @param [String] agent_id UUID for the agent that will be used later on by - # the director to locate and talk to the agent - # @param [String] stemcell_id OpenStack image UUID that will be used to - # power on new server - # @param [Hash] cloud_properties cloud specific properties describing the - # resources needed for this VM - # @param [Hash] network_spec list of networks and their settings needed for - # this VM - # @param [optional, Array] disk_locality List of disks that might be - # attached to this server in the future, can be used as a placement - # hint (i.e. server will only be created if resource pool availability - # zone is the same as disk availability zone) - # @param [optional, Hash] environment Data to be merged into agent settings - # @return [String] OpenStack server UUID - def create_vm(agent_id, stemcell_id, cloud_properties, - network_spec = nil, disk_locality = nil, environment = nil) - with_thread_name("create_vm(#{agent_id}, ...)") do - @logger.info('Creating new server...') - @logger.debug("Using scheduler hints: `#{cloud_properties['scheduler_hints']}'") if cloud_properties['scheduler_hints'] - registry_key = "vm-#{generate_unique_name}" - create_vm_params = { - name: registry_key, - os_scheduler_hints: cloud_properties['scheduler_hints'], - config_drive: @use_config_drive, - } - server = Server.new(@agent_properties, @human_readable_vm_names, @logger, @openstack, registry, @use_dhcp) - - openstack_properties = OpenStruct.new( - boot_from_volume: @boot_from_volume, - default_key_name: @default_key_name, - default_security_groups: @default_security_groups, - ) - - vm_factory = VmFactory.new(@openstack, server, create_vm_params, disk_locality, @az_provider, openstack_properties) - network_configurator = NetworkConfigurator.new(network_spec, cloud_properties['allowed_address_pairs']) - instance_id = vm_factory.create_vm(network_configurator, agent_id, environment, stemcell_id, cloud_properties) - - if @cpi_api_version >= 2 - [instance_id, network_configurator.network_spec] - else - instance_id - end - end - end - - ## - # Terminates an OpenStack server and waits until it reports as terminated - # - # @param [String] server_id OpenStack server UUID - # @return [void] - def delete_vm(server_id) - with_thread_name("delete_vm(#{server_id})") do - @logger.info("Deleting server `#{server_id}'...") - server = openstack_server(server_id) - if server - server_tags = metadata_to_tags(server.metadata) - @logger.debug("Server tags: `#{server_tags}' found for server #{server_id}") - Server.new(@agent_properties, @human_readable_vm_names, @logger, @openstack, registry, @use_dhcp) - .destroy(server, server_tags) - else - @logger.info("Server `#{server_id}' not found. Skipping.") - end - end - end - - ## - # Checks if an OpenStack server exists - # - # @param [String] server_id OpenStack server UUID - # @return [Boolean] True if the vm exists - def has_vm?(server_id) - with_thread_name("has_vm?(#{server_id})") do - server = openstack_server(server_id) - !server.nil? && !%i[terminated deleted].include?(server.state.downcase.to_sym) - end - end - - ## - # Reboots an OpenStack Server - # - # @param [String] server_id OpenStack server UUID - # @return [void] - def reboot_vm(server_id) - with_thread_name("reboot_vm(#{server_id})") do - server = openstack_server(server_id) - cloud_error("Server `#{server_id}' not found") unless server - - soft_reboot(server) - end - end - - ## - # Configures networking on existing OpenStack server - # - # @param [String] server_id OpenStack server UUID - # @param [Hash] network_spec Raw network spec passed by director - # @return [void] - # @raise [Bosh::Clouds:NotSupported] If there's a network change that requires the recreation of the VM - def configure_networks(server_id, network_spec) - with_thread_name("configure_networks(#{server_id}, ...)") do - raise Bosh::Clouds::NotSupported, - format('network configuration change requires VM recreation: %s', network_spec) - end - end - - ## - # Creates a new OpenStack volume - # - # @param [Integer] size disk size in MiB - # @param [optional, String] server_id OpenStack server UUID of the VM that - # this disk will be attached to - # @return [String] OpenStack volume UUID - def create_disk(size, cloud_properties, server_id = nil) - volume_service_client = @openstack.volume - with_thread_name("create_disk(#{size}, #{cloud_properties}, #{server_id})") do - raise ArgumentError, 'Disk size needs to be an integer' unless size.is_a?(Integer) - cloud_error('Minimum disk size is 1 GiB') if size < 1024 - - unique_name = generate_unique_name - volume_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: mib_to_gib(size), - } - - if cloud_properties.key?('type') - volume_params[:volume_type] = cloud_properties['type'] - elsif !@default_volume_type.nil? - volume_params[:volume_type] = @default_volume_type - end - - if server_id && @az_provider.use_server_availability_zone? - server = openstack_server(server_id) - volume_params[:availability_zone] = server.availability_zone if server&.availability_zone - end - - @logger.info('Creating new volume...') - new_volume = @openstack.with_openstack { volume_service_client.volumes.create(volume_params) } - - @logger.info("Creating new volume `#{new_volume.id}'...") - @openstack.wait_resource(new_volume, :available) - - new_volume.id.to_s - end - end - - ## - # Check whether an OpenStack volume exists or not - # - # @param [String] disk_id OpenStack volume UUID - # @return [bool] whether the specific disk is there or not - def has_disk?(disk_id) - with_thread_name("has_disk?(#{disk_id})") do - @logger.info("Check the presence of disk with id `#{disk_id}'...") - volume = openstack_volume(disk_id) - - !volume.nil? - end - end - - ## - # Deletes an OpenStack volume - # - # @param [String] disk_id OpenStack volume UUID - # @return [void] - # @raise [Bosh::Clouds::CloudError] if disk is not in available state - def delete_disk(disk_id) - with_thread_name("delete_disk(#{disk_id})") do - @logger.info("Deleting volume `#{disk_id}'...") - volume = openstack_volume(disk_id) - if volume - state = volume.status - cloud_error("Cannot delete volume `#{disk_id}', state is #{state}") if state.to_sym != :available - - @openstack.with_openstack(retryable: true, ignore_not_found: true) { volume.destroy } - @openstack.wait_resource(volume, :deleted, :status, true) - else - @logger.info("Volume `#{disk_id}' not found. Skipping.") - end - end - end - - ## - # Attaches an OpenStack volume to an OpenStack server - # - # @param [String] server_id OpenStack server UUID - # @param [String] disk_id OpenStack volume UUID - # @return [void] - def attach_disk(server_id, disk_id) - with_thread_name("attach_disk(#{server_id}, #{disk_id})") do - server = openstack_server(server_id) - cloud_error("Server `#{server_id}' not found") unless server - - volume = openstack_volume(disk_id) - cloud_error("Volume `#{disk_id}' not found") unless volume - - device_name = attach_volume(server, volume) - - update_agent_settings(server) do |settings| - settings['disks'] ||= {} - settings['disks']['persistent'] ||= {} - settings['disks']['persistent'][disk_id] = device_name - end - - device_name if @cpi_api_version >= 2 - end - end - - ## - # Detaches an OpenStack volume from an OpenStack server - # - # @param [String] server_id OpenStack server UUID - # @param [String] disk_id OpenStack volume UUID - # @return [void] - def detach_disk(server_id, disk_id) - with_thread_name("detach_disk(#{server_id}, #{disk_id})") do - server = openstack_server(server_id) - cloud_error("Server `#{server_id}' not found") unless server - - volume = openstack_volume(disk_id) - if volume.nil? - @logger.info("Disk `#{disk_id}' not found while trying to detach it from vm `#{server_id}'...") - else - detach_volume(server, volume) - end - - update_agent_settings(server) do |settings| - settings['disks'] ||= {} - settings['disks']['persistent'] ||= {} - settings['disks']['persistent'].delete(disk_id) - end - end - end - - ## - # Takes a snapshot of an OpenStack volume - # - # @param [String] disk_id OpenStack volume UUID - # @param [Hash] metadata Metadata key/value pairs to add to snapshot - # @return [String] OpenStack snapshot UUID - # @raise [Bosh::Clouds::CloudError] if volume is not found - def snapshot_disk(disk_id, metadata) - with_thread_name("snapshot_disk(#{disk_id})") do - metadata = Hash[metadata.map { |key, value| [key.to_s, value] }] - volume = openstack_volume(disk_id) - cloud_error("Volume `#{disk_id}' not found") unless volume - - devices = [] - volume.attachments.each { |attachment| devices << attachment['device'] unless attachment.empty? } - - description = %w[deployment job index].collect { |key| metadata[key] } - description << devices.first.split('/').last unless devices.empty? - name = "snapshot-#{generate_unique_name}" - snapshot_params = { - display_name: name, - display_description: description.join('/'), - name:, - description: description.join('/'), - volume_id: volume.id, - force: true, - } - - @logger.info("Creating new snapshot for volume `#{disk_id}'...") - snapshot = @openstack.volume.snapshots.new(snapshot_params) - @openstack.with_openstack { - snapshot.save - } - - @logger.info("Creating new snapshot `#{snapshot.id}' for volume `#{disk_id}'...") - @openstack.wait_resource(snapshot, :available) - - metadata.merge!( - 'director' => metadata['director_name'], - 'instance_index' => metadata['index'].to_s, - 'instance_name' => metadata['job'] + '/' + metadata['instance_id'], - ) - - metadata.delete('director_name') - metadata.delete('index') - metadata.delete('job') - - @logger.info("Creating metadata for snapshot `#{snapshot.id}'...") - @openstack.with_openstack { - TagManager.tag_snapshot(snapshot, metadata) - } - - snapshot.id.to_s - end - end - - ## - # Deletes an OpenStack volume snapshot - # - # @param [String] snapshot_id OpenStack snapshot UUID - # @return [void] - # @raise [Bosh::Clouds::CloudError] if snapshot is not in available state - def delete_snapshot(snapshot_id) - with_thread_name("delete_snapshot(#{snapshot_id})") do - @logger.info("Deleting snapshot `#{snapshot_id}'...") - volume = @openstack.with_openstack { @openstack.volume } - snapshot = @openstack.with_openstack(retryable: true) { volume.snapshots.get(snapshot_id) } - if snapshot - state = snapshot.status - cloud_error("Cannot delete snapshot `#{snapshot_id}', state is #{state}") if state.to_sym != :available - - @openstack.with_openstack(retryable: true, ignore_not_found: true) { snapshot.destroy } - @openstack.wait_resource(snapshot, :deleted, :status, true) - else - @logger.info("Snapshot `#{snapshot_id}' not found. Skipping.") - end - end - end - - ## - # Set metadata for an OpenStack server - # - # @param [String] server_id OpenStack server UUID - # @param [Hash] metadata Metadata key/value pairs - # @return [void] - def set_vm_metadata(server_id, metadata) - with_thread_name("set_vm_metadata(#{server_id}, ...)") do - server = openstack_server(server_id) - cloud_error("Server `#{server_id}' not found") unless server - @openstack.with_openstack { TagManager.tag_server(server, metadata) } - - apply_human_readable_name(metadata, server_id) if human_readable_name?(server, server_id) - end - end - - ## - # Set metadata for an OpenStack disk - # - # @param [String] disk_id OpenStack disk UUID - # @param [Hash] metadata Metadata key/value pairs - # @return [void] - def set_disk_metadata(disk_id, metadata) - with_thread_name("set_disk_metadata(#{disk_id}, ...)") do - volume = openstack_volume(disk_id) - cloud_error("Disk `#{disk_id}' not found") unless volume - @openstack.with_openstack { TagManager.tag_volume(@openstack.volume, disk_id, metadata) } - end - end - - # Map a set of cloud agnostic VM properties (cpu, ram, ephemeral_disk_size) to - # a set of OpenStack specific cloud_properties - # @param [Hash] requirements requested cpu, ram, and ephemeral_disk_size - # @return [Hash] OpenStack specific cloud_properties describing instance (e.g. instance_type) - def calculate_vm_cloud_properties(requirements) - required_keys = %w[cpu ram ephemeral_disk_size] - missing_keys = required_keys.reject { |key| requirements[key] } - unless missing_keys.empty? - missing_keys.map! { |k| "'#{k}'" } - raise "Missing VM cloud properties: #{missing_keys.join(', ')}" - end - - @instance_type_mapper.map( - requirements:, - flavors: compute.flavors, - boot_from_volume: @boot_from_volume, - ) - end - - ## - # Updates the agent settings - # - # @param [Fog::OpenStack::Compute::Server] server OpenStack server - def update_agent_settings(server) - raise ArgumentError, 'Block is not provided' unless block_given? - registry_key = registry_key_for(server) - unless cpi_without_registry? - @logger.info("Updating settings for server '#{server.id}' with registry key '#{registry_key}'...") - end - settings = registry.read_settings(registry_key) - yield settings - registry.update_settings(registry_key, settings) - end - - # Information about Openstack CPI, currently supported stemcell formats - # @return [Hash] Openstack CPI properties - def info - { 'api_version' => 2, 'stemcell_formats' => ['openstack-raw', 'openstack-qcow2', 'openstack-light'] } - end - - ## - # Resizes an existing OpenStack volume - # - # @param [String] disk_id volume Cloud ID - # @param [Integer] new_size disk size in MiB - def resize_disk(disk_id, new_size) - new_size_gib = mib_to_gib(new_size) - - with_thread_name("resize_disk(#{disk_id}, #{new_size_gib})") do - @logger.info("Resizing volume `#{disk_id}'...") - volume = openstack_volume(disk_id) - cloud_error("Cannot resize volume because volume with #{disk_id} not found") unless volume - actual_size_gib = volume.size - if actual_size_gib == new_size_gib - @logger.info("Skipping resize of disk #{disk_id} because current value #{actual_size_gib} GiB" \ - " is equal new value #{new_size_gib} GiB") - elsif actual_size_gib > new_size_gib - not_supported_error("Cannot resize volume to a smaller size from #{actual_size_gib} GiB to #{new_size_gib} GiB") - else - attachments = volume.attachments - unless attachments.empty? - cloud_error("Cannot resize volume '#{disk_id}' it still has #{attachments.size} attachment(s)") - end - @openstack.with_openstack { volume.extend(new_size_gib) } - @logger.info("Resizing #{disk_id} from #{actual_size_gib} GiB to #{new_size_gib} GiB") - @openstack.wait_resource(volume, :available) - @logger.info("Disk #{disk_id} resized from #{actual_size_gib} GiB to #{new_size_gib} GiB") - end - end - - nil - end - - def registry - return @registry_instance if @registry_instance - - if cpi_without_registry? - @registry_instance = Bosh::OpenStackCloud::NoopRegistry.new - else - begin - registry_properties = @options.fetch('registry') - registry_endpoint = registry_properties.fetch('endpoint') - registry_user = registry_properties.fetch('user') - registry_password = registry_properties.fetch('password') - rescue KeyError => e - raise ArgumentError, "Invalid CPI properties. Used CPI API version is #{@cpi_api_version} and stemcell API "\ - "version is #{stemcell_api_version}. Since at least one API version is 1, the registry has to be configured. "\ - "Error: #{e.message}" - end - @registry_instance = Bosh::Cpi::RegistryClient.new(registry_endpoint, registry_user, registry_password) - end - end - - private - - def apply_human_readable_name(metadata, server_id) - name = metadata['name'] - job = metadata['job'] - index = metadata['index'] - compiling = metadata['compiling'] - if name - @logger.debug("Rename VM with id '#{server_id}' to '#{name}'") - update_servername(server_id, name.to_s) - elsif job && index - @logger.debug("Rename VM with id '#{server_id}' to '#{job}/#{index}'") - update_servername(server_id, "#{job}/#{index}") - elsif compiling - @logger.debug("Rename VM with id '#{server_id}' to 'compiling/#{compiling}'") - update_servername(server_id, "compiling/#{compiling}") - end - end - - def update_servername(server_id, name) - @openstack.with_openstack { @openstack.compute.update_server(server_id, 'name' => name) } - end - - def human_readable_name?(server, server_id) - return true if (@human_readable_vm_names && cpi_without_registry?) || - @openstack.with_openstack(retryable: true) { server.metadata.get(REGISTRY_KEY_TAG) } - - @logger.debug("VM with id '#{server_id}' has no 'registry_key' tag") - false - end - - def cpi_without_registry? - @cpi_api_version >= 2 && stemcell_api_version >= 2 - end - - def openstack_volume(disk_id) - volumes = @openstack.with_openstack { @openstack.volume.volumes } - @openstack.with_openstack(retryable: true) { volumes.get(disk_id) } - end - - def openstack_server(server_id) - servers = @openstack.with_openstack { @openstack.compute.servers } - @openstack.with_openstack(retryable: true) { servers.get(server_id) } - end - - def mib_to_gib(size) - (size / 1024.0).ceil - end - - def registry_key_for(server) - registry_key_metadatum = @openstack.with_openstack(retryable: true) { server.metadata.get(REGISTRY_KEY_TAG) } - registry_key_metadatum ? registry_key_metadatum.value : server.name - end - - ## - # Generates an unique name - # - # @return [String] Unique name - def generate_unique_name - SecureRandom.uuid - end - - ## - # Soft reboots an OpenStack server - # - # @param [Fog::OpenStack::Compute::Server] server OpenStack server - # @return [void] - def soft_reboot(server) - @logger.info("Soft rebooting server `#{server.id}'...") - @openstack.with_openstack { server.reboot } - @openstack.wait_resource(server, :active, :state) - end - - ## - # Hard reboots an OpenStack server - # - # @param [Fog::OpenStack::Compute::Server] server OpenStack server - # @return [void] - def hard_reboot(server) - @logger.info("Hard rebooting server `#{server.id}'...") - @openstack.with_openstack { server.reboot('HARD') } - @openstack.wait_resource(server, :active, :state) - end - - ## - # Attaches an OpenStack volume to an OpenStack server - # - # @param [Fog::OpenStack::Compute::Server] server OpenStack server - # @param [Fog::OpenStack::Compute::Volume] volume OpenStack volume - # @return [String] Device name - def attach_volume(server, volume) - @logger.info("Attaching volume `#{volume.id}' to server `#{server.id}'...") - volume_attachments = @openstack.with_openstack(retryable: true) { server.volume_attachments } - device = volume_attachments.find { |a| a['volumeId'] == volume.id } - - if device.nil? - device_name = select_device_name(volume_attachments, first_device_name_letter(server)) - cloud_error('Server has too many disks attached') if device_name.nil? - - @logger.info("Attaching volume `#{volume.id}' to server `#{server.id}', device name is `#{device_name}'") - @openstack.with_openstack { server.attach_volume(volume.id, device_name) } - @openstack.wait_resource(volume, :'in-use') - else - device_name = device['device'] - @logger.info("Volume `#{volume.id}' is already attached to server `#{server.id}' in `#{device_name}'. Skipping.") - end - - device_name - end - - ## - # Select the first available device name - # - # @param [Array] volume_attachments Volume attachments - # @param [String] first_device_name_letter First available letter for device names - # @return [String] First available device name or nil is none is available - def select_device_name(volume_attachments, first_device_name_letter) - (first_device_name_letter..'z').each do |char| - # Some kernels remap device names (from sd* to vd* or xvd*). - device_names = ["/dev/sd#{char}", "/dev/vd#{char}", "/dev/xvd#{char}"] - # Bosh Agent will lookup for the proper device name if we set it initially to sd*. - return "/dev/sd#{char}" if volume_attachments.select { |v| device_names.include?(v['device']) }.empty? - @logger.warn("`/dev/sd#{char}' is already taken") - end - - nil - end - - ## - # Returns the first letter to be used on device names - # - # @param [Fog::OpenStack::Compute::Server] server OpenStack server - # @return [String] First available letter - def first_device_name_letter(server) - letter = FIRST_DEVICE_NAME_LETTER.dup - return letter if server.flavor.nil? - return letter unless server.flavor.key?('id') - flavors = @openstack.compute.flavors - flavor = @openstack.with_openstack(retryable: true) { flavors.find { |f| f.id == server.flavor['id'] } } - return letter if flavor.nil? - - letter.succ! if flavor_has_ephemeral_disk?(flavor) - letter.succ! if flavor_has_swap_disk?(flavor) - letter.succ! if @config_drive == 'disk' - letter - end - - ## - # Detaches an OpenStack volume from an OpenStack server - # - # @param [Fog::OpenStack::Compute::Server] server OpenStack server - # @param [Fog::OpenStack::Compute::Volume] volume OpenStack volume - # @return [void] - def detach_volume(server, volume) - @logger.info("Detaching volume `#{volume.id}' from `#{server.id}'...") - volume_attachments = @openstack.with_openstack(retryable: true) { server.volume_attachments } - attachment = volume_attachments.find { |a| a['volumeId'] == volume.id } - if attachment - @openstack.with_openstack { server.detach_volume(volume.id) } - @openstack.wait_resource(volume, :available) - else - @logger.info("Disk `#{volume.id}' is not attached to server `#{server.id}'. Skipping.") - end - end - - ## - # Checks if the OpenStack flavor has ephemeral disk - # - # @param [Fog::OpenStack::Compute::Flavor] OpenStack flavor - # @return [Boolean] true if flavor has ephemeral disk, false otherwise - def flavor_has_ephemeral_disk?(flavor) - flavor.ephemeral && flavor.ephemeral.to_i > 0 - end - - ## - # Checks if the OpenStack flavor has swap disk - # - # @param [Fog::OpenStack::Compute::Flavor] OpenStack flavor - # @return [Boolean] true if flavor has swap disk, false otherwise - def flavor_has_swap_disk?(flavor) - flavor.swap.nil? || flavor.swap.to_i <= 0 ? false : true - end - - ## - # Checks if options passed to CPI are valid and can actually - # be used to create all required data structures etc. - # - # @return [void] - # @raise [ArgumentError] if options are not valid - def validate_options - raise ArgumentError, "Invalid OpenStack cloud properties: No 'openstack' properties specified." unless @options['openstack'] - - auth_url = @options['openstack']['auth_url'] - user_domain_name = @options['openstack']['user_domain_name'] - project_domain_name = @options['openstack']['project_domain_name'] - project_id = @options['openstack']['project_id'] - username = @options['openstack']['username'] - api_key = @options['openstack']['api_key'] - application_credential_id = @options['openstack']['application_credential_id'] - application_credential_secret = @options['openstack']['application_credential_secret'] - validate_cloud_properties(username, api_key, application_credential_id, application_credential_secret) - - schema = Membrane::SchemaParser.parse do - openstack_options_schema = { - 'openstack' => { - 'auth_url' => String, - optional('region') => String, - optional('endpoint_type') => String, - optional('state_timeout') => Numeric, - optional('stemcell_public_visibility') => bool, - optional('connection_options') => Hash, - optional('boot_from_volume') => bool, - optional('default_key_name') => String, - optional('default_security_groups') => [String], - optional('default_volume_type') => String, - optional('wait_resource_poll_interval') => Integer, - optional('config_drive') => enum('disk', 'cdrom'), - optional('human_readable_vm_names') => bool, - optional('use_dhcp') => bool, - optional('use_nova_networking') => bool, - optional('vm') => Hash, - }, - optional('registry') => { - 'endpoint' => String, - 'user' => String, - 'password' => String, - }, - optional('agent') => Hash, - } - - if username - openstack_options_schema['openstack']['username'] = String - openstack_options_schema['openstack']['api_key'] = String - else - openstack_options_schema['openstack']['application_credential_id'] = String - openstack_options_schema['openstack']['application_credential_secret'] = String - end - - if Bosh::OpenStackCloud::Openstack.is_v3(auth_url) - if project_id - openstack_options_schema['openstack']['project_id'] = String - else - openstack_options_schema['openstack']['project'] = String - end - if user_domain_name || project_domain_name - openstack_options_schema['openstack']['user_domain_name'] = String - openstack_options_schema['openstack']['project_domain_name'] = String - else - openstack_options_schema['openstack']['domain'] = String - end - else - openstack_options_schema['openstack']['tenant'] = String - openstack_options_schema['openstack'][optional('domain')] = String - end - openstack_options_schema - end - schema.validate(@options) - rescue Membrane::SchemaValidationError => e - raise ArgumentError, "Invalid OpenStack cloud properties: #{e.inspect}" - end - - def validate_cloud_properties(username, api_key, application_credential_id, application_credential_secret) - return if valid_credentials?(username, api_key, application_credential_id, application_credential_secret) - raise ArgumentError, 'Invalid OpenStack cloud properties: username and api_key or application_credential_id and application_credential_secret is required' - end - - def valid_credentials?(username, api_key, application_credential_id, application_credential_secret) - valid_user_credentials?(username, api_key, application_credential_id, application_credential_secret) || - valid_app_credentials?(username, api_key, application_credential_id, application_credential_secret) - end - - def valid_user_credentials?(username, api_key, application_credential_id, application_credential_secret) - username && api_key && !application_credential_id && !application_credential_secret - end - - def valid_app_credentials?(username, api_key, application_credential_id, application_credential_secret) - !username && !api_key && application_credential_id && application_credential_secret - end - - def stemcell_api_version - @options.dig('openstack', 'vm', 'stemcell', 'api_version') || 1 - end - - def normalize_options(options) - raise ArgumentError, "Invalid OpenStack cloud properties: Hash expected, received #{options}" unless options.is_a?(Hash) - # we only care about two top-level fields - options = hash_filter(options.dup) { |key| OPTION_KEYS.include?(key) } - # nil values should be treated the same as missing keys (makes validating optional fields easier) - delete_entries_with_nil_keys(options) - end - - def hash_filter(hash) - copy = {} - hash.each do |key, value| - copy[key] = value if yield(key) - end - copy - end - - def delete_entries_with_nil_keys(options) - options.each do |key, value| - if value.nil? - options.delete(key) - elsif value.is_a?(Hash) - options[key] = delete_entries_with_nil_keys(value.dup) - end - end - options - end - - def metadata_to_tags(fog_metadata) - fog_metadata.map { |metadatum| [metadatum.key, metadatum.value] }.to_h - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/cpi_lambda.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/cpi_lambda.rb deleted file mode 100644 index 1b34328c6..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/cpi_lambda.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Bosh::OpenStackCloud - class CpiLambda - def self.create(cpi_config, cpi_log, ca_cert_from_config, ca_cert_from_context) - lambda do |context, api_version| - unless cpi_config.key?('cloud') && cpi_config['cloud'].key?('properties') - raise 'Could not find cloud properties in the configuration' - end - - cloud_properties = cpi_config['cloud']['properties'] - cloud_properties['cpi_log'] = cpi_log - - # If 'ca_cert' is set in job config we render non-empty `config/openstack.crt` (excon needs it as a file) - connection_options = cloud_properties['openstack']['connection_options'] - connection_options['ssl_ca_file'] = ca_cert_from_config if connection_options&.delete('ca_cert') - - # allow openstack config to be overwritten dynamically by context - cloud_properties['openstack'].merge!(context) - - # write ca cert to disk if given in context - connection_options = cloud_properties['openstack']['connection_options'] - if connection_options && (ca_cert = connection_options.delete('ca_cert')) - File.write(ca_cert_from_context, ca_cert) - connection_options['ssl_ca_file'] = ca_cert_from_context - end - - request_id = context['request_id'] - Bosh::Clouds::Config.logger.set_request_id(request_id) if request_id - - Bosh::Clouds::Openstack.new(cloud_properties, api_version) - end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/dynamic_network.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/dynamic_network.rb deleted file mode 100644 index 951368a2e..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/dynamic_network.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Bosh::OpenStackCloud - ## - # Represents OpenStack dynamic network: where IaaS sets VM's IP - class DynamicNetwork < PrivateNetwork - def prepare(_openstack, _security_group_ids) - cloud_error("Network with id '#{net_id}' is a dynamic network. VRRP is not supported for dynamic networks") if @allowed_address_pairs - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/excon_logging_instrumentor.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/excon_logging_instrumentor.rb deleted file mode 100644 index 5362eb331..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/excon_logging_instrumentor.rb +++ /dev/null @@ -1,64 +0,0 @@ -module Bosh::OpenStackCloud - class ExconLoggingInstrumentor - REDACTED = ''.freeze - - class << self - def instrument(name, params = {}) - redacted_params = redact(params) - logger = Bosh::Clouds::Config.logger - - if name == 'excon.response' - message = ResponseMessage.new(redacted_params).format - logger.debug("#{name} #{message}") - - elsif name == 'excon.request' - message = RequestMessage.new(redacted_params).format - logger.debug("#{name} #{message}") - - elsif name == 'excon.error' && redacted_params[:error].respond_to?(:response) - message = ResponseMessage.new(redacted_params[:error].response.data).format - logger.debug("#{name} #{message}") - - else - logger.debug("#{name} #{redacted_params}") - end - - yield if block_given? - end - - def redact(params) - redacted_params = params.dup - redact_body(redacted_params, 'auth.passwordCredentials.password') - redact_body(redacted_params, 'server.user_data') - redact_body(redacted_params, 'auth.identity.password.user.password') - redact_body(redacted_params, 'auth.identity.application_credential.secret') - redact_headers(redacted_params, 'X-Auth-Token') - redacted_params - end - - private - - def redact_body(params, json_path) - return unless params.key?(:body) && params[:body].is_a?(String) - return unless params.key?(:headers) && params[:headers]['Content-Type'] == 'application/json' - - begin - json_content = JSON.parse(params[:body]) - rescue JSON::ParserError - return - end - json_content = Bosh::Cpi::Redactor.redact!(json_content, json_path) - params[:body] = JSON.dump(json_content) - end - - def redact_headers(params, property) - return unless params.key?(:headers) - return unless params[:headers].key?(property) - - headers = params[:headers] = params[:headers].dup - - headers.store(property, REDACTED) - end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/floating_ip.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/floating_ip.rb deleted file mode 100644 index c9959c540..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/floating_ip.rb +++ /dev/null @@ -1,66 +0,0 @@ -module Bosh::OpenStackCloud - class FloatingIp - include Helpers - - def self.reassociate(openstack, ip, server, network_id) - if openstack.use_nova_networking? - nova_reassociate(openstack, ip, server) - else - floating_ip = get_floating_ip(openstack, ip) - - if port_attached?(floating_ip) - old_port = openstack.with_openstack(retryable: true) { openstack.network.get_port(floating_ip['port_id']).body['port'] } - old_server = openstack.with_openstack(retryable: true) { openstack.compute.get_server_details(old_port['device_id']).body['server'] } - disassociate(openstack, floating_ip, old_server['name'], old_server['id']) - end - - port_id = get_port_id(openstack, server.id, network_id) - Bosh::Clouds::Config.logger.info("Associating floating IP '#{ip}' with server '#{server.name} (#{server.id})'") - openstack.network.associate_floating_ip(floating_ip['id'], port_id) - end - end - - - def self.port_attached?(floating_ip) - return false if floating_ip['port_id'].nil? || floating_ip['port_id'].empty? - - true - end - - def self.disassociate(openstack, floating_ip, server_name, server_id) - Bosh::Clouds::Config.logger.info("Disassociating floating IP '#{floating_ip['floating_ip_address']}' from server '#{server_name} (#{server_id})'") - openstack.network.disassociate_floating_ip(floating_ip['id']) - end - - def self.get_port_id(openstack, server_id, network_id) - port = openstack.with_openstack(retryable: true) { openstack.network.ports.all(device_id: server_id, network_id:).first } - cloud_error("Server has no port in network '#{network_id}'") unless port - port.id - end - - def self.get_floating_ip(openstack, ip) - floating_ips = openstack.with_openstack(retryable: true) { openstack.network.list_floating_ips('floating_ip_address' => ip).body['floatingips'] } - if floating_ips.length > 1 - cloud_error("Floating IP '#{ip}' found in multiple networks: #{floating_ips.map { |floating_ip| "'#{floating_ip['floating_network_id']}'" }.join(', ')}") - elsif floating_ips.empty? - cloud_error("Floating IP '#{ip}' not allocated") - end - floating_ips.first - end - - def self.nova_reassociate(openstack, ip, server) - address = openstack.with_openstack(retryable: true) { openstack.compute.addresses.find { |a| a.ip == ip } } - if address - unless address.instance_id.nil? - Bosh::Clouds::Config.logger.info("Disassociating floating IP '#{ip}' from server '#{address.instance_id}'") - address.server = nil - end - - Bosh::Clouds::Config.logger.info("Associating floating IP '#{ip}' with server '#{server.id}'") - address.server = server - else - cloud_error("Floating IP #{ip} not allocated") - end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/helpers.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/helpers.rb deleted file mode 100644 index 56a8ef074..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/helpers.rb +++ /dev/null @@ -1,72 +0,0 @@ -module Bosh::OpenStackCloud - module Helpers - def self.included(base) - base.extend(Helpers) - end - - ## - # Raises CloudError exception - # - # @param [String] message Message about what went wrong - # @param [Exception] exception Exception to be logged (optional) - def cloud_error(message, exception = nil) - raise_error(Bosh::Clouds::CloudError, exception, message) - end - - def not_supported_error(message, exception = nil) - raise_error(Bosh::Clouds::NotSupported, exception, message) - end - - def fail_on_error(*error_wrappers) - error_wrappers = error_wrappers.compact - return if error_wrappers.empty? - - error_wrappers.each do |error_wrapper| - @logger.error(error_wrapper.error) - @logger.error(error_wrapper.error.backtrace) - end - - message = error_wrappers.map(&:message).join("\n") - prefix = error_wrappers.size > 1 ? "Multiple cloud errors occurred:\n" : '' - - raise Bosh::Clouds::CloudError, prefix + message - end - - def catch_error(prefix = nil) - return nil unless block_given? - begin - yield - rescue StandardError => e - return ErrorWrapper.new(e, prefix) - end - - nil - end - - private - - class ErrorWrapper - attr_reader :error, :prefix - - def initialize(error, prefix=nil) - @error = error - @prefix = prefix - end - - def message - if prefix - "#{prefix}: #{error.message}" - else - error.message - end - end - end - - - def raise_error(error_class, exception, message) - @logger&.error(message) - @logger.error(exception) if @logger && exception - raise error_class, message - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/instance_type_mapper.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/instance_type_mapper.rb deleted file mode 100644 index 0368d138a..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/instance_type_mapper.rb +++ /dev/null @@ -1,88 +0,0 @@ -module Bosh::OpenStackCloud - class InstanceTypeMapper - OS_OVERHEAD_IN_GB = 3 - NO_DISK = 0 - - def map(requirements:, flavors:, boot_from_volume: false) - normalized_requirements = convert_disk_to_GB(requirements) - - possible_flavors = find_possible_flavors(normalized_requirements, flavors, boot_from_volume) - if possible_flavors.empty? - raise ["Unable to meet requested VM requirements: #{requirements['cpu']} CPU, #{requirements['ram']} MB RAM, #{requirements['ephemeral_disk_size'] / 1024.0} GB Disk.\n", - "Available flavors:\n", - flavors.map { |flavor| "#{flavor.name}: #{flavor.vcpus} CPU, #{flavor.ram} MB RAM, #{flavor.disk} GB Disk\n" }].join - end - - closest_match = possible_flavors.min_by do |flavor| - [flavor.vcpus, flavor.ram, flavor.disk + flavor.ephemeral, flavor.disk] - end - - vm_cloud_properties(normalized_requirements, closest_match, boot_from_volume) - end - - private - - def find_possible_flavors(requirements, flavors, boot_from_volume) - valid_flavors = flavors.select do |flavor| - flavor.vcpus >= requirements['cpu'] && - flavor.ram >= requirements['ram'] && - flavor.disabled != true - end - - if boot_from_volume - boot_from_volume_flavors(valid_flavors) - else - boot_default_flavors(requirements, valid_flavors) - end - end - - def boot_from_volume_flavors(valid_flavors) - valid_flavors.select { |flavor| flavor.ephemeral == NO_DISK } - end - - def boot_default_flavors(requirements, valid_flavors) - flavors_meeting_ephemeral_requirement = valid_flavors.select do |flavor| - flavor.ephemeral >= requirements['ephemeral_disk_size'].ceil && - flavor.disk >= OS_OVERHEAD_IN_GB - end - return flavors_meeting_ephemeral_requirement unless flavors_meeting_ephemeral_requirement.empty? - - flavors_meeting_root_requirement = valid_flavors.select do |flavor| - flavor.ephemeral == NO_DISK && - flavor.disk >= requirements['ephemeral_disk_size'].ceil + OS_OVERHEAD_IN_GB - end - - flavors_meeting_root_requirement - end - - def convert_disk_to_GB(requirements) - normalized = requirements.clone - normalized['ephemeral_disk_size'] = requirements['ephemeral_disk_size'] / 1024.0 - normalized - end - - def vm_cloud_properties(requirements, closest_match, boot_from_volume) - properties = { - 'instance_type' => closest_match.name, - } - - if boot_from_volume && root_disk_size(closest_match) < required_boot_volume_size(requirements) - properties.merge( - 'root_disk' => { - 'size' => required_boot_volume_size(requirements), - }, - ) - else - properties - end - end - - def required_boot_volume_size(requirements) - OS_OVERHEAD_IN_GB + requirements['ephemeral_disk_size'] - end - - def root_disk_size(flavor) - flavor.disk - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/loadbalancer_configurator.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/loadbalancer_configurator.rb deleted file mode 100644 index 2dfa99c36..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/loadbalancer_configurator.rb +++ /dev/null @@ -1,224 +0,0 @@ -module Bosh::OpenStackCloud - class LoadbalancerConfigurator - def initialize(openstack, logger) - @openstack = openstack - @logger = logger - end - - def create_pool_memberships(server, network_spec, pools) - pools - .map { |pool| add_vm_to_pool(server, network_spec, pool) } - .each_with_index - .map do |membership, index| - ["lbaas_pool_#{index + 1}", "#{membership.pool_id}/#{membership.membership_id}"] - end - .to_h - end - - def add_vm_to_pool(server, network_spec, pool_spec) - validate_configuration(pool_spec) - openstack_pool_id = openstack_pool_id(pool_spec['name']) - ip = NetworkConfigurator.gateway_ip(network_spec, @openstack, server) - subnet_id = matching_subnet_id(network_spec, ip) - membership_id = create_membership(openstack_pool_id, ip, pool_spec['port'], subnet_id) - LoadbalancerPoolMembership.new(pool_spec['name'], pool_spec['port'], openstack_pool_id, membership_id) - rescue Bosh::Clouds::VMCreationFailed => e - message = "VM with id '#{server.id}' cannot be attached to load balancer pool '#{pool_spec['name']}'. Reason: #{e.message}" - raise Bosh::Clouds::VMCreationFailed.new(false), message - end - - def create_membership(pool_id, ip, port, subnet_id) - @openstack.with_openstack do - membership_id = nil - begin - @logger.debug("Creating load balancer pool membership with pool id '#{pool_id}', ip '#{ip}', and port '#{port}'.") - membership_id = retry_on_conflict_pending_update(pool_id) { - @openstack.network.create_lbaas_pool_member(pool_id, ip, port, subnet_id:).body['member']['id'] - } - rescue Excon::Error::Conflict - lbaas_pool_members = @openstack.with_openstack(retryable: true) { @openstack.network.list_lbaas_pool_members(pool_id) } - membership_id = - lbaas_pool_members - .body.fetch('members', []) - .detect( - raise_if_not_found(pool_id, ip, port), - &matching_member(ip, port, subnet_id) - ) - .fetch('id') - - @logger.info("Load balancer pool membership with pool id '#{pool_id}', ip '#{ip}', and port '#{port}' already exists. The membership has the id '#{membership_id}'.") - rescue LoadBalancerResource::NotFound, LoadBalancerResource::NotSupportedConfiguration => e - raise Bosh::Clouds::VMCreationFailed.new(false), e.message - rescue StandardError => e - raise Bosh::Clouds::CloudError, "Creating load balancer pool membership with pool_id '#{pool_id}' and membership_id '#{membership_id}' failed. Reason: #{e.class} #{e.message}" - end - membership_id - end - end - - def raise_if_not_found(pool_id, ip, port) - -> { raise Bosh::Clouds::CloudError, "Load balancer pool membership with pool id '#{pool_id}', ip '#{ip}', and port '#{port}' supposedly exists, but cannot be found." } - end - - def matching_member(ip, port, subnet_id) - ->(member) { member['address'] == ip && member['protocol_port'] == port && member['subnet_id'] == subnet_id } - end - - def cleanup_memberships(server_metadata) - server_metadata - .select { |key, _| key.start_with?('lbaas_pool_') } - .map { |_, value| value.split('/') } - .each { |pool_id, membership_id| remove_vm_from_pool(pool_id, membership_id) } - end - - def remove_vm_from_pool(pool_id, membership_id) - @openstack.with_openstack(retryable: true) do - begin - @logger.debug("Deleting load balancer pool membership with pool id '#{pool_id}' and membership id '#{membership_id}'.") - retry_on_conflict_pending_update(pool_id) { - @openstack.network.delete_lbaas_pool_member(pool_id, membership_id) - } - rescue Fog::OpenStack::Network::NotFound - @logger.debug("Skipping deletion of load balancer pool membership. Member with pool_id '#{pool_id}' and membership_id '#{membership_id}' does not exist.") - rescue LoadBalancerResource::NotFound => e - @logger.debug("Skipping deletion of load balancer pool membership because load balancer resource cannot be found. #{e.message}") - rescue StandardError => e - raise Bosh::Clouds::CloudError, "Deleting load balancer pool membership with pool_id '#{pool_id}' and membership_id '#{membership_id}' failed. Reason: #{e.class} #{e.message}" - end - end - end - - def retry_on_conflict_pending_update(pool_id) - action_result = nil - start_time = Time.now - attempts = 0 - - loadbalancer_id = loadbalancer_id(pool_id) - resource = LoadBalancerResource.new(loadbalancer_id, @openstack) - - loop do - @openstack.wait_resource(resource, :active, :provisioning_status) - attempts += 1 - action_result = yield - break - rescue Excon::Error::Conflict => e - neutron_error = @openstack.parse_openstack_response(e.response, 'NeutronError') - if neutron_error&.fetch('message', '')&.include? 'PENDING_UPDATE' - @logger.debug("Changing load balancer resource failed with '#{e.message}', unsuccessful attempts: '#{attempts}'") - if Time.now - start_time >= @openstack.state_timeout - @openstack.cloud_error("Failed after #{Time.now - start_time}s with #{attempts} attempts with '#{e.message}'") - end - else - raise e - end - end - @openstack.wait_resource(resource, :active, :provisioning_status) - - action_result - end - - def loadbalancer_id(pool_id) - pool_response = @openstack.with_openstack(retryable: true) do - begin - @openstack.network.get_lbaas_pool(pool_id) - rescue Fog::OpenStack::Network::NotFound => e - raise LoadBalancerResource::NotFound, "Load balancer ID could not be determined because pool with ID '#{pool_id}' was not found. Reason: #{e.message}" - end - end - loadbalancers = pool_response.body['pool']['loadbalancers'] || - retrieve_loadbalancers_via_listener( - pool_response.body['pool']['listeners'], - pool_id, - ) - extract_loadbalancer_id(loadbalancers, pool_id) - end - - private - - def retrieve_loadbalancers_via_listener(listeners, pool_id) - if listeners.empty? - raise LoadBalancerResource::NotFound, "No listeners associated with load balancer pool '#{pool_id}'" - elsif listeners.size > 1 - raise LoadBalancerResource::NotSupportedConfiguration, "More than one listener is associated with load balancer pool '#{pool_id}'. It is not possible to verify the status of the load balancer responsible for the pool membership." - end - - listener_response = @openstack.with_openstack(retryable: true) { @openstack.network.get_lbaas_listener(listeners[0]['id']) } - listener_response.body['listener']['loadbalancers'] - end - - def extract_loadbalancer_id(loadbalancers, pool_id) - if loadbalancers.empty? - raise LoadBalancerResource::NotFound, "No load balancers associated with load balancer pool '#{pool_id}'" - elsif loadbalancers.size > 1 - raise LoadBalancerResource::NotSupportedConfiguration, "More than one load balancer is associated with load balancer pool '#{pool_id}'. It is not possible to verify the status of the load balancer responsible for the pool membership." - end - - loadbalancers[0]['id'] - end - - def matching_subnet_id(network_spec, ip) - subnet_ids = NetworkConfigurator.matching_gateway_subnet_ids_for_ip(network_spec, @openstack, ip) - if subnet_ids.size > 1 - raise Bosh::Clouds::VMCreationFailed.new(false), "In network '#{NetworkConfigurator.get_gateway_network_id(network_spec)}' more than one subnet CIDRs match the IP '#{ip}'" - end - if subnet_ids.empty? - raise Bosh::Clouds::VMCreationFailed.new(false), "Network '#{NetworkConfigurator.get_gateway_network_id(network_spec)}' does not contain any subnet to match the IP '#{ip}'" - end - subnet_ids.first - end - - def validate_configuration(pool_spec) - raise Bosh::Clouds::VMCreationFailed.new(false), 'Load balancer pool defined without a name' unless pool_spec['name'] - - unless pool_spec['port'] - raise Bosh::Clouds::VMCreationFailed.new(false), "Load balancer pool '#{pool_spec['name']}' has no port definition" - end - end - - class LoadbalancerPoolMembership - attr_reader :name, :port, :membership_id, :pool_id - - def initialize(name, port, pool_id, membership_id) - @name = name - @port = port - @pool_id = pool_id - @membership_id = membership_id - end - end - - def openstack_pool_id(pool_name) - pools = @openstack.with_openstack(retryable: true) { - @openstack.network.list_lbaas_pools('name' => pool_name).body['pools'] - } - - if pools.empty? - raise Bosh::Clouds::VMCreationFailed.new(false), "Load balancer pool '#{pool_name}' does not exist" - elsif pools.size > 1 - raise Bosh::Clouds::VMCreationFailed.new(false), "Load balancer pool '#{pool_name}' exists multiple times. Make sure to use unique naming." - end - pools.first['id'] - end - - class LoadBalancerResource - attr_reader :id - - def initialize(loadbalancer_id, openstack) - @id = loadbalancer_id - @openstack = openstack - end - - def reload - true - end - - def provisioning_status - @openstack.with_openstack(retryable: true) do - @openstack.network.get_lbaas_loadbalancer(@id).body['loadbalancer']['provisioning_status'] - end - end - - class NotFound < StandardError; end - class NotSupportedConfiguration < StandardError; end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/manual_network.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/manual_network.rb deleted file mode 100644 index 76373a180..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/manual_network.rb +++ /dev/null @@ -1,98 +0,0 @@ -module Bosh::OpenStackCloud - ## - # Represents OpenStack manual network: where user sets VM's IP - class ManualNetwork < PrivateNetwork - ## - # Creates a new manual network - # - # @param [String] name Network name - # @param [Hash] spec Raw network spec - def initialize(name, spec) - super - @vrrp_port_check = @spec - .fetch('cloud_properties', {}) - .fetch('vrrp_port_check', true) - end - - ## - # Returns the private IP address - # - # @return [String] ip address - def private_ip - @ip - end - - ## - # Returns the vrrp port check - # true: port check enabled - # false: port check disabled - def vrrp_port_check_enabled? - @vrrp_port_check - end - - def prepare(openstack, security_group_ids) - if openstack.use_nova_networking? - @nic['v4_fixed_ip'] = @ip - else - @logger.debug("Creating port for IP #{@ip} in network #{net_id}") - port = create_port_for_manual_network(openstack, net_id, security_group_ids) - @logger.debug("Port with ID #{port.id} and MAC address #{port.mac_address} created") - @nic['port_id'] = port.id - @spec['mac'] = port.mac_address - end - end - - def cleanup(openstack) - unless openstack.use_nova_networking? - ports = openstack.with_openstack { openstack.network.ports } - port = openstack.with_openstack(retryable: true) { ports.get(@nic['port_id']) } - openstack.with_openstack(retryable: true, ignore_not_found: true) { port&.destroy } - end - end - - private - - def create_port_for_manual_network(openstack, net_id, security_group_ids) - port_properties = { - network_id: net_id, - fixed_ips: [{ ip_address: @ip }], - security_groups: security_group_ids, - } - if @allowed_address_pairs - @logger.debug("VRRP port check for #{@allowed_address_pairs} is #{vrrp_port_check_enabled?}") - cloud_error("Configured VRRP port with ip '#{@allowed_address_pairs}' does not exist.") unless vrrp_port?(openstack) - port_properties[:allowed_address_pairs] = [{ ip_address: @allowed_address_pairs }] - end - openstack.with_openstack do - retried = false - begin - openstack.network.ports.create(port_properties) - rescue Excon::Error::Conflict => e - raise e if retried - delete_conflicting_unused_ports(openstack, net_id) - @logger.info("Retrying to create port for IP #{@ip} in network #{net_id}") - retried = true - retry - end - end - end - - def delete_conflicting_unused_ports(openstack, net_id) - ports = openstack.with_openstack(retryable: true) do - openstack.network.ports.all(fixed_ips: ["ip_address=#{@ip}", "network_id=#{net_id}"]) - end - detached_port_ids = ports.select { |p| p.status == 'DOWN' && p.device_id.empty? && p.device_owner.empty? }.map(&:id) - @logger.warn("IP #{@ip} already allocated: Deleting conflicting unused ports with ids=#{detached_port_ids}") - NetworkConfigurator.cleanup_ports(openstack, detached_port_ids) - end - - def vrrp_port?(openstack) - return true unless vrrp_port_check_enabled? - - vrrp_port = openstack.with_openstack(retryable: true) do - openstack.network.ports.all(fixed_ips: "ip_address=#{@allowed_address_pairs}") - end - !(vrrp_port.nil? || vrrp_port.empty?) - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/network.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/network.rb deleted file mode 100644 index f6ad9c5d7..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/network.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Bosh::OpenStackCloud - ## - # Represents OpenStack network. - class Network - include Helpers - - attr_reader :name, :spec - - ## - # Creates a new network - # - # @param [String] name Network name - # @param [Hash] spec Raw network spec - def initialize(name, spec) - raise ArgumentError, "Invalid spec, Hash expected, #{spec.class} provided" unless spec.is_a?(Hash) - - @logger = Bosh::Clouds::Config.logger - @spec = spec - @name = name - @ip = spec['ip'] - @cloud_properties = spec['cloud_properties'] - end - - ## - # Configures given server - # - # @param [Bosh::OpenStackCloud::Openstack] openstack - # @param [Fog::OpenStack::Compute::Server] server OpenStack server to configure - def configure(openstack, server); end - - def prepare(openstack, security_groups); end - - def cleanup(openstack); end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/network_configurator.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/network_configurator.rb deleted file mode 100644 index ddddeb76b..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/network_configurator.rb +++ /dev/null @@ -1,263 +0,0 @@ -module Bosh::OpenStackCloud - ## - # Represents OpenStack server network config. OpenStack server has single NIC - # with a dynamic or manual IP's address and (optionally) a single floating - # IP address which server itself is not aware of (vip). Thus we should - # perform a number of sanity checks for the network spec provided by director - # to make sure we don't apply something OpenStack doesn't understand how to - # deal with. - class NetworkConfigurator - include Helpers - - attr_reader :network_spec, :networks, :picked_security_groups - - ## - # Creates new network spec - # - # @param [Hash] spec Raw network spec passed by director - def initialize(spec, allowed_address_pairs = nil) - raise ArgumentError, "Invalid spec, Hash expected, #{spec.class} provided" unless spec.is_a?(Hash) - - @network_spec = spec - @logger = Bosh::Clouds::Config.logger - @networks = [] - @vip_network = nil - @security_groups = [] - @picked_security_groups = [] - @net_ids = [] - @dynamic_network = nil - - spec.each_pair do |name, network_spec| - initialize_network(name, network_spec) - end - - cloud_error('At least one dynamic or manual network should be defined') if @networks.empty? - add_vrrp_ip_to_default_network(allowed_address_pairs) if allowed_address_pairs - end - - def check_preconditions(use_nova_networking, config_drive, use_dhcp) - return unless multiple_private_networks? - - if use_nova_networking - error_message = "Multiple manual networks can only be used with 'openstack.use_nova_networking=false'. Multiple networks require Neutron." - raise Bosh::Clouds::VMCreationFailed.new(false), error_message - end - - if use_dhcp || !config_drive - error_message = "Multiple manual networks can only be used with 'openstack.use_dhcp=false' and 'openstack.config_drive=cdrom|disk'" - raise Bosh::Clouds::VMCreationFailed.new(false), error_message - end - end - - def pick_groups(openstack, default_security_groups, resource_pool_groups) - @picked_security_groups = SecurityGroups.new(openstack).select_and_retrieve( - default_security_groups, - security_groups, - resource_pool_groups, - ) - @logger.debug("Using security groups: `#{@picked_security_groups.map(&:name).join(', ')}'") - end - - def prepare(openstack) - security_group_ids = picked_security_groups.map(&:id) - @networks.each do |network| - network.prepare(openstack, security_group_ids) - end - end - - def cleanup(openstack) - @networks.each do |network| - network.cleanup(openstack) - end - end - - ## - # Setup network configuration for one network spec. - # - # @param [String] name - # @param [Hash] network_spec - def initialize_network(name, network_spec) - network_type = NetworkConfigurator.network_type(network_spec) - - case network_type - when 'dynamic' - cloud_error('Only one dynamic network per instance should be defined') if @dynamic_network - net_id = NetworkConfigurator.extract_net_id(network_spec) - cloud_error("Dynamic network with id #{net_id} is already defined") if @net_ids.include?(net_id) - network = DynamicNetwork.new(name, network_spec) - @security_groups += extract_security_groups(network_spec) - @networks << network - @net_ids << net_id - @dynamic_network = network - when 'manual' - net_id = NetworkConfigurator.extract_net_id(network_spec) - cloud_error('Manual network must have net_id') if net_id.nil? - cloud_error("Manual network with id #{net_id} is already defined") if @net_ids.include?(net_id) - network = ManualNetwork.new(name, network_spec) - @security_groups += extract_security_groups(network_spec) - @networks << network - @net_ids << net_id - when 'vip' - cloud_error('Only one VIP network per instance should be defined') if @vip_network - @vip_network = VipNetwork.new(name, network_spec) - @security_groups += extract_security_groups(network_spec) - else - cloud_error("Invalid network type `#{network_type}': OpenStack " \ - "CPI can only handle `dynamic', 'manual' or `vip' " \ - 'network types') - end - - @security_groups.uniq! - end - - def self.get_gateway_network(network_spec) - private_network_specs = private_network_specs(network_spec) - spec = if private_network_specs.size == 1 - private_network_specs.first - else - private_network_specs.select do |spec| - spec['default']&.include?('gateway') - end.first - end - spec - end - - def self.get_gateway_network_id(network_spec) - network = get_gateway_network(network_spec) - extract_net_id(network) - end - - def self.matching_gateway_subnet_ids_for_ip(network_spec, openstack, ip) - network_id = get_gateway_network_id(network_spec) - network_subnets = openstack.with_openstack(retryable: true) { openstack.network.list_subnets('network_id' => network_id).body['subnets'] } - network_subnets.select do |subnet| - NetAddr.parse_net(subnet['cidr']).contains(NetAddr.parse_ip(ip)) - end.map { |subnet| subnet['id'] } - end - - def self.gateway_ip(network_spec, openstack, server) - network = get_gateway_network(network_spec) - network_type = network_type(network) - - if network_type == 'manual' - network['ip'] - elsif network_type == 'dynamic' - if private_network_specs(network_spec).size > 1 - raise Bosh::Clouds::VMCreationFailed.new(false), 'Gateway IP address could not be determined. Gateway network is dynamic, but additional private networks exist.' - end - - openstack.with_openstack(retryable: true) { - return server.addresses.values.first.dig(0, 'addr') - } - end - end - - def self.network_type(network) - # in case of a manual network bosh doesn't provide a type. - network.fetch('type', 'manual') - end - - def self.private_network_specs(network_spec) - network_spec.values.reject { |spec| spec['type'] == 'vip' } - end - - ## - # Extracts the network ID from the network configuration - # - # @param [Hash] network_spec Network specification - # @return [Hash] network ID - def self.extract_net_id(network_spec) - if network_spec && network_spec['cloud_properties'] - cloud_properties = network_spec['cloud_properties'] - return cloud_properties['net_id'] if cloud_properties&.key?('net_id') - end - nil - end - - ## - # Applies network configuration to the vm - # - # @param [Bosh::OpenStackCloud::Openstack] openstack - # @param [Fog::OpenStack::Compute::Server] server OpenStack server to - # configure - def configure(openstack, server) - @networks.each do |network| - network.configure(openstack, server) - end - - @vip_network&.configure(openstack, server, NetworkConfigurator.get_gateway_network_id(@network_spec)) - end - - ## - # Returns the sorted security groups for this network configuration - # - # @return [Array] security groups - def security_groups - @security_groups.sort - end - - ## - # Returns the nics for this network configuration - # - # @return [Array] nics - def nics - @networks.each_with_object([]) do |network, memo| - nic = network.nic - memo << nic if nic.any? - end - end - - def self.port_ids(openstack, server_id) - return [] if openstack.use_nova_networking? - ports = openstack.with_openstack(retryable: true) do - openstack.network.ports.all(device_id: server_id) - end - ports.map(&:id) - end - - def self.cleanup_ports(openstack, port_ids) - return if openstack.use_nova_networking? - port_ids.each do |port_id| - port = openstack.with_openstack(retryable: true) { openstack.network.ports.get(port_id) } - if port - Bosh::Clouds::Config.logger.debug("Deleting port #{port_id}") - openstack.with_openstack(retryable: true, ignore_not_found: true) { port.destroy } - end - end - end - - private - - def add_vrrp_ip_to_default_network(allowed_address_pairs) - @networks.each do |network| - network.allowed_address_pairs = allowed_address_pairs if default_network?(network) - end - end - - def default_network?(network) - default_network_spec = NetworkConfigurator.get_gateway_network(@network_spec) - network.spec == default_network_spec - end - - def multiple_private_networks? - @networks.length > 1 - end - - ## - # Extracts the security groups from the network configuration - # - # @param [Hash] network_spec Network specification - # @return [Array] security groups - # @raise [ArgumentError] if the security groups in the network_spec is not an Array - def extract_security_groups(network_spec) - if network_spec && network_spec['cloud_properties'] - cloud_properties = network_spec['cloud_properties'] - if cloud_properties&.key?('security_groups') - raise ArgumentError, 'security groups must be an Array' unless cloud_properties['security_groups'].is_a?(Array) - return cloud_properties['security_groups'] - end - end - [] - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/noop_registry.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/noop_registry.rb deleted file mode 100644 index 579d16572..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/noop_registry.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Bosh::OpenStackCloud - class NoopRegistry - include Helpers - - def initialize(endpoint = nil, user = nil, password = nil) end - - def update_settings(instance_id, settings) end - - def read_settings(_instance_id) - {} - end - - def delete_settings(instance_id) end - - def endpoint - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/openstack.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/openstack.rb deleted file mode 100644 index 1ce64bff6..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/openstack.rb +++ /dev/null @@ -1,341 +0,0 @@ -require 'common/common' - -module Bosh::OpenStackCloud - class Openstack - include Helpers - - DEFAULT_STATE_TIMEOUT = 300 # Default timeout for target state (in seconds) - MAX_RETRIES = 10 # Max number of retries - DEFAULT_RETRY_TIMEOUT = 3 # Default timeout before retrying a call (in seconds) - - attr_reader :auth_url, :params, :is_v3, :state_timeout - - def self.is_v3(auth_url) - auth_url.match(/\/v3(?=\/|$)/) || !auth_url.match(/\/v2.0(?=\/|$)/) - end - - def initialize(options, retry_options_overwrites = {}, extra_connection_options = { 'instrumentor' => Bosh::OpenStackCloud::ExconLoggingInstrumentor }) - @logger = Bosh::Clouds::Config.logger - @is_v3 = Openstack.is_v3(options['auth_url']) - - @auth_url = build_auth_url(options['auth_url']) - - @use_nova_networking = options.fetch('use_nova_networking', false) - if @use_nova_networking - @logger.debug("Property 'use_nova_networking' is set to true. Using Nova networking APIs instead of Neutron APIs.") - end - - options['connection_options'] ||= {} - - @extra_connection_options = extra_connection_options - @state_timeout = options['state_timeout'] || DEFAULT_STATE_TIMEOUT - @wait_resource_poll_interval = options['wait_resource_poll_interval'] - - @params = openstack_params(options) - @retry_options = { - sleep: 1, - tries: 5, - on: [Excon::Error::GatewayTimeout, Excon::Error::Socket], - }.merge(retry_options_overwrites) - end - - def with_openstack(retryable: false, ignore_not_found: false) - retries = 0 - retryable_errors = [Excon::Error::Timeout, - Excon::Error::Socket, - Excon::Error::ServiceUnavailable, - Excon::Error::InternalServerError] - begin - yield - rescue Excon::Error::RequestEntityTooLarge => e - overlimit = parse_openstack_response(e.response, 'overLimit', 'overLimitFault') - - raise_openstack_error(e, format_error(e, e.response.body)) unless overlimit - raise_openstack_error(e, format_error(e, overlimit['message'])) unless retries < MAX_RETRIES - - wait_time = overlimit['retryAfter'] || e.response.headers['Retry-After'] || DEFAULT_RETRY_TIMEOUT - overlimit_msg = "Over Limit: #{overlimit['message']} - #{overlimit['details']}" - log_retry_error(e, retries, overlimit_msg, wait_time) - sleep(wait_time.to_i) - retries += 1 - retry - rescue Fog::Errors::NotFound => e - raise_openstack_error(e) unless ignore_not_found - @logger.debug("Suppressed 'not found' error message: #{e.message}") - rescue Excon::Error::BadRequest, Excon::Error::Conflict, Excon::Error::Forbidden => e - raise_openstack_error(e, format_error_body(e)) - rescue *retryable_errors => e - raise_openstack_error(e) unless retries < MAX_RETRIES && - (dns_problem?(e) || (connectivity_problem?(e) && retryable) || server_problem?(e)) - - retries += 1 - log_retry_error(e, retries) - sleep(DEFAULT_RETRY_TIMEOUT) - retry - end - end - - def project_name - if is_v3 - # Check if our connection is done through the openstack_project_name - if params.key?(:openstack_project_name) - params[:openstack_project_name] - else - # Then it means that we are using the openstack_project_id - connection = compute - connection.current_tenant['name'] - end - else - # Return the tenant name - params[:openstack_tenant] - end - end - - def use_nova_networking? - @use_nova_networking - end - - def compute - unless @compute - begin - Bosh::Common.retryable(@retry_options) do |tries, error| - @logger.error("Failed #{tries} times, last failure due to: #{error.inspect}") unless error.nil? - @compute = Fog::OpenStack::Compute.new(params) - end - rescue Excon::Error::Socket => e - cloud_error(socket_error_msg + e.message.to_s) - rescue Bosh::Common::RetryCountExceeded, Excon::Error::Client, Excon::Error::Server => e - cloud_error("Unable to connect to the OpenStack Compute Service API: #{e.message}. Check task debug log for details.") - end - end - @compute - end - - def image - unless @glance - begin - Bosh::Common.retryable(@retry_options) do |tries, error| - @logger.error("Failed #{tries} times, last failure due to: #{error.inspect}") unless error.nil? - @glance = Fog::OpenStack::Image.new(params) - end - rescue Excon::Error::Socket => e - cloud_error(socket_error_msg + e.message.to_s) - rescue Bosh::Common::RetryCountExceeded, Excon::Error::Client, Excon::Error::Server => e - cloud_error("Unable to connect to the OpenStack Image Service API: #{e.message}. Check task debug log for details.") - end - end - @glance - end - - ## - # Creates a client for the OpenStack volume service, or return - # the existing connection - # - # - def volume - unless @volume - begin - Bosh::Common.retryable(@retry_options) do |tries, error| - @logger.error("Failed #{tries} times, last failure due to: #{error.inspect}") unless error.nil? - @volume = Fog::OpenStack::Volume.new(params) - end - rescue Excon::Error::Socket => e - cloud_error(socket_error_msg + e.message.to_s) - rescue Bosh::Common::RetryCountExceeded, Excon::Error::Client, Excon::Error::Server => e - cloud_error("Unable to connect to the OpenStack Volume Service API: #{e.message}. Check task debug log for details.") - end - end - - @volume - end - - def network - unless @network - begin - Bosh::Common.retryable(@retry_options) do |tries, error| - @logger.error("Failed #{tries} times, last failure due to: #{error.inspect}") unless error.nil? - @network = Fog::OpenStack::Network.new(params) - end - rescue Excon::Error::Socket => e - cloud_error(socket_error_msg + e.message.to_s) - rescue Bosh::Common::RetryCountExceeded, Excon::Error::Client, Excon::Error::Server, Fog::Errors::NotFound => e - cloud_error("Unable to connect to the OpenStack Network Service API: #{e.message}. Check task debug log for details.") - end - end - - @network - end - - ## - # Waits for a resource to be on a target state - # - # @param [Fog::Model] resource Resource to query - # @param [Array] target_state Resource's state desired - # @param [Symbol] state_method Resource's method to fetch state - # @param [Boolean] ignore_not_found true if resource could be not found - def wait_resource(resource, target_state, state_method = :status, ignore_not_found = false) - started_at = Time.now - desc = resource.class.name.split('::').last.to_s + ' `' + resource.id.to_s + "'" - target_state = Array(target_state) - - loop do - duration = Time.now - started_at - - cloud_error("Timed out waiting for #{desc} to be #{target_state.join(', ')}") if duration > @state_timeout - - @logger.debug("Waiting for #{desc} to be #{target_state.join(', ')} (#{duration}s)") - - # If resource reload is nil, perhaps it's because resource went away - # (ie: a destroy operation). Don't raise an exception if this is - # expected (ignore_not_found). - if with_openstack(retryable: true) { resource.reload.nil? } - break if ignore_not_found - cloud_error("#{desc}: Resource not found") - else - state = with_openstack { resource.send(state_method).downcase.to_sym } - end - - # This is not a very strong convention, but some resources - # have 'error', 'failed' and 'killed' states, we probably don't want to keep - # waiting if we're in these states. Alternatively we could introduce a - # set of 'loop breaker' states but that doesn't seem very helpful - # at the moment - if state == :error || state == :failed || state == :killed - cloud_error("#{desc} state is #{state}, expected #{target_state.join(', ')}#{openstack_fault_message(resource)}") - end - - break if target_state.include?(state) - - sleep(@wait_resource_poll_interval) - end - - total = Time.now - started_at - @logger.info("#{desc} is now #{target_state.join(', ')}, took #{total}s") - end - - ## - # Parses and look ups for keys in an OpenStack response - # - # @param [Excon::Response] response Response from OpenStack API - # @param [Array] keys Keys to look up in response - # @return [Hash] Contents at the first key found, or nil if not found - def parse_openstack_response(response, *keys) - json_body = parse_openstack_response_body(response.body) - key = keys.detect { |k| json_body.key?(k) } if json_body && !json_body.empty? - json_body[key] if key - end - - private - - def dns_problem?(e) - e.is_a?(Excon::Error::Socket) && e.message =~ /getaddrinfo/ - end - - def connectivity_problem?(e) - e.is_a?(Excon::Error::Timeout) || e.is_a?(Excon::Error::Socket) - end - - def server_problem?(e) - e.is_a?(Excon::Error::ServiceUnavailable) || e.is_a?(Excon::Error::InternalServerError) - end - - def format_error_body(e) - body = parse_openstack_response_body(e.response.body) - msg = determine_message(body) - format_error(e, msg) - end - - def format_error(e, msg = nil) - error_name = e.class.name.split('::').last - [error_name, e.message, msg].uniq.reject(&:nil?).reject(&:empty?).join(' ') - end - - def raise_openstack_error(e, msg = nil) - msg ||= format_error(e) - error_message = "OpenStack API #{msg}.\nCheck task debug log for details." - cloud_error(error_message, e) - end - - def log_retry_error(e, retry_index, msg = nil, wait_time = DEFAULT_RETRY_TIMEOUT) - msg ||= format_error(e) - retry_msg = "OpenStack API #{msg}, retrying (#{retry_index}/#{MAX_RETRIES}) after #{wait_time} seconds." - @logger.info(retry_msg) - end - - def openstack_params(options) - { - openstack_auth_url: auth_url, - openstack_username: options['username'], - openstack_api_key: options['api_key'], - openstack_application_credential_id: options['application_credential_id'], - openstack_application_credential_secret: options['application_credential_secret'], - openstack_tenant: options['tenant'], - openstack_project_name: options['project'], - openstack_project_id: options['project_id'], - openstack_domain_name: options['domain'], - openstack_user_domain_name: options['user_domain_name'], - openstack_project_domain_name: options['project_domain_name'], - openstack_region: options['region'], - openstack_endpoint_type: options['endpoint_type'], - connection_options: options['connection_options'].merge(@extra_connection_options), - } - end - - def socket_error_msg - "Unable to connect to the OpenStack Keystone API #{auth_url}\n" - end - - def build_auth_url(url) - remove_url_trailing_slash(remove_url_path_from_version(url)) - end - - def remove_url_path_from_version(url) - if auth_url_with_version?(url) - version_regex = is_v3 ? /\/v3(?=\/|$)/ : /\/v2.0(?=\/|$)/ - url.slice(0..(url.index(version_regex))) - else - url - end - end - - def auth_url_with_version?(url) - url.match(/\/v2.0(?=\/|$)/) || url.match(/\/v3(?=\/|$)/) - end - - def remove_url_trailing_slash(url) - if url.end_with?('/') - url[0..-2] - else - url - end - end - - def openstack_fault_message(resource) - openstack_message = '' - if resource.respond_to?(:fault) && (fault = resource.fault) - openstack_message = "\n#{fault['message']}" if fault['message'] - openstack_message += fault['details'] if fault['details'] - end - openstack_message - end - - def determine_message(body) - hash_with_msg_property = proc { |_k, v| (v.is_a? Hash) && v['message'] } - - body ||= {} - _, value = body.find(&hash_with_msg_property) - value ? "(#{value['message']})" : '' - end - - def parse_openstack_response_body(body) - unless body.empty? - begin - return JSON.parse(body) - rescue JSON::ParserError - # do nothing - end - end - nil - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/private_network.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/private_network.rb deleted file mode 100644 index 5c3ad0d95..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/private_network.rb +++ /dev/null @@ -1,18 +0,0 @@ - -module Bosh::OpenStackCloud - class PrivateNetwork < Network - attr_reader :nic - attr_accessor :allowed_address_pairs - - def initialize(name, spec) - super - @nic = {} - @nic['net_id'] = net_id if net_id - end - - def net_id - @spec.fetch('cloud_properties', {}) - .fetch('net_id', nil) - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/request_message.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/request_message.rb deleted file mode 100644 index 1a24203db..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/request_message.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Bosh::OpenStackCloud - class RequestMessage - def initialize(params) - @params = params - end - - def format - params = @params.dup - params.delete(:ciphers) - params.delete(:connection) - params.delete(:__construction_args) - params.delete(:instrumentor) - params.delete(:middlewares) - params.delete(:uri_parser) - params.delete(:stack) - hostname = params.delete(:hostname) - method = params.delete(:method) - protocol = params.delete(:scheme) - port = params.delete(:port) - path = params.delete(:path) - headers = params.delete(:headers) - body = params.delete(:body) || 'null' - "#{method} #{protocol}://#{hostname}:#{port}#{path} params: #{params.to_json} headers: #{headers.to_json} body: #{body}" - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/resource_pool.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/resource_pool.rb deleted file mode 100644 index ba60178f5..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/resource_pool.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Bosh::OpenStackCloud - class ResourcePool - def self.security_groups(resource_pool_spec) - if resource_pool_spec&.key?('security_groups') - raise ArgumentError, 'security groups must be an Array' unless resource_pool_spec['security_groups'].is_a?(Array) - return resource_pool_spec['security_groups'] - end - - [] - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/response_message.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/response_message.rb deleted file mode 100644 index 620b01653..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/response_message.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Bosh::OpenStackCloud - class ResponseMessage - def initialize(params) - @params = params.dup - end - - def format - params = @params.dup - body = params.delete(:body) - path = params.delete(:path) - headers = params.delete(:headers) - status_line = params.delete(:status_line)&.chomp - params.delete(:cookies) - params.delete(:reason_phrase) - params.delete(:status) - "#{status_line} #{path} params: #{params.to_json} headers: #{headers.to_json} body: #{body}" - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/security_groups.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/security_groups.rb deleted file mode 100644 index 4aeef8f1d..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/security_groups.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Bosh::OpenStackCloud - class SecurityGroups - include Helpers - - def initialize(openstack) - @openstack = openstack - end - - def select_and_retrieve(default_security_groups, network_spec_security_groups, resource_pool_spec_security_groups) - picked_security_groups = pick_security_groups( - default_security_groups, - network_spec_security_groups, - resource_pool_spec_security_groups, - ) - - openstack_security_groups = @openstack.with_openstack { - retrieve_security_groups - } - map_to_security_groups_in_openstack(picked_security_groups, openstack_security_groups) - end - - private - - def retrieve_security_groups - if @openstack.use_nova_networking? - @openstack.compute.security_groups - else - @openstack.network.security_groups - end - end - - def pick_security_groups(default_security_groups, network_spec_security_groups, resource_pool_spec_security_groups) - return resource_pool_spec_security_groups unless resource_pool_spec_security_groups.empty? - - return network_spec_security_groups unless network_spec_security_groups.empty? - - default_security_groups - end - - def map_to_security_groups_in_openstack(picked_security_groups, openstack_security_groups) - picked_security_groups.map do |configured_sg| - openstack_security_group = find_openstack_sg_by_name(openstack_security_groups, configured_sg) - openstack_security_group ||= find_openstack_sg_by_id(openstack_security_groups, configured_sg) - cloud_error("Security group `#{configured_sg}' not found") unless openstack_security_group - openstack_security_group - end - end - - def find_openstack_sg_by_name(openstack_security_groups, security_group_name) - @openstack.with_openstack(retryable: true) do - openstack_security_groups.find { |openstack_sg| openstack_sg.name == security_group_name } - end - end - - def find_openstack_sg_by_id(openstack_security_groups, security_group_id) - @openstack.with_openstack(retryable: true) do - openstack_security_groups.find { |openstack_sg| openstack_sg.id == security_group_id } - end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/server.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/server.rb deleted file mode 100644 index c38dd6527..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/server.rb +++ /dev/null @@ -1,230 +0,0 @@ -module Bosh::OpenStackCloud - class Server - include Helpers - - REGISTRY_KEY_TAG = :registry_key - - def initialize(agent_properties, human_readable_vm_names, logger, openstack, registry, use_dhcp) - @agent_properties = agent_properties - @human_readable_vm_names = human_readable_vm_names - @logger = logger - @openstack = openstack - @registry = registry - @use_dhcp = use_dhcp - end - - attr_reader :use_dhcp - - def create(agent_settings, network_configurator, resource_pool, create_vm_params) - create_vm_params = create_vm_params.dup - - begin - nics = network_configurator.nics - @logger.debug("Using NICs: `#{nics.join(', ')}'") - create_vm_params[:nics] = nics - create_vm_params[:user_data] = JSON.dump( - user_data(create_vm_params[:name], network_configurator.network_spec, agent_settings), - ) - - server = create_server(create_vm_params) - configure_server(network_configurator, server) - - server_tags = {} - tag_server(server_tags, server, agent_settings.registry_key, network_configurator.network_spec, resource_pool.fetch('loadbalancer_pools', [])) - update_server_settings(server, agent_settings) - - server.id.to_s - rescue StandardError => e - begin - destroy(server, server_tags) if server - rescue StandardError => destroy_err - @logger.warn("Failed to destroy server: #{destroy_err.message}") - end - - raise e - end - end - - def destroy(server, server_tags) - server_tags ||= {} - server_port_ids = NetworkConfigurator.port_ids(@openstack, server.id) - @logger.debug("Network ports: `#{server_port_ids.join(', ')}' found for server #{server.id}") - - lbaas_error = catch_error('Removing lbaas pool memberships') { LoadbalancerConfigurator.new(@openstack, @logger).cleanup_memberships(server_tags) } - @openstack.with_openstack(retryable: true, ignore_not_found: true) { server.destroy } - fail_on_error( - catch_error('Wait for server deletion') { @openstack.wait_resource(server, %i[terminated deleted], :state, true) }, - catch_error('Removing ports') { NetworkConfigurator.cleanup_ports(@openstack, server_port_ids) }, - lbaas_error, - catch_error('Deleting registry settings') { - registry_key = server_tags.fetch(REGISTRY_KEY_TAG.to_s, server.name) - unless @registry.instance_of? NoopRegistry - @logger.info("Deleting settings for server `#{server.id}' with registry_key `#{registry_key}' ...") - end - @registry.delete_settings(registry_key) - }, - ) - end - - private - - def create_server(create_vm_params) - @logger.debug("Using boot params: `#{Bosh::Cpi::Redactor.clone_and_redact(create_vm_params, 'user_data').inspect}'") - server = @openstack.with_openstack do - begin - @openstack.compute.servers.create(create_vm_params) - rescue Excon::Error::Timeout => e - @logger.debug(e.backtrace) - cloud_error_message = "VM creation with name '#{create_vm_params[:name]}' received a timeout. " \ - "The VM might still have been created by OpenStack.\nOriginal message: " - raise Bosh::Clouds::VMCreationFailed.new(false), cloud_error_message + e.message - rescue Excon::Error::BadRequest, Excon::Error::NotFound, Fog::OpenStack::Compute::NotFound => e - raise e if @openstack.use_nova_networking? - not_existing_net_ids = not_existing_net_ids(create_vm_params[:nics]) - raise e if not_existing_net_ids.empty? - @logger.debug(e.backtrace) - cloud_error_message = "VM creation with name '#{create_vm_params[:name]}' failed. Following network " \ - "IDs are not existing or not accessible from this project: '#{not_existing_net_ids.join(',')}'. " \ - 'Make sure you do not use subnet IDs' - raise Bosh::Clouds::VMCreationFailed.new(false), cloud_error_message - end - end - server - end - - def configure_server(network_configurator, server) - @logger.info("Creating new server `#{server.id}'...") - begin - @openstack.wait_resource(server, :active, :state) - - @logger.info("Configuring network for server `#{server.id}'...") - network_configurator.configure(@openstack, server) - rescue StandardError => e - @logger.warn("Failed to create server: #{e.message}") - raise Bosh::Clouds::VMCreationFailed.new(true), e.message - end - end - - def tag_server(server_tags, server, registry_key, network_spec, loadbalancer_pools) - if @human_readable_vm_names - @logger.debug("'human_readable_vm_names' enabled") - - server_tags[REGISTRY_KEY_TAG] = registry_key - else - @logger.debug("'human_readable_vm_names' disabled") - end - - server_tags.merge!( - LoadbalancerConfigurator - .new(@openstack, @logger) - .create_pool_memberships(server, network_spec, loadbalancer_pools), - ) - - begin - unless server_tags.empty? - TagManager.tag_server(server, server_tags) - @logger.debug("Tagged VM '#{server.id}' with tags '#{server_tags}") - end - rescue StandardError => e - @logger.warn("Unable to tag server with tags '#{server_tags}") - raise Bosh::Clouds::VMCreationFailed.new(true), e.message - end - end - - def update_server_settings(server, agent_settings) - settings = initial_agent_settings(agent_settings) - @logger.info("Updating settings for server `#{server.id}'...") unless @registry.instance_of? NoopRegistry - @registry.update_settings(agent_settings.registry_key, settings) - rescue StandardError => e - @logger.warn("Failed to register server: #{e.message}") - raise Bosh::Clouds::VMCreationFailed.new(false), e.message - end - - ## - # Prepare server user data - # - # @param [String] registry_key used by agent to look up settings from registry - # @param [Hash] network_spec network specification - # @return [Hash] server user data - def user_data(registry_key, network_spec, agent_settings, public_key = nil) - data = {} - - data['server'] = { 'name' => registry_key } - data['openssh'] = { 'public_key' => public_key } if public_key - data['networks'] = agent_network_spec(network_spec) - - with_dns(network_spec) do |servers| - data['dns'] = { 'nameserver' => servers } - end - - if @registry.endpoint - data['registry'] = { 'endpoint' => @registry.endpoint } - else - data.merge!(initial_agent_settings(agent_settings)) - end - - data - end - - def not_existing_net_ids(nics) - result = [] - begin - network = @openstack.network - nics.each do |nic| - if nic['net_id'] - result << nic['net_id'] unless @openstack.with_openstack(retryable: true) { network.networks.get(nic['net_id']) } - end - end - rescue StandardError => e - @logger.warn(e.backtrace) - end - result - end - - ## - # Generates initial agent settings. These settings will be used by Bosh Agent - # server. Disk conventions in Bosh Agent for OpenStack are: - # - system disk: /dev/sda - # - ephemeral disk: /dev/sdb - # - persistent disks: /dev/sdc through /dev/sdz - # As some kernels remap device names (from sd* to vd* or xvd*), Bosh Agent will lookup for the proper device name - def initial_agent_settings(agent_settings) - settings = { - 'vm' => { - 'name' => agent_settings.registry_key, - }, - 'agent_id' => agent_settings.agent_id, - 'networks' => agent_network_spec(agent_settings.network_spec), - 'disks' => { - 'system' => '/dev/sda', - 'persistent' => {}, - }, - } - - settings['disks']['ephemeral'] = agent_settings.has_ephemeral ? '/dev/sdb' : nil - settings['env'] = agent_settings.environment if agent_settings.environment - settings.merge(@agent_properties) - end - - def agent_network_spec(network_spec) - network_spec.map do |name, settings| - settings['use_dhcp'] = @use_dhcp unless settings['type'] == 'vip' - [name, settings] - end.to_h - end - - ## - # Extract dns server list from network spec and yield the the list - # - # @param [Hash] network_spec network specification for instance - # @yield [Array] - def with_dns(network_spec) - network_spec.each_value do |properties| - if properties.key?('dns') && !properties['dns'].nil? - yield properties['dns'] - end - end - end - - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/stemcell.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/stemcell.rb deleted file mode 100644 index 8198d20e1..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/stemcell.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Bosh::OpenStackCloud - module Stemcell - include Helpers - - attr_reader :id, :image_id - - def initialize(logger, openstack, id) - @image_id = id - @openstack = openstack - @logger = logger - end - - def self.create(logger, openstack, id) - regex = / light$/ - - if id.match?(regex) - LightStemcell.new(logger, openstack, id.gsub(regex, '')) - else - HeavyStemcell.new(logger, openstack, id) - end - end - - def validate_existence - image = @openstack.with_openstack(retryable: true) { @openstack.image.images.find_by_id(image_id) } - cloud_error("Image `#{id}' not found") if image.nil? - @logger.debug("Using image: `#{id}'") - end - end - - class HeavyStemcell - include Stemcell - - def initialize(logger, openstack, id) - super - @id = id - end - - def delete - image = @openstack.with_openstack(retryable: true) { @openstack.image.images.find_by_id(image_id) } - if image - @openstack.with_openstack(retryable: true, ignore_not_found: true) { image.destroy } - @logger.info("Stemcell `#{image_id}' is now deleted") - else - @logger.info("Stemcell `#{image_id}' not found. Skipping.") - end - end - end - - class LightStemcell - include Stemcell - - def initialize(logger, openstack, id) - super - @id = "#{id} light" - end - - def delete - @logger.info("NoOP: Deleting light stemcell '#{id}'") - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/stemcell_creator.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/stemcell_creator.rb deleted file mode 100644 index 2c8f2dbf6..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/stemcell_creator.rb +++ /dev/null @@ -1,138 +0,0 @@ -module Bosh::OpenStackCloud - class StemcellCreator - include Helpers - def initialize(logger, openstack, cloud_properties) - @logger = logger - @openstack = openstack - @cloud_properties = cloud_properties - end - - def create(*args) - creator.create(*args) - end - - private - - def creator - if @cloud_properties.key?('image_id') - LightStemcellCreator.new(@logger, @openstack, @cloud_properties) - else - HeavyStemcellCreator.new(@logger, @openstack, @cloud_properties) - end - end - end - - class LightStemcellCreator - include Helpers - def initialize(logger, openstack, cloud_properties) - @logger = logger - @openstack = openstack - @cloud_properties = cloud_properties - end - - def create(_, _) - image_id = @cloud_properties['image_id'] - @logger.info("Checking for image with id '#{image_id}' referenced by light stemcell") - image = @openstack.with_openstack(retryable: true) { @openstack.image.images.get(image_id) } - if !image || image.status != 'active' - cloud_error("No active image with id '#{image_id}' referenced by light stemcell found in OpenStack.") - end - - LightStemcell.new(@logger, @openstack, image.id) - end - end - - class HeavyStemcellCreator - include Helpers - - def initialize(logger, openstack, cloud_properties) - @logger = logger - @openstack = openstack - @cloud_properties = cloud_properties - end - - def set_public_param(image_params, is_public) - image_params[:visibility] = is_public ? 'public' : 'private' - end - - def set_image_properties(image_params, image_properties) - image_params.merge!(image_properties) - end - - def upload(image_params, image_location) - image = create_openstack_image(image_params) - @openstack.wait_resource(image, :queued) - @logger.info("Performing file upload for image: '#{image.id}'...") - image.upload_data(File.open(image_location, 'rb')) - image - end - - def create(image_path, is_public) - Dir.mktmpdir do |tmp_dir| - @logger.info('Creating new image...') - - image_params = { - name: "#{@cloud_properties['name']}/#{@cloud_properties['version']}", - disk_format: @cloud_properties['disk_format'], - container_format: @cloud_properties['container_format'], - } - - set_public_param(image_params, is_public) - - image_properties = HeavyStemcellCreator.normalize_image_properties(@cloud_properties) - - set_image_properties(image_params, image_properties) - - @logger.info("Extracting stemcell file to `#{tmp_dir}'...") - unpack_image(tmp_dir, image_path) - - image_location = File.join(tmp_dir, 'root.img') - image = upload(image_params, image_location) - - @logger.info("Waiting for image '#{image.id}' to have status 'active'...") - @openstack.wait_resource(image, :active) - - HeavyStemcell.new(@logger, @openstack, image.id.to_s) - end - rescue StandardError => e - @logger.error(e) - raise e - end - - def create_openstack_image(image_params) - @logger.debug("Using image params: `#{image_params.inspect}'") - @openstack.with_openstack { @openstack.image.images.create(image_params) } - end - - def unpack_image(tmp_dir, image_path) - result = Bosh::Exec.sh("tar -C #{tmp_dir} -xzf #{image_path} 2>&1", on_error: :return) - if result.failed? - @logger.error("Extracting stemcell root image failed in dir #{tmp_dir}, " \ - "tar returned #{result.exit_status}, output: #{result.output}") - cloud_error('Extracting stemcell root image failed. Check task debug log for details.') - end - root_image = File.join(tmp_dir, 'root.img') - cloud_error('Root image is missing from stemcell archive') unless File.exist?(root_image) - end - - def self.normalize_image_properties(properties) - image_properties = {} - image_options = %w[version os_type os_distro architecture auto_disk_config - hw_vif_model hw_disk_bus_model hw_scsi_model hw_disk_bus - hw_vif_multiqueue_enabled hypervisor_type vmware_adaptertype - vmware_disktype vmware_linked_clone vmware_ostype] - image_options.reject { |image_option| properties[property_option_for_image_option(image_option)].nil? }.each do |image_option| - image_properties[image_option.to_sym] = properties[property_option_for_image_option(image_option)].to_s - end - image_properties - end - - def self.property_option_for_image_option(image_option) - if image_option == 'hypervisor_type' - 'hypervisor' - else - image_option - end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/tag_manager.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/tag_manager.rb deleted file mode 100644 index 7bab6d797..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/tag_manager.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Bosh::OpenStackCloud - class TagManager - MAX_TAG_KEY_LENGTH = 255 - MAX_TAG_VALUE_LENGTH = 255 - - def self.tag_server(server, tags) - formatted_tags = format(tags) - - server.metadata.update(formatted_tags) unless formatted_tags.empty? - end - - def self.tag_volume(volume, volume_id, tags) - formatted_tags = format(tags) - - volume.update_metadata(volume_id, formatted_tags) unless formatted_tags.empty? - end - - def self.tag_snapshot(snapshot, tags) - formatted_tags = format(tags) - - snapshot.update_metadata(formatted_tags) unless formatted_tags.empty? - end - - def self.format(tags) - tags - .reject { |key, value| key.nil? || value.nil? } - .map do |key, value| - [key.to_s[0..(MAX_TAG_KEY_LENGTH - 1)], value.to_s[0..(MAX_TAG_VALUE_LENGTH - 1)]] - end - .to_h - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/vip_network.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/vip_network.rb deleted file mode 100644 index 9eccb289d..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/vip_network.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Bosh::OpenStackCloud - ## - # Represents OpenStack vip network: where users sets VM's IP (floating IP's - # in OpenStack) - class VipNetwork < Network - ## - # Configures OpenStack vip network - # - # @param [Bosh::OpenStackCloud::Openstack] openstack - # @param [Fog::OpenStack::Compute::Server] server OpenStack server to - # configure - def configure(openstack, server, network_id) - cloud_error("No IP provided for vip network `#{@name}'") if @ip.nil? - - openstack.with_openstack do - FloatingIp.reassociate(openstack, @ip, server, network_id) - end - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/vm_creator.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/vm_creator.rb deleted file mode 100644 index 439a2beef..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/vm_creator.rb +++ /dev/null @@ -1,79 +0,0 @@ -module Bosh::OpenStackCloud - class VmCreator - include Helpers - - def initialize(openstack, network_configurator, server, az_provider, cloud_properties, agent_settings, create_vm_params) - @openstack = openstack - @network_configurator = network_configurator - @server = server - @az_provider = az_provider - - @cloud_properties = cloud_properties - @create_vm_params = create_vm_params - @agent_settings = agent_settings - - @logger = Bosh::Clouds::Config.logger - end - - def perform - if @az_provider.use_multiple_azs?(@cloud_properties) - create_vm_multiple_azs - else - create_vm_single_az - end - end - - private - - def create_vm_single_az - availability_zone = @az_provider.select(@disk_locality, @cloud_properties['availability_zone']) - prepare_vm_network - create_vm_in_az(availability_zone) - rescue StandardError => e - clean_vm_network - raise e - end - - def create_vm_multiple_azs - availability_zones = @az_provider.select_azs(@cloud_properties) - prepare_vm_network - begin - availability_zones.each_with_index do |az, i| - begin - return create_vm_in_az(az) - rescue StandardError => e - if az == availability_zones.last - @logger.error("Failed to create VM in AZ '#{az}' with error '#{e}' after #{i + 1} retries. No AZs left to retry.") - raise e - else - @logger.warn("Failed to create VM in AZ '#{az}' with error '#{e}', retrying in a different AZ") - end - end - end - rescue StandardError => e - clean_vm_network - raise e - end - end - - def create_vm_in_az(availability_zone) - @create_vm_params[:availability_zone] = availability_zone if availability_zone - @server.create( - @agent_settings, - @network_configurator, - @cloud_properties, - @create_vm_params - ) - end - - def prepare_vm_network - @network_configurator.prepare(@openstack) - end - - def clean_vm_network - @network_configurator.cleanup(@openstack) - rescue StandardError => e - @logger.warn("Failed to cleanup network resources: #{e.message}") - end - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/vm_factory.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/vm_factory.rb deleted file mode 100644 index f71a4374f..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/vm_factory.rb +++ /dev/null @@ -1,119 +0,0 @@ -module Bosh::OpenStackCloud - class VmFactory - include Helpers - - def initialize(openstack, server, create_vm_params, disk_locality, az_provider, openstack_properties) - @openstack = openstack - @server = server - - @create_vm_params = create_vm_params - @disk_locality = disk_locality - @az_provider = az_provider - @openstack_properties = openstack_properties - - @logger = Bosh::Clouds::Config.logger - end - - def create_vm(network_configurator, agent_id, environment, stemcell_id, cloud_properties) - pick_security_groups(@create_vm_params, ResourcePool.security_groups(cloud_properties), network_configurator) - pick_stemcell(@create_vm_params, stemcell_id) - flavor = pick_flavor(@create_vm_params, cloud_properties) - pick_key_name(@create_vm_params, cloud_properties) - configure_volumes(@create_vm_params, flavor, cloud_properties) - - agent_settings = OpenStruct.new( - registry_key: @create_vm_params[:name], - agent_id:, - environment:, - has_ephemeral: flavor_has_ephemeral_disk?(flavor), - network_spec: network_configurator.network_spec, - ) - - VmCreator.new( - @openstack, - network_configurator, - @server, - @az_provider, - cloud_properties, - agent_settings, - @create_vm_params, - ).perform - end - - private - - def pick_security_groups(server_params, resource_pool_groups, network_configurator) - network_configurator.check_preconditions(@openstack.use_nova_networking?, @create_vm_params[:config_drive], @server.use_dhcp) - network_configurator.pick_groups(@openstack, @openstack_properties.default_security_groups, resource_pool_groups) - server_params[:security_groups] = network_configurator.picked_security_groups.map(&:name) - end - - def pick_stemcell(server_params, stemcell_id) - stemcell = Stemcell.create(@logger, @openstack, stemcell_id) - stemcell.validate_existence - server_params[:image_ref] = stemcell.image_id - end - - def pick_flavor(server_params, resource_pool) - compute = @openstack.with_openstack { @openstack.compute } - flavor = @openstack.with_openstack(retryable: true) do - compute.flavors.find { |f| f.name == resource_pool['instance_type'] } - end - cloud_error("Flavor `#{resource_pool['instance_type']}' not found") if flavor.nil? - if flavor_has_ephemeral_disk?(flavor) - if flavor.ram - # Ephemeral disk size should be at least the double of the vm total memory size, as agent will need: - # - vm total memory size for swapon, - # - the rest for /var/vcap/data - min_ephemeral_size = (flavor.ram / 1024) * 2 - if flavor.ephemeral < min_ephemeral_size - cloud_error("Flavor `#{resource_pool['instance_type']}' should have at least #{min_ephemeral_size}Gb " \ - 'of ephemeral disk') - end - end - end - @logger.debug("Using flavor: `#{resource_pool['instance_type']}'") - server_params[:flavor_ref] = flavor.id - flavor - end - - def pick_key_name(server_params, resource_pool) - keyname = resource_pool['key_name'] || @openstack_properties.default_key_name - validate_key_exists(keyname) - server_params[:key_name] = keyname - end - - def validate_key_exists(keyname) - keypair = @openstack.with_openstack(retryable: true) { @openstack.compute.key_pairs.find { |k| k.name == keyname } } - cloud_error("Key-pair `#{keyname}' not found") if keypair.nil? - @logger.debug("Using key-pair: `#{keypair.name}' (#{keypair.fingerprint})") - end - - def configure_volumes(server_params, flavor, resource_pool) - volume_configurator = Bosh::OpenStackCloud::VolumeConfigurator.new(@logger) - return unless volume_configurator.boot_from_volume?(@openstack_properties.boot_from_volume, resource_pool) - - boot_vol_size = volume_configurator.select_boot_volume_size(flavor, resource_pool) - server_params[:block_device_mapping_v2] = [{ - uuid: server_params[:image_ref], - source_type: 'image', - destination_type: 'volume', - volume_size: boot_vol_size, - boot_index: '0', - delete_on_termination: '1', - device_name: '/dev/vda', - }] - server_params.delete(:image_ref) - end - - ## - # Checks if the OpenStack flavor has ephemeral disk - # - # @param [Fog::OpenStack::Compute::Flavor] OpenStack flavor - # @return [Boolean] true if flavor has ephemeral disk, false otherwise - def flavor_has_ephemeral_disk?(flavor) - flavor.ephemeral && flavor.ephemeral.to_i > 0 - end - - end -end diff --git a/src/bosh_openstack_cpi/lib/cloud/openstack/volume_configurator.rb b/src/bosh_openstack_cpi/lib/cloud/openstack/volume_configurator.rb deleted file mode 100644 index f3f584a35..000000000 --- a/src/bosh_openstack_cpi/lib/cloud/openstack/volume_configurator.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Bosh::OpenStackCloud - class VolumeConfigurator - include Helpers - - def initialize(logger) - @logger = logger - end - - def select_boot_volume_size(flavor, resource_pool) - if resource_pool['root_disk'].nil? - if flavor.disk == 0 - cloud_error("Flavor '#{flavor.name}' has a root disk size of 0. Either pick a different flavor or define root_disk.size in your VM cloud_properties") - end - - flavor.disk - else - root_disk_size = resource_pool['root_disk']['size'] - raise ArgumentError, 'Minimum root_disk size is 1 GiB' if root_disk_size.nil? || root_disk_size < 1 - @logger.debug("Using root_disk of size '#{root_disk_size}', instead of flavor.disk") - - root_disk_size - end - end - - def boot_from_volume?(boot_from_volume, resource_pool) - return boot_from_volume if resource_pool['boot_from_volume'].nil? - - resource_pool['boot_from_volume'] - end - end -end diff --git a/src/bosh_openstack_cpi/spec/assets/sample_config.yml b/src/bosh_openstack_cpi/spec/assets/sample_config.yml deleted file mode 100644 index 52425bf82..000000000 --- a/src/bosh_openstack_cpi/spec/assets/sample_config.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -openstack: - auth_url: http://127.0.0.1:5000/v2.0 - username: foo - api_key: bar - tenant: bosh - region: - endpoint_type: publicURL - default_key_name: default - default_security_groups: ["default"] - -registry: - endpoint: http://127.0.0.1:25695 - user: admin - password: admin diff --git a/src/bosh_openstack_cpi/spec/integration/calculate_vm_cloud_properties_spec.rb b/src/bosh_openstack_cpi/spec/integration/calculate_vm_cloud_properties_spec.rb deleted file mode 100644 index 25e31a029..000000000 --- a/src/bosh_openstack_cpi/spec/integration/calculate_vm_cloud_properties_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require_relative './spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - include Bosh::OpenStackCloud::Helpers - - before(:all) do - @config = IntegrationConfig.new - end - - let(:logger) { @config.logger } - - before do - delegate = double('delegate', logger:, cpi_task_log: nil) - Bosh::Clouds::Config.configure(delegate) - allow(Bosh::Clouds::Config).to receive(:logger).and_return(logger) - end - - let(:cpi_for_cloud_props) { @config.create_cpi(boot_from_volume:) } - - let(:boot_from_volume) { false } - - it 'maps cloud agnostic VM properties to OpenStack-specific cloud_properties' do - cloud_props = cpi_for_cloud_props.calculate_vm_cloud_properties( - 'ram' => 512, - 'cpu' => 1, - 'ephemeral_disk_size' => 2 * 1024, - ) - - expect(cloud_props).to include('instance_type') - instance_type = cloud_props['instance_type'] - expect(instance_type).to_not be_nil - - flavor = cpi_for_cloud_props.compute.flavors.find { |f| f.name == instance_type } - expect(flavor).to_not be_nil - expect(flavor.ram).to be >= 512 - expect(flavor.vcpus).to be >= 1 - expect(flavor.disk).to be >= 2 - end - - context 'when boot_from_volume is true' do - let(:boot_from_volume) { true } - - it 'sets custom root disk size if no flavor has enough ephemeral disk' do - cloud_props = cpi_for_cloud_props.calculate_vm_cloud_properties( - 'ram' => 512, - 'cpu' => 1, - 'ephemeral_disk_size' => 10_000 * 1024, # assumes no flavor has 10TB disks - ) - - expect(cloud_props).to include('instance_type') - instance_type = cloud_props['instance_type'] - expect(instance_type).to_not be_nil - - flavor = cpi_for_cloud_props.compute.flavors.find { |f| f.name == instance_type } - expect(flavor).to_not be_nil - expect(flavor.ram).to be >= 512 - expect(flavor.vcpus).to be >= 1 - - custom_root_disk = cloud_props['root_disk'] - expect(custom_root_disk).to_not be_nil - expect(custom_root_disk['size']).to be >= 10_000 - end - end -end diff --git a/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb b/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb deleted file mode 100644 index f2ccf8772..000000000 --- a/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require_relative './spec_helper' - -describe Bosh::OpenStackCloud::ExconLoggingInstrumentor do - include Bosh::OpenStackCloud::Helpers - - before(:all) do - @config = IntegrationConfig.new - end - - let(:logger) { Bosh::Cpi::Logger.new(log) } - let(:log) { StringIO.new('') } - let(:request_id) { '1234' } - let(:cpi) { @config.create_cpi(boot_from_volume:) } - let(:boot_from_volume) { false } - - before do - logger.set_request_id(request_id) - allow(Bosh::Clouds::Config).to receive(:logger).and_return(logger) - end - - it 'logs excon messages' do - cpi.calculate_vm_cloud_properties( - 'ram' => 512, - 'cpu' => 1, - 'ephemeral_disk_size' => 2 * 1024, - ) - - expect(log.string).to match(%r{excon\.request GET https?://.*:\d+(?:/\w+)*/v\d\.\d/}) - expect(log.string).to match(%r{excon\.response HTTP/.*(?:/\w+)*/v\d\.\d/}) - end - - it 'logs excon exceptions' do - cpi.delete_disk('123') - - expect(log.string).to match(%r{excon\.error HTTP/1\.1 404 Not Found \S*/v3/\S*volumes/123 params: .*}) - expect(log.string).not_to include('excon.error.response') - end - - it 'includes the request_id' do - cpi.calculate_vm_cloud_properties( - 'ram' => 512, - 'cpu' => 1, - 'ephemeral_disk_size' => 2 * 1024, - ) - - expect(log.string).to include('[req_id 1234]') - end -end diff --git a/src/bosh_openstack_cpi/spec/integration/image_spec.rb b/src/bosh_openstack_cpi/spec/integration/image_spec.rb deleted file mode 100644 index 605f9766f..000000000 --- a/src/bosh_openstack_cpi/spec/integration/image_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require_relative './spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - include Bosh::OpenStackCloud::Helpers - - before(:all) do - @config = IntegrationConfig.new - end - - let(:logger) { @config.logger } - let(:openstack) { @config.create_openstack } - - before do - delegate = double('delegate', logger:, cpi_task_log: nil) - Bosh::Clouds::Config.configure(delegate) - allow(Bosh::Clouds::Config).to receive(:logger).and_return(logger) - end - - describe 'Glance support' do - let(:cpi_for_stemcell) { @config.create_cpi } - - after do - cpi_for_stemcell.delete_stemcell(@stemcell_id) if @stemcell_id - openstack.wait_resource(@image, :deleted, :status, true) if @image - end - - it 'uploads and deletes a stemcell' do - @stemcell_id, stemcell_manifest = upload_stemcell(cpi_for_stemcell, @config.stemcell_path) - expect(@stemcell_id).to_not be_nil - - @image = openstack.with_openstack(retryable: true) { openstack.image.images.get(@stemcell_id) } - expect(@image).to_not be_nil - expect(@image.name).to eq("#{stemcell_manifest['cloud_properties']['name']}/#{stemcell_manifest['cloud_properties']['version']}") - expect(@image.visibility).to eq('private') - expect(@image.os_distro).to eq('ubuntu') - end - end -end diff --git a/src/bosh_openstack_cpi/spec/integration/lifecycle_spec.rb b/src/bosh_openstack_cpi/spec/integration/lifecycle_spec.rb deleted file mode 100644 index 20833ec32..000000000 --- a/src/bosh_openstack_cpi/spec/integration/lifecycle_spec.rb +++ /dev/null @@ -1,604 +0,0 @@ -require_relative './spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - before(:all) do - @config = IntegrationConfig.new - @cpi_for_stemcell = @config.create_cpi - @stemcell_id, = upload_stemcell(@cpi_for_stemcell, @config.stemcell_path) - end - - before { allow(Bosh::Clouds::Config).to receive(:logger).and_return(@config.logger) } - - after(:all) do - @cpi_for_stemcell.delete_stemcell(@stemcell_id) - end - - let(:boot_from_volume) { false } - let(:config_drive) { nil } - let(:use_dhcp) { true } - let(:human_readable_vm_names) { false } - let(:use_nova_networking) { false } - let(:openstack) { @config.create_openstack } - - subject(:cpi) do - @config.create_cpi(boot_from_volume:, config_drive:, human_readable_vm_names:, use_nova_networking:, use_dhcp:) - end - - describe 'dynamic network' do - # even for dynamic networking we need to set the net_id as we may be in an environment - # with multiple networks - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - context 'without existing disks and with a floating ip' do - let(:network_spec_with_vip_network) do - { - 'vip_network' => { - 'type' => 'vip', - 'ip' => @config.floating_ip, - }, - }.merge(network_spec) - end - - before { @vm_with_assigned_floating_ip = create_vm(@stemcell_id, network_spec_with_vip_network, []) } - after { clean_up_vm(@vm_with_assigned_floating_ip) if @vm_with_assigned_floating_ip } - - it 'exercises the vm lifecycle and reassigns the floating ip' do - vm_lifecycle(@stemcell_id, network_spec_with_vip_network) - end - end - - context 'with existing disks' do - before do - @temp_vm_cid = create_vm(@stemcell_id, network_spec, []) - @existing_volume_id = cpi.create_disk(2048, {}, @temp_vm_cid) - cpi.delete_vm(@temp_vm_cid) - end - after { cpi.delete_disk(@existing_volume_id) if @existing_volume_id } - - it 'exercises the vm lifecycle' do - expect { - vm_lifecycle(@stemcell_id, network_spec, @existing_volume_id) - }.to_not raise_error - end - end - - describe 'set_vm_metadata' do - let(:human_readable_vm_names) { true } - before { @human_readable_vm_name_id = create_vm(@stemcell_id, network_spec, []) } - after { clean_up_vm(@human_readable_vm_name_id) if @human_readable_vm_name_id } - - it 'sets the vm name according to the metadata' do - vm = openstack.compute.servers.get(@human_readable_vm_name_id) - expect(vm.name).to eq 'openstack_cpi_spec/instance_id' - end - end - end - - describe 'manual network' do - let(:network_spec) do - { - 'default' => { - 'type' => 'manual', - 'ip' => @config.manual_ip, - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - context 'without existing disks' do - it 'exercises the vm lifecycle' do - expect { - vm_lifecycle(@stemcell_id, network_spec) - }.to_not raise_error - end - end - - context 'with existing disks' do - before do - @temp_vm_cid = create_vm(@stemcell_id, network_spec, []) - @existing_volume_id = cpi.create_disk(2048, {}, @temp_vm_cid) - clean_up_vm(@temp_vm_cid) - end - - after { cpi.delete_disk(@existing_volume_id) if @existing_volume_id } - - it 'exercises the vm lifecycle' do - expect { - vm_lifecycle(@stemcell_id, network_spec, @existing_volume_id) - }.to_not raise_error - end - end - - context 'with multiple networks and config_drive' do - let(:multiple_network_spec) do - { - 'network_1' => { - 'type' => 'manual', - 'ip' => @config.no_dhcp_manual_ip_1, - 'cloud_properties' => { - 'net_id' => @config.net_id_no_dhcp_1, - }, - }, - 'network_2' => { - 'type' => 'manual', - 'ip' => @config.no_dhcp_manual_ip_2, - 'cloud_properties' => { - 'net_id' => @config.net_id_no_dhcp_2, - }, - 'use_dhcp' => false, - }, - } - end - - let(:config_drive) { 'cdrom' } - let(:use_dhcp) { false } - - after { clean_up_vm(@multiple_nics_vm_id) if @multiple_nics_vm_id } - - it 'creates writes the mac addresses of the two networks to the metadata service' do - server_user_data_as_string = nil - allow_any_instance_of(Bosh::OpenStackCloud::Server).to receive(:create_server).and_wrap_original do |method, *arguments| - server_user_data_as_string = arguments[0][:user_data] - method.call(*arguments) - end - - @multiple_nics_vm_id = create_vm(@stemcell_id, multiple_network_spec, []) - - vm = openstack.compute.servers.get(@multiple_nics_vm_id) - network_interfaces = vm.addresses.map { |_, interfaces| interfaces }.flatten - network_interface_1 = network_interfaces.find(&where_ip_address_is(@config.no_dhcp_manual_ip_1)) - network_interface_2 = network_interfaces.find(&where_ip_address_is(@config.no_dhcp_manual_ip_2)) - - server_user_data = JSON.parse(server_user_data_as_string) - expect(network_interface_1['OS-EXT-IPS-MAC:mac_addr']).to eq(server_user_data['networks']['network_1']['mac']) - expect(network_interface_2['OS-EXT-IPS-MAC:mac_addr']).to eq(server_user_data['networks']['network_2']['mac']) - - ports = openstack.with_openstack(retryable: true) { openstack.network.ports.all(device_id: @multiple_nics_vm_id) } - clean_up_vm(@multiple_nics_vm_id) if @multiple_nics_vm_id - - found_ports = openstack.with_openstack(retryable: true) { ports.find { |port| openstack.network.ports.get port.id } } - expect(found_ports).to be_nil - end - - def where_ip_address_is(ip) - ->(network_interface) { network_interface['addr'] == ip } - end - end - - context 'when a neutron port already exists for a given IP' do - before do - clean_up_port(@config.manual_ip, @config.net_id) - openstack.network.create_port( - @config.net_id, - fixed_ips: [{ ip_address: @config.manual_ip }], - security_groups: [@config.security_group_id], - ) - end - - after do - clean_up_vm(@vm_id) if @vm_id - clean_up_port(@config.manual_ip, @config.net_id) - end - - it 'can still create the VM' do - expect { - @vm_id = create_vm(@stemcell_id, network_spec, []) - }.to_not raise_error - end - - def clean_up_port(ip, net_id) - ports = openstack.with_openstack(retryable: true) do - openstack.network.ports.all(fixed_ips: ["ip_address=#{ip}", "network_id=#{net_id}"]) - end - - port_ids = ports.select { |p| p.status == 'DOWN' && p.device_id.empty? && p.device_owner.empty? }.map(&:id) - openstack.with_openstack(retryable: true) { openstack.network.delete_port(port_ids.first) } unless port_ids.empty? - end - end - - context 'with vrrp' do - before { @vm_with_vrrp_ip = create_vm(@stemcell_id, network_spec, [], { 'allowed_address_pairs' => @config.allowed_address_pairs }) } - after { clean_up_vm(@vm_with_vrrp_ip) if @vm_with_vrrp_ip } - - it 'adds vrrp_ip as allowed_address_pairs' do - vrrp_port = openstack.with_openstack(retryable: true) do - openstack.network.ports.all(fixed_ips: "ip_address=#{@config.manual_ip}")[0] - end - port_info = openstack.with_openstack(retryable: true) do - openstack.network.get_port(vrrp_port.id) - end - expect(port_info).to be - - allowed_address_pairs = port_info[:body]['port']['allowed_address_pairs'] - expect(allowed_address_pairs.size).not_to be_zero - - assigned_allowed_address_pairs = allowed_address_pairs[0]['ip_address'] - - expect(assigned_allowed_address_pairs).to eq(@config.allowed_address_pairs) - end - end - end - - context 'when booting from volume' do - let(:boot_from_volume) { true } - let(:network_spec) do - { - 'default' => { - 'type' => 'manual', - 'ip' => @config.manual_ip, - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - def test_boot_volume(resource_pool = {}) - @vm_id = create_vm(@stemcell_id, network_spec, [], resource_pool) - volumes = volumes(@vm_id) - expect(volumes.size).to eq(1) - expect(volumes.first['device']).to eq('/dev/vda') - end - - after(:each) { clean_up_vm(@vm_id) if @vm_id } - - it 'creates a vm with boot_volume on /dev/vda' do - test_boot_volume - end - - context 'when boot_from_volume defined in the cloud_properties' do - let(:boot_from_volume) { false } - - it 'creates a vm with boot_volume on /dev/vda' do - test_boot_volume({ 'boot_from_volume' => true }) - end - end - - context 'and flavor has root disk size 0' do - let(:resource_pool) do - { - 'instance_type' => @config.instance_type_with_no_root_disk, - } - end - - context 'and root disk size given in manifest' do - before do - resource_pool['root_disk'] = { - 'size' => 20, - } - end - - it 'creates a vm with boot_volume on /dev/vda' do - test_boot_volume - end - end - - context 'and root disk size not given in manifest' do - it 'raises an error' do - expect { - vm_lifecycle(@stemcell_id, network_spec, nil, resource_pool) - }.to raise_error(Bosh::Clouds::CloudError, /Flavor '#{@config.instance_type_with_no_root_disk}' has a root disk size of 0/) - end - end - end - end - - context 'when using cloud_properties and specifying security groups' do - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - 'security_groups' => [security_group], - }, - }, - } - end - - context 'when security group is specified by name' do - let(:security_group) { @config.security_group_name } - - it 'exercises the vm lifecycle' do - expect { - vm_lifecycle(@stemcell_id, network_spec) - }.to_not raise_error - end - end - - context 'when security group is specified by id' do - let(:security_group) { @config.security_group_id } - - it 'exercises the vm lifecycle' do - expect { - vm_lifecycle(@stemcell_id, network_spec) - }.to_not raise_error - end - end - end - - context 'when using config drive as cdrom' do - let(:config_drive) { @config.config_drive } - - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - it 'exercises the vm lifecycle' do - expect { - vm_lifecycle(@stemcell_id, network_spec) - }.to_not raise_error - end - end - - context 'when vm creation fails' do - let(:network_spec_that_fails) do - { - 'default' => { - 'type' => 'manual', - 'ip' => @config.manual_ip, - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - 'vip' => { - 'type' => 'vip', - 'ip' => '255.255.255.255', - }, - } - end - - def no_active_vm_with_ip?(ip) - openstack.compute.servers.none? do |s| - s.private_ip_address == ip && [:active].include?(s.state.downcase.to_sym) - end - end - - def no_port_remaining?(net_id, ip) - openstack.with_openstack(retryable: true) do - openstack.network.ports - .select { |port| port.network_id == net_id } - .none? { |port| port.fixed_ips.detect { |ips| ips['ip_address'] == ip } } - end - end - - it 'cleans up vm' do - expect { - create_vm(@stemcell_id, network_spec_that_fails, []) - }.to raise_error Bosh::Clouds::VMCreationFailed, /Floating IP '255.255.255.255' not allocated/ - - expect(no_active_vm_with_ip?(@config.manual_ip)).to be - expect(no_port_remaining?(@config.net_id, @config.manual_ip)).to eq(true) - end - - it 'better error message for wrong net ID' do - network_spec_with_wrong_net_id = { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => '00000000-0000-0000-0000-000000000000', - }, - }, - } - expect { - create_vm(@stemcell_id, network_spec_with_wrong_net_id, []) - }.to raise_error Bosh::Clouds::VMCreationFailed, /'00000000-0000-0000-0000-000000000000'/ - end - end - - context 'when detaching a non-existing disk' do - # Detaching a non-existing disk from vm should NOT raise error - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - it 'exercises the vm lifecycles' do - vm_id = create_vm(@stemcell_id, network_spec, []) - - expect { - @config.logger.info("Detaching disk vm_id=#{vm_id} disk_id=non-existing-disk") - cpi.detach_disk(vm_id, 'non-existing-disk') - }.to_not raise_error - - clean_up_vm(vm_id) - end - end - - describe 'use_nova_networking=true' do - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - let(:use_nova_networking) { true } - after { clean_up_vm(@vm_id_for_nova_compatibility) if @vm_id_for_nova_compatibility } - - it 'create vm does not use neutron for security groups' do - stub_request(:any, /.*\/v2\.0\/security-groups/) - - @vm_id_for_nova_compatibility = create_vm(@stemcell_id, network_spec, []) - - expect(WebMock).to_not have_requested(:any, /.*\/v2\.0\/security-groups/) - end - end - - describe 'light stemcell' do - let(:light_stemcell_id) { "#{@stemcell_id} light" } - - describe '#create_stemcell' do - it 'returns the stemcell id with ` light` suffix' do - cloud_properties = { - 'image_id' => @stemcell_id, - } - - expect(cpi.create_stemcell('not_relevant_path', cloud_properties)).to eq(light_stemcell_id) - end - - context 'when referenced image does not exist' do - it 'raises an error' do - cloud_properties = { - 'image_id' => 'non-existing-id', - } - - expect { - cpi.create_stemcell('not_relevant_path', cloud_properties) - }.to raise_error Bosh::Clouds::CloudError - end - end - end - - describe '#create_vm' do - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - it 'creates a vm with the heavy stemcell id' do - vm_lifecycle(light_stemcell_id, network_spec) - end - end - end - - describe 'set_disk_metadata' do - before { @disk_id = cpi.create_disk(2048, {}, nil) } - after { clean_up_disk(@disk_id) if @disk_id } - - let(:metadata) do - { - 'id' => 'my-id', - 'deployment' => 'my-deployment', - 'job' => 'my-job', - 'index' => 'my-index', - 'some_key' => 'some_value', - } - end - - it 'sets the disk metadata accordingly' do - disk = openstack.with_openstack(retryable: true) { openstack.volume.volumes.get(@disk_id) } - expect(disk.metadata).not_to include(metadata) - - cpi.set_disk_metadata(@disk_id, metadata) - - disk = openstack.with_openstack(retryable: true) { openstack.volume.volumes.get(@disk_id) } - expect(disk.metadata).to include(metadata) - end - end - - describe 'resize_disk' do - before { @disk_id = cpi.create_disk(2048, {}, nil) } - after { clean_up_disk(@disk_id) if @disk_id } - - it 'resizes the disk' do - cpi.resize_disk(@disk_id, 4096) - - disk = openstack.with_openstack(retryable: true) { openstack.volume.volumes.get(@disk_id) } - expect(disk.size).to eq(4) - end - end - - describe 'when using load balancer pool' do - before(:all) do - skip('No lbaas pool configured') unless @config.lbaas_pool_name - end - - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - let(:resource_pool_spec_with_lbaas_pools) do - { - 'loadbalancer_pools' => [ - { 'name' => @config.lbaas_pool_name, 'port' => 4443 }, - ], - 'key_name' => @config.default_key_name, - 'availability_zone' => @config.availability_zone, - 'instance_type' => @config.instance_type, - } - end - - it 'exercises vm lifecycle' do - vm_id = nil - - expect { - vm_id = create_vm(@stemcell_id, network_spec, [], resource_pool_spec_with_lbaas_pools) - }.to_not raise_error - - expect(vm_id).not_to be_nil - - expect { - clean_up_vm(vm_id) - }.to_not raise_error - end - end - - describe 'when creating a server in a non-existent availability zone' do - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - let(:resource_pool_spec) do - { - 'key_name' => @config.default_key_name, - 'availability_zone' => 'fake-availability-zone', - 'instance_type' => @config.instance_type, - } - end - - it 'raises an error' do - expect{ - create_vm(@stemcell_id, network_spec, [], resource_pool_spec) - }.to raise_error; - end - end -end diff --git a/src/bosh_openstack_cpi/spec/integration/lifecycle_v2_spec.rb b/src/bosh_openstack_cpi/spec/integration/lifecycle_v2_spec.rb deleted file mode 100644 index 9804c5c88..000000000 --- a/src/bosh_openstack_cpi/spec/integration/lifecycle_v2_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require_relative './spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - subject(:cpi) { @config.create_cpi } - - before(:all) do - @config = IntegrationConfig.new(:v2) - end - - let(:boot_from_volume) { false } - let(:config_drive) { nil } - let(:logger) { @config.logger } - - before do - delegate = double('delegate', logger:, cpi_task_log: nil) - Bosh::Clouds::Config.configure(delegate) - allow(Bosh::Clouds::Config).to receive(:logger).and_return(logger) - end - - describe 'Basic Keystone V2 support' do - context 'with missing VM id' do - it 'should return false' do - logger.info('Checking VM existence') - expect(cpi).to_not have_vm('non-existing-vm-id') - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/integration/spec_helper.rb b/src/bosh_openstack_cpi/spec/integration/spec_helper.rb deleted file mode 100644 index 253c4700a..000000000 --- a/src/bosh_openstack_cpi/spec/integration/spec_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' -require 'webmock/rspec' -require_relative './support/integration_config' -require_relative './support/lifecycle_helpers' -require 'cloud' -require 'tempfile' -require 'logger' -require 'ostruct' -require 'bosh/cpi' - -RSpec.configure do |config| - config.before(:all) { WebMock.allow_net_connect! } -end - -def upload_stemcell(cpi, stemcell_path) - stemcell_manifest = Psych.load_file(File.join(stemcell_path, 'stemcell.MF')) - stemcell_id = cpi.create_stemcell(File.join(stemcell_path, 'image'), stemcell_manifest['cloud_properties']) - [stemcell_id, stemcell_manifest] -end diff --git a/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb b/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb deleted file mode 100644 index e17fcb5ae..000000000 --- a/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb +++ /dev/null @@ -1,143 +0,0 @@ -class IntegrationConfig - attr_reader :logger, - :auth_url, - :username, - :api_key, - :tenant, - :project, - :domain, - :stemcell_path, - :net_id, - :net_id_no_dhcp_1, - :net_id_no_dhcp_2, - :volume_type, - :manual_ip, - :no_dhcp_manual_ip_1, - :no_dhcp_manual_ip_2, - :disable_snapshots, - :default_key_name, - :config_drive, - :ignore_server_az, - :instance_type, - :instance_type_with_no_root_disk, - :availability_zone, - :region, - :floating_ip, - :security_group_id, - :security_group_name, - :ca_cert_path, - :insecure, - :lbaas_pool_name, - :allowed_address_pairs - - def initialize(identity_version = :v3) - if identity_version == :v3 - @auth_url = LifecycleHelper.get_config(:auth_url_v3) - @domain = LifecycleHelper.get_config(:domain) - @username = LifecycleHelper.get_config(:username_v3) - @api_key = LifecycleHelper.get_config(:api_key_v3) - @project = LifecycleHelper.get_config(:project) - else - @auth_url = LifecycleHelper.get_config(:auth_url_v2) - @username = LifecycleHelper.get_config(:username_v2, LifecycleHelper.get_config(:username_v3)) - @api_key = LifecycleHelper.get_config(:api_key_v2, LifecycleHelper.get_config(:api_key_v3)) - @tenant = LifecycleHelper.get_config(:tenant, LifecycleHelper.get_config(:project)) - end - - @logger = Bosh::Cpi::Logger.new(STDERR) - @logger.level = ::Logger::INFO - @logger.set_request_id("fake-#{Random.rand(100000..999999)}") - - @ca_cert_content = LifecycleHelper.get_config(:ca_cert, nil) - if @ca_cert_content && !@ca_cert_content.empty? && @ca_cert_content != 'null' - @ca_cert_file = write_ca_cert(logger) - @ca_cert_path = @ca_cert_file.path - end - @insecure = LifecycleHelper.get_config(:insecure, false) - @connection_options = { - 'connect_timeout' => LifecycleHelper.get_config(:connect_timeout, '120').to_i, - 'read_timeout' => LifecycleHelper.get_config(:read_timeout, '120').to_i, - 'write_timeout' => LifecycleHelper.get_config(:write_timeout, '120').to_i, - 'ssl_verify_peer' => !@insecure, - } - @connection_options['ssl_ca_file'] = @ca_cert_path if @ca_cert_path - - @stemcell_path = LifecycleHelper.get_config(:stemcell_path) - @net_id = LifecycleHelper.get_config(:net_id) - @net_id_no_dhcp_1 = LifecycleHelper.get_config(:net_id_no_dhcp_1) - @net_id_no_dhcp_2 = LifecycleHelper.get_config(:net_id_no_dhcp_2) - @volume_type = LifecycleHelper.get_config(:volume_type) - @manual_ip = LifecycleHelper.get_config(:manual_ip) - @no_dhcp_manual_ip_1 = LifecycleHelper.get_config(:no_dhcp_manual_ip_1) - @no_dhcp_manual_ip_2 = LifecycleHelper.get_config(:no_dhcp_manual_ip_2) - @disable_snapshots = LifecycleHelper.get_config(:disable_snapshots, false) - @default_key_name = LifecycleHelper.get_config(:default_key_name) - @config_drive = LifecycleHelper.get_config(:config_drive, 'cdrom') - @ignore_server_az = LifecycleHelper.get_config(:ignore_server_az, 'false') - @instance_type = LifecycleHelper.get_config(:instance_type, 'm1.small') - @instance_type_with_no_root_disk = LifecycleHelper.get_config(:flavor_with_no_root_disk) - @availability_zone = LifecycleHelper.get_config(:availability_zone, nil) - @floating_ip = LifecycleHelper.get_config(:floating_ip) - @security_group_id = LifecycleHelper.get_config(:security_group_id) - @security_group_name = LifecycleHelper.get_config(:security_group_name) - @lbaas_pool_name = LifecycleHelper.get_config(:lbaas_pool_name, nil) - @allowed_address_pairs = LifecycleHelper.get_config(:allowed_address_pairs, nil) - # some environments may not have this set, and it isn't strictly necessary so don't raise if it isn't set - @region = LifecycleHelper.get_config(:region, nil) - Bosh::Clouds::Config.configure(OpenStruct.new(logger: @logger, cpi_task_log: nil)) - end - - def create_cpi(boot_from_volume: false, config_drive: nil, human_readable_vm_names: false, use_nova_networking: false, use_dhcp: true, default_volume_type: nil) - properties = { - 'openstack' => openstack_properties(boot_from_volume, config_drive, human_readable_vm_names, use_nova_networking, use_dhcp, default_volume_type), - } - Bosh::OpenStackCloud::Cloud.new(properties, 2) - end - - def create_openstack - Bosh::OpenStackCloud::Openstack.new(openstack_properties, {}, {}) - end - - def openstack_properties(boot_from_volume = false, config_drive = nil, human_readable_vm_names = false, use_nova_networking = false, use_dhcp = true, default_volume_type = nil) - properties = { - 'auth_url' => @auth_url, - 'username' => @username, - 'api_key' => @api_key, - 'region' => @region, - 'endpoint_type' => 'publicURL', - 'default_key_name' => @default_key_name, - 'default_security_groups' => %w[default], - 'default_volume_type' => default_volume_type, - 'wait_resource_poll_interval' => 5, - 'boot_from_volume' => boot_from_volume, - 'config_drive' => config_drive, - 'use_dhcp' => use_dhcp, - 'ignore_server_availability_zone' => str_to_bool(@ignore_server_az), - 'human_readable_vm_names' => human_readable_vm_names, - 'use_nova_networking' => use_nova_networking, - 'connection_options' => @connection_options, - 'vm' => { - 'stemcell' => { - 'api_version' => 2, - }, - }, - } - - if @domain - properties['domain'] = @domain - properties['project'] = @project - else - properties['tenant'] = @tenant - end - properties - end - - private - - def write_ca_cert(logger) - @ca_cert_file = Tempfile.new(['cacert', '.pem']) - logger.info("cacert.pem file: #{@ca_cert_file.path}") - File.write(@ca_cert_file.path, @ca_cert_content) - @ca_cert_file - end -end diff --git a/src/bosh_openstack_cpi/spec/integration/support/lifecycle_helpers.rb b/src/bosh_openstack_cpi/spec/integration/support/lifecycle_helpers.rb deleted file mode 100644 index bcd715996..000000000 --- a/src/bosh_openstack_cpi/spec/integration/support/lifecycle_helpers.rb +++ /dev/null @@ -1,130 +0,0 @@ -module Support - module LifecycleHelpers - def volumes(vm_id) - openstack.with_openstack(retryable: true) { openstack.compute.servers.get(vm_id).volume_attachments } - end - - def vm_lifecycle(stemcell_id, network_spec, disk_id = nil, resource_pool = {}) - vm_id = create_vm(stemcell_id, network_spec, Array(disk_id), resource_pool) - - if disk_id - @config.logger.info("Reusing disk #{disk_id} for VM vm_id #{vm_id}") - else - @config.logger.info("Creating disk for VM vm_id #{vm_id}") - disk_id = cpi.create_disk(2048, {}, vm_id) - expect(disk_id).to be - end - - @config.logger.info("Checking existence of disk vm_id=#{vm_id} disk_id=#{disk_id}") - expect(cpi.has_disk?(disk_id)).to be(true) - - @config.logger.info("Attaching disk vm_id=#{vm_id} disk_id=#{disk_id}") - cpi.attach_disk(vm_id, disk_id) - - @config.logger.info("Detaching disk vm_id=#{vm_id} disk_id=#{disk_id}") - cpi.detach_disk(vm_id, disk_id) - - disk_snapshot_id = create_disk_snapshot(disk_id) unless @config.disable_snapshots - rescue StandardError => create_error - # intentionally blank - ensure - funcs = [ - -> { clean_up_disk(disk_id) }, - -> { clean_up_vm(vm_id) }, - ] - funcs.unshift(-> { clean_up_disk_snapshot(disk_snapshot_id) }) unless @config.disable_snapshots - run_all_and_raise_any_errors(create_error, funcs) - end - - def create_vm(stemcell_id, network_spec, disk_locality, resource_pool = {}, environment = { 'bosh' => { 'group' => 'instance-group-1' } }) - @config.logger.info("Creating VM with stemcell_id=#{stemcell_id}") - vm_id, = cpi.create_vm( - 'agent-007', - stemcell_id, - { 'instance_type' => @config.instance_type, - 'availability_zone' => @config.availability_zone}.merge(resource_pool), - network_spec, - disk_locality, - environment, - ) - expect(vm_id).to be - - @config.logger.info("Checking VM existence vm_id=#{vm_id}") - expect(cpi).to have_vm(vm_id) - - @config.logger.info("Setting VM metadata vm_id=#{vm_id}") - cpi.set_vm_metadata(vm_id, { - 'deployment' => 'deployment', - 'name' => 'openstack_cpi_spec/instance_id', - }) - - vm_id - end - - def clean_up_vm(vm_id) - if vm_id - @config.logger.info("Deleting VM vm_id=#{vm_id}") - cpi.delete_vm(vm_id) - - @config.logger.info("Checking VM existence vm_id=#{vm_id}") - expect(cpi).to_not have_vm(vm_id) - else - @config.logger.info('No VM to delete') - end - end - - def clean_up_disk(disk_id) - if disk_id - @config.logger.info("Deleting disk disk_id=#{disk_id}") - cpi.delete_disk(disk_id) - else - @config.logger.info('No disk to delete') - end - end - - def create_disk_snapshot(disk_id) - @config.logger.info("Creating disk snapshot disk_id=#{disk_id}") - disk_snapshot_id = cpi.snapshot_disk(disk_id, { - deployment: 'deployment', - job: 'openstack_cpi_spec', - index: '0', - instance_id: 'instance', - agent_id: 'agent', - director_name: 'Director', - director_uuid: '6d06b0cc-2c08-43c5-95be-f1b2dd247e18', - }) - expect(disk_snapshot_id).to be - - @config.logger.info("Created disk snapshot disk_snapshot_id=#{disk_snapshot_id}") - disk_snapshot_id - end - - def clean_up_disk_snapshot(disk_snapshot_id) - if disk_snapshot_id - @config.logger.info("Deleting disk snapshot disk_snapshot_id=#{disk_snapshot_id}") - cpi.delete_snapshot(disk_snapshot_id) - else - @config.logger.info('No disk snapshot to delete') - end - end - - def run_all_and_raise_any_errors(existing_errors, funcs) - exceptions = Array(existing_errors) - funcs.each do |f| - begin - f.call - rescue StandardError => e - exceptions << e - end - end - # Prints all exceptions but raises original exception - exceptions.each { |e| @config.logger.info("Failed with: #{e.inspect}\n#{e.backtrace.join("\n")}\n") } - raise exceptions.first if exceptions.any? - end - end -end - -RSpec.configure do |config| - config.include(Support::LifecycleHelpers) -end - diff --git a/src/bosh_openstack_cpi/spec/integration/volume_spec.rb b/src/bosh_openstack_cpi/spec/integration/volume_spec.rb deleted file mode 100644 index 23c7ae684..000000000 --- a/src/bosh_openstack_cpi/spec/integration/volume_spec.rb +++ /dev/null @@ -1,157 +0,0 @@ -require_relative './spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - include Bosh::OpenStackCloud::Helpers - - before(:all) do - @config = IntegrationConfig.new - @cpi_for_stemcell = @config.create_cpi - @stemcell_id, = upload_stemcell(@cpi_for_stemcell, @config.stemcell_path) - end - - after(:all) do - @cpi_for_stemcell.delete_stemcell(@stemcell_id) - end - - let(:disk_metadata) do - { - 'director' => 'Director', - 'deployment' => 'deployment', - 'instance_id' => 'instance', - 'job' => 'openstack_cpi_spec', - 'instance_index' => '0', - 'instance_name' => 'openstack_cpi_spec/instance', - 'attached_at' => '2017-08-23T16:13:00Z', - } - end - - let(:disk_snapshot_metadata) do - { - deployment: 'deployment', - job: 'openstack_cpi_spec', - index: 0, - instance_id: 'instance', - agent_id: 'agent', - director_name: 'Director', - director_uuid: '6d06b0cc-2c08-43c5-95be-f1b2dd247e18', - } - end - - let(:expected_snapshot_metadata) do - { - 'director' => 'Director', - 'director_uuid' => '6d06b0cc-2c08-43c5-95be-f1b2dd247e18', - 'deployment' => 'deployment', - 'instance_id' => 'instance', - 'instance_index' => '0', - 'instance_name' => 'openstack_cpi_spec/instance', - 'agent_id' => 'agent', - } - end - - let(:cpi_for_vm) { @config.create_cpi } - let(:openstack) { @config.create_openstack } - - let(:network_spec) do - { - 'default' => { - 'type' => 'dynamic', - 'cloud_properties' => { - 'net_id' => @config.net_id, - }, - }, - } - end - - let(:vm_id) do - created_vm_id, = cpi_for_vm.create_vm( - 'agent-007', - @stemcell_id, - { - 'instance_type' => @config.instance_type, - 'availability_zone' => @config.availability_zone, - }, - network_spec, - [], - 'key' => 'value', - ) - created_vm_id - end - - let(:cloud_properties) { {} } - - let(:supported_volume_type) { @config.volume_type } - - after(:each) do - cpi_for_vm.delete_vm(vm_id) - end - - describe 'Cinder support' do - context 'with NO global default_volume_type' do - let(:cpi_for_volume) { @config.create_cpi } - - context 'and NO `type` set in cloud_properties' do - it 'uses default Cinder volume type' do - volume_lifecycle - end - end - - context 'and `type` set in cloud_properties' do - let(:cloud_properties) { { 'type' => supported_volume_type } } - - it 'uses the `type` configured in cloud_properties' do - volume_lifecycle(supported_volume_type) - end - end - end - - context 'with global default_volume_type' do - context 'and `type` set in cloud_properties' do - let(:cpi_for_volume) { @config.create_cpi(default_volume_type: 'type-to-override') } - let(:cloud_properties) { { 'type' => supported_volume_type } } - - it 'uses the `type` configured in cloud_properties' do - volume_lifecycle(supported_volume_type) - end - end - - context 'and no `type` set in cloud_properties' do - let(:cpi_for_volume) { @config.create_cpi(default_volume_type: supported_volume_type) } - - it 'uses the default_volume_type' do - volume_lifecycle(supported_volume_type) - end - end - end - end - - def volume_lifecycle(volume_type = nil) - expect(vm_id).to_not be_nil - - disk_id = cpi_for_volume.create_disk(2048, cloud_properties, vm_id) - expect(disk_id).to be - - expect(cpi_for_volume.has_disk?(disk_id)).to be(true) - - found_volume_type = openstack.with_openstack(retryable: true) { openstack.volume.volumes.get(disk_id).volume_type } - expect(found_volume_type).to eq(volume_type) unless volume_type.nil? - - cpi_for_volume.attach_disk(vm_id, disk_id) - - cpi_for_volume.set_disk_metadata(disk_id, disk_metadata) - found_metadata = openstack.with_openstack(retryable: true) { cpi_for_volume.volume.volumes.get(disk_id).metadata } - expect(found_metadata).to include(disk_metadata) - - disk_snapshot_id = cpi_for_volume.snapshot_disk(disk_id, disk_snapshot_metadata) - expect(disk_snapshot_id).to be - - found_snapshot_metadata = openstack.with_openstack(retryable: true) do - cpi_for_volume.volume.snapshots.get(disk_snapshot_id).metadata - end - expect(found_snapshot_metadata).to eq(expected_snapshot_metadata) - - cpi_for_volume.delete_snapshot(disk_snapshot_id) - cpi_for_volume.detach_disk(vm_id, disk_id) - cpi_for_volume.delete_disk(disk_id) - end -end diff --git a/src/bosh_openstack_cpi/spec/spec_helper.rb b/src/bosh_openstack_cpi/spec/spec_helper.rb deleted file mode 100644 index 5de0f380e..000000000 --- a/src/bosh_openstack_cpi/spec/spec_helper.rb +++ /dev/null @@ -1,233 +0,0 @@ -$LOAD_PATH.unshift File.expand_path('../lib', __dir__) - -require 'tmpdir' -require 'zlib' -require 'webmock' -require 'tempfile' -require 'timecop' - -require 'cloud/openstack' - -def mock_cloud_options(api_version = 2, devstack = false) - cloud_properties = { - 'plugin' => 'openstack', - 'properties' => { - 'openstack' => { - 'auth_url' => 'http://127.0.0.1:5000/v2.0', - 'username' => 'admin', - 'api_key' => 'nova', - 'tenant' => 'dummy_tenant', - 'region' => 'RegionOne', - 'state_timeout' => 1, - 'wait_resource_poll_interval' => 3, - 'use_dhcp' => true, - 'stemcell_public_visibility' => false, - }, - 'registry' => { - 'endpoint' => 'localhost:42288', - 'user' => 'admin', - 'password' => 'admin', - }, - 'agent' => { - 'foo' => 'bar', - 'baz' => 'zaz', - }, - }, - } - if api_version == 3 - keystone_path = devstack ? '/identity/v3' : '/v3' - cloud_properties['properties']['openstack']['auth_url'] = "http://127.0.0.1:5000#{keystone_path}" - cloud_properties['properties']['openstack']['project'] = 'dummy_project' - cloud_properties['properties']['openstack']['domain'] = 'some_domain' - else - keystone_path = devstack ? '/identity/v2.0' : '/v2.0' - cloud_properties['properties']['openstack']['auth_url'] = "http://127.0.0.1:5000#{keystone_path}" - end - cloud_properties -end - -def mock_registry(endpoint = 'http://registry:3333') - registry = double('registry', endpoint:) - allow(Bosh::Cpi::RegistryClient).to receive(:new).and_return(registry) - registry -end - -def mock_cloud(options = nil, cpi_api_version = 1) - servers = double('servers') - images = double('images') - flavors = double('flavors') - volumes = double('volumes') - snapshots = double('snapshots') - key_pairs = double('key_pairs') - security_groups = [double('default_sec_group', id: 'default_sec_group_id', name: 'default')] - - image = double(Fog::OpenStack::Image) - allow(Fog::OpenStack::Image).to receive(:new).and_return(image) - allow(image).to receive(:images).and_return(images) - - volume = double(Fog::OpenStack::Volume) - allow(volume).to receive(:volumes).and_return(volumes) - allow(volume).to receive(:snapshots).and_return(snapshots) - allow(Fog::OpenStack::Volume).to receive(:new).and_return(volume) - - network = double(Fog::OpenStack::Network) - allow(network).to receive(:security_groups).and_return(security_groups) - allow(Fog::OpenStack::Network).to receive(:new).and_return(network) - - compute = double(Fog::OpenStack::Compute) - - allow(compute).to receive(:servers).and_return(servers) - allow(compute).to receive(:flavors).and_return(flavors) - allow(compute).to receive(:key_pairs).and_return(key_pairs) - - allow(Fog::OpenStack::Compute).to receive(:new).and_return(compute) - - fog = Struct - .new(:compute, :network, :image, :volume) - .new(compute, network, image, volume) - - yield(fog) if block_given? - - Bosh::OpenStackCloud::Cloud.new(options || mock_cloud_options['properties'], cpi_api_version) -end - -def mock_glance(options = nil) - cloud = mock_cloud(options) - - image = double(Fog::OpenStack::Image, images: double('images')) - allow(cloud.instance_variable_get('@openstack')).to receive(:image).and_return(image) - allow(image).to receive(:class).and_return(Fog::OpenStack::Image) - - yield image if block_given? - - cloud -end - -def mock_sec_groups(receiver, sec_groups = []) - allow(receiver).to receive(:security_groups).and_return(sec_groups) - allow(sec_groups).to receive(:all).and_return(sec_groups) -end - -def dynamic_network_spec - { - 'type' => 'dynamic', - 'cloud_properties' => { - 'security_groups' => %w[default], - }, - 'use_dhcp' => true, - } -end - -def manual_network_spec(net_id: 'net', ip: '0.0.0.0', default: nil, overwrites: {}) - { - 'ip' => ip, - 'default' => default, - 'cloud_properties' => { - 'security_groups' => %w[default], - 'net_id' => net_id, - }, - 'use_dhcp' => true, - }.merge(overwrites) -end - -def manual_network_without_netid_spec - { - 'cloud_properties' => { - 'security_groups' => %w[default], - }, - } -end - -def dynamic_network_with_netid_spec - { - 'type' => 'dynamic', - 'cloud_properties' => { - 'security_groups' => %w[default], - 'net_id' => 'net', - }, - } -end - -def vip_network_spec - { - 'type' => 'vip', - 'ip' => '10.0.0.1', - } -end - -def combined_network_spec - { - 'network_a' => dynamic_network_spec, - 'network_b' => vip_network_spec, - } -end - -def resource_pool_spec - { - 'key_name' => 'test_key', - 'availability_zone' => 'foobar-1a', - 'instance_type' => 'm1.tiny', - } -end - -def resource_pool_spec_with_root_disk - { - 'key_name' => 'test_key', - 'availability_zone' => 'foobar-1a', - 'instance_type' => 'm1.tiny', - 'root_disk' => { 'size' => 10_240 }, - } -end - -def resource_pool_spec_with_0_root_disk - { - 'key_name' => 'test_key', - 'availability_zone' => 'foobar-1a', - 'instance_type' => 'm1.tiny', - 'root_disk' => { 'size' => 0 }, - } -end - -RSpec.configure do |config| - config.before(:each) { allow(Bosh::Clouds::Config).to receive(:logger).and_return(double.as_null_object) } -end - -class LifecycleHelper - extend WebMock::API - - def self.get_config(key, default = :none) - env_file = ENV['LIFECYCLE_ENV_FILE'] - env_name = ENV['LIFECYCLE_ENV_NAME'] - env_key = "BOSH_OPENSTACK_#{key.to_s.upcase}" - - value = if env_file - config = load_config_from_file(env_file, env_name) - config[key.to_s] - else - ENV[env_key] - end - - value_empty = value.to_s.empty? - if value_empty && default == :none - raise("Missing #{key}/#{env_key}; use LIFECYCLE_ENV_FILE=file.yml and LIFECYCLE_ENV_NAME=xxx or set in ENV") - end - - value_empty ? default : value - end - - def self.load_config_from_file(env_file, env_name) - @configs ||= YAML.load_file(env_file) - config = - if env_name - raise "no such env #{env_name} in #{env_file} (available: #{@configs.keys.sort.join(', ')})" unless @configs[env_name] - @configs[env_name] - else - @configs - end - config - end -end - -def str_to_bool(string) - string == 'true' -end diff --git a/src/bosh_openstack_cpi/spec/unit/attach_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/attach_disk_spec.rb deleted file mode 100644 index 3884377d5..000000000 --- a/src/bosh_openstack_cpi/spec/unit/attach_disk_spec.rb +++ /dev/null @@ -1,223 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - let(:server_metadata) { [] } - let(:server) { double('server', id: 'i-test', name: 'i-test', flavor: { 'id' => 'f-test' }, metadata: server_metadata) } - let(:volume) { double('volume', id: 'v-foobar') } - let(:flavor) { double('flavor', id: 'f-test', ephemeral: 10, swap: '') } - let(:cpi_api_version) { 1 } - let(:cloud) do - mock_cloud(cloud_options['properties'], cpi_api_version) do |fog| - expect(fog.compute.servers).to receive(:get).with('i-test').and_return(server) - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - expect(fog.compute.flavors).to receive(:find).and_return(flavor) - end - end - let(:cloud_options) { mock_cloud_options } - - before(:each) do - allow(server.metadata).to receive(:get).with(:registry_key).and_return(double('metadatum', 'value' => 'i-test')) - @registry = mock_registry - end - - it 'attaches an OpenStack volume to a server' do - volume_attachments = [] - attachment = double('attachment', device: '/dev/sdc') - - expect(server).to receive(:volume_attachments).and_return(volume_attachments) - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdc').and_return(attachment) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :'in-use') - - old_settings = { 'foo' => 'bar' } - new_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-foobar' => '/dev/sdc', - }, - }, - } - - expect(@registry).to receive(:read_settings).with('i-test').and_return(old_settings) - expect(@registry).to receive(:update_settings).with('i-test', new_settings) - - cloud.attach_disk('i-test', 'v-foobar') - end - - it 'picks available device name' do - volume_attachments = [{ 'volumeId' => 'v-c', 'device' => '/dev/vdc' }, - { 'volumeId' => 'v-d', 'device' => '/dev/xvdd' }] - attachment = double('attachment', device: '/dev/sdd') - - expect(server).to receive(:volume_attachments).and_return(volume_attachments) - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sde').and_return(attachment) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :'in-use') - - old_settings = { 'foo' => 'bar' } - new_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-foobar' => '/dev/sde', - }, - }, - } - - expect(@registry).to receive(:read_settings).with('i-test').and_return(old_settings) - expect(@registry).to receive(:update_settings).with('i-test', new_settings) - - cloud.attach_disk('i-test', 'v-foobar') - end - - it 'raises an error when sdc..sdz are all reserved' do - volume_attachments = ('c'..'z').each_with_object([]) do |char, array| - array << { 'volumeId' => "v-#{char}", 'device' => "/dev/sd#{char}" } - end - - expect(server).to receive(:volume_attachments).and_return(volume_attachments) - - expect { - cloud.attach_disk('i-test', 'v-foobar') - }.to raise_error(Bosh::Clouds::CloudError, /too many disks attached/) - end - - it 'bypasses the attaching process when volume is already attached to a server' do - volume_attachments = [{ 'volumeId' => 'v-foobar', 'device' => '/dev/sdc' }] - - cloud = mock_cloud do |fog| - expect(fog.compute.servers).to receive(:get).with('i-test').and_return(server) - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - end - - expect(server).to receive(:volume_attachments).and_return(volume_attachments) - expect(volume).not_to receive(:attach) - - old_settings = { 'foo' => 'bar' } - new_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-foobar' => '/dev/sdc', - }, - }, - } - - expect(@registry).to receive(:read_settings).with('i-test').and_return(old_settings) - expect(@registry).to receive(:update_settings).with('i-test', new_settings) - - cloud.attach_disk('i-test', 'v-foobar') - end - - context 'first device name letter' do - before do - allow(server).to receive(:volume_attachments).and_return([]) - allow(cloud.openstack).to receive(:wait_resource) - allow(cloud).to receive(:update_agent_settings) - end - subject(:attach_disk) { cloud.attach_disk('i-test', 'v-foobar') } - - let(:flavor) { double('flavor', id: 'f-test', ephemeral: 0, swap: '') } - - context 'when there is no ephemeral, swap disk and config drive' do - it 'return letter b' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdb') - attach_disk - end - end - - context 'when there is ephemeral disk' do - let(:flavor) { double('flavor', id: 'f-test', ephemeral: 1024, swap: '') } - - it 'return letter c' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdc') - attach_disk - end - end - - context 'when there is swap disk' do - let(:flavor) { double('flavor', id: 'f-test', ephemeral: 0, swap: 200) } - - it 'return letter c' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdc') - attach_disk - end - end - - context 'when config_drive is set as disk' do - let(:cloud_options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['config_drive'] = 'disk' - cloud_options - end - - it 'returns letter c' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdc') - attach_disk - end - end - - context 'when there is ephemeral and swap disk' do - let(:flavor) { double('flavor', id: 'f-test', ephemeral: 1024, swap: 200) } - - it 'returns letter d' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdd') - attach_disk - end - end - - context 'when there is ephemeral, swap disk and config drive is disk' do - let(:flavor) { double('flavor', id: 'f-test', ephemeral: 1024, swap: 200) } - let(:cloud_options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['config_drive'] = 'disk' - cloud_options - end - - it 'returns letter e' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sde') - attach_disk - end - end - - context 'when server flavor is not found' do - let(:flavor) { nil } - - it 'returns letter b' do - expect(server).to receive(:attach_volume).with(volume.id, '/dev/sdb') - attach_disk - end - end - end - - context 'when cpi api verion is 1' do - let(:cpi_api_version) { 1 } - - before do - allow(server).to receive(:volume_attachments).and_return([]) - allow(cloud.openstack).to receive(:wait_resource) - allow(cloud).to receive(:update_agent_settings) - allow(server).to receive(:attach_volume) - end - - it 'does not return a disk hint' do - disk_hint = cloud.attach_disk('i-test', 'v-foobar') - expect(disk_hint).to eq(nil) - end - end - - context 'when cpi api version is 2' do - let(:cpi_api_version) { 2 } - - before do - allow(server).to receive(:volume_attachments).and_return([]) - allow(cloud.openstack).to receive(:wait_resource) - allow(cloud).to receive(:update_agent_settings) - allow(server).to receive(:attach_volume) - end - - it 'returns a disk hint' do - disk_hint = cloud.attach_disk('i-test', 'v-foobar') - expect(disk_hint).to eq('/dev/sdc') - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/availability_zone_provider_spec.rb b/src/bosh_openstack_cpi/spec/unit/availability_zone_provider_spec.rb deleted file mode 100644 index d346dcfda..000000000 --- a/src/bosh_openstack_cpi/spec/unit/availability_zone_provider_spec.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::AvailabilityZoneProvider do - let(:foo_volume) { double('foo_volume') } - let(:bar_volume) { double('bar_volume') } - let(:volumes) { double('volumes') } - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack) } - let(:compute) { double(Fog::OpenStack::Compute) } - let(:volume) { double(Fog::Volume) } - let(:az_provider) { Bosh::OpenStackCloud::AvailabilityZoneProvider.new(openstack, ignore_server_az) } - - before do - allow(openstack).to receive(:compute).and_return(compute) - allow(openstack).to receive(:volume).and_return(volume) - allow(openstack).to receive(:with_openstack) { |&block| block.call } - allow(bar_volume).to receive(:id).and_return('bar_id') - allow(foo_volume).to receive(:id).and_return('foo_id') - allow(foo_volume).to receive(:availability_zone).and_return('west_az') - allow(volumes).to receive(:get).with('foo_id').and_return(foo_volume) - allow(volumes).to receive(:get).with('bar_id').and_return(bar_volume) - allow(volume).to receive(:volumes).and_return(volumes) - end - - describe '.use_multiple_azs?' do - let(:ignore_server_az) { true } - let(:multiple_azs_cloud_properties) { { 'availability_zones' => %w[]} } - let(:single_az_cloud_properties) { { 'availability_zone' => '' } } - let(:invalid_cloud_properties) { { 'availability_zone' => '', 'availability_zones' => [] } } - let(:empty_cloud_properties) { {} } - - it 'returns true if list is set' do - expect(az_provider.use_multiple_azs?(multiple_azs_cloud_properties)).to be_truthy - end - - it 'returns false if single_az is set' do - expect(az_provider.use_multiple_azs?(single_az_cloud_properties)).to be_falsey - end - - it 'should return false if neither is given' do - expect(az_provider.use_multiple_azs?(empty_cloud_properties)).to be_falsey - end - - it 'raises an exception if both are configured' do - expect { - az_provider.use_multiple_azs?(invalid_cloud_properties) - }.to raise_error(Bosh::Clouds::CloudError, 'Invalid cloud_properties: only one property of "availability_zone" and "availability_zones" allowed.') - end - - context 'when ignore_server is false' do - let(:ignore_server_az) { false } - let(:cloud_properties) { { } } - - it 'raises an error if az list is given and ignore_server_availability_zone is false' do - expect { - az_provider.use_multiple_azs?(multiple_azs_cloud_properties) - }.to raise_error(Bosh::Clouds::CloudError, 'Cannot use multiple azs without openstack.ignore_server_availability_zone') - end - end - - # end - end - - context 'when the server availability zone of the server must be the same as the disk' do - let(:ignore_server_az) { false } - - context 'when the volume IDs are present' do - context 'when az of volume is empty string' do - before do - allow(bar_volume).to receive(:availability_zone).and_return('') - end - - it 'returns nil' do - expect(az_provider.select(['bar_id'], nil)).to be_nil - end - end - - context 'when az of volume is nil' do - before do - allow(bar_volume).to receive(:availability_zone).and_return(nil) - end - - it 'returns nil' do - expect(az_provider.select(['bar_id'], nil)).to be_nil - end - end - - context 'when the volumes and resource pool are all from the same availability zone' do - before do - expect(bar_volume).to receive(:availability_zone).and_return('west_az') - end - - it "should return the disk's availability zone" do - selected_availability_zone = az_provider.select(%w[foo_id bar_id], 'west_az') - expect(selected_availability_zone).to eq('west_az') - end - end - - context 'when the disks are from different AZs and no resource pool AZ is provided' do - before do - allow(bar_volume).to receive(:availability_zone).and_return('east_az') - end - - it 'should raise an error' do - expect { - az_provider.select(%w[foo_id bar_id], nil) - }.to raise_error Bosh::Clouds::CloudError, "can't use multiple availability zones: VM is created in default AZ, disk 'foo_id' is in AZ 'west_az', disk 'bar_id' is in AZ 'east_az'. Enable 'openstack.ignore_server_availability_zone' to allow VMs and disks to be in different AZs, or use the same AZ for both." - end - end - - context 'when the disks are from the same AZ and no resource pool AZ is provided' do - before do - expect(bar_volume).to receive(:availability_zone).and_return('west_az') - end - - it 'should select the common disk AZ' do - selected_availability_zone = az_provider.select(%w[foo_id bar_id], nil) - expect(selected_availability_zone).to eq('west_az') - end - end - - context 'when there is a volume in a different AZ from other volumes or the resource pool AZ' do - before do - allow(bar_volume).to receive(:availability_zone).and_return('east_az') - end - - it 'should raise an error' do - expect { - az_provider.select(%w[foo_id bar_id], 'west_az') - }.to raise_error Bosh::Clouds::CloudError, "can't use multiple availability zones: VM is created in AZ 'west_az', disk 'foo_id' is in AZ 'west_az', disk 'bar_id' is in AZ 'east_az'. Enable 'openstack.ignore_server_availability_zone' to allow VMs and disks to be in different AZs, or use the same AZ for both." - end - end - - context 'when the disk AZs do not match the resource pool AZ' do - before do - allow(bar_volume).to receive(:availability_zone).and_return('west_az') - end - - it 'should raise an error' do - expect { - az_provider.select(%w[foo_id bar_id], 'south_az') - }.to raise_error Bosh::Clouds::CloudError, "can't use multiple availability zones: VM is created in AZ 'south_az', disk 'foo_id' is in AZ 'west_az', disk 'bar_id' is in AZ 'west_az'. Enable 'openstack.ignore_server_availability_zone' to allow VMs and disks to be in different AZs, or use the same AZ for both." - end - end - - context 'when all AZs provided are mismatched' do - before do - allow(bar_volume).to receive(:availability_zone).and_return('east_az') - end - - it 'should raise an error' do - expect { - az_provider.select(%w[foo_id bar_id], 'south_az') - }.to raise_error Bosh::Clouds::CloudError, "can't use multiple availability zones: VM is created in AZ 'south_az', disk 'foo_id' is in AZ 'west_az', disk 'bar_id' is in AZ 'east_az'. Enable 'openstack.ignore_server_availability_zone' to allow VMs and disks to be in different AZs, or use the same AZ for both." - end - end - - context 'when there are no disks IDs' do - it 'should return the resource pool AZ value' do - expect(az_provider.select([], nil)).to eq nil - expect(az_provider.select([], 'north_az')).to eq('north_az') - - expect(az_provider.select(nil, 'north_az')).to eq('north_az') - end - end - end - - context '.select_azs' do - context 'when the server availability zone of the server can be different from the disk' do - let(:availability_zones) { double } - let(:cloud_properties) { { 'availability_zones' => availability_zones } } - - it 'should return the multiple availability zones' do - allow(availability_zones).to receive(:shuffle).and_return(%w[multiple_id multiple_id2]) - selected_availability_zones = az_provider.select_azs(cloud_properties) - - expect(availability_zones).to have_received(:shuffle) - expect(selected_availability_zones).to include('multiple_id', 'multiple_id2') - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/bin/openstack_cpi_spec.rb b/src/bosh_openstack_cpi/spec/unit/bin/openstack_cpi_spec.rb deleted file mode 100644 index 5cee00311..000000000 --- a/src/bosh_openstack_cpi/spec/unit/bin/openstack_cpi_spec.rb +++ /dev/null @@ -1,144 +0,0 @@ -require 'spec_helper' -require 'tempfile' -require 'English' - -describe 'the openstack_cpi executable' do - describe '#set_vm_metadata' do - it 'will not evaluate anything that causes an exception and will return the proper message to stdout' do - config_file = create_config_file('0.0.0.0:5000/v2.0') - command_file = create_cpi_command_file('set_vm_metadata', [1, {}]) - - stdoutput = execute_cpi_command(command_file, config_file) - status = $CHILD_STATUS.exitstatus - - expect(status).to eq(0) - result = JSON.parse(stdoutput) - - expect(result.keys).to eq(%w[result error log]) - - expect(result['result']).to be_nil - - expect(result['error']).to eq( - 'type' => 'Unknown', - 'message' => 'bad URI (is not URI?): "0.0.0.0:5000"', - 'ok_to_retry' => false, - ) - - expect(result['log']).to include('backtrace') - end - - it 'will fail if registry.endpoint is not provided' do - config_file = create_config_file('http://0.0.0.0:5000/v2.0', nil) - command_file = create_cpi_command_file('set_vm_metadata', [1, {}]) - - stdoutput = execute_cpi_command(command_file, config_file) - - result = JSON.parse(stdoutput) - - expect(result['result']).to be_nil - expect(result['error']['type']).to eq('InvalidCall') - expect(result['error']['message']).to match(/# { endpoint => Missing key } }/) - expect(result['error']['ok_to_retry']).to eq(false) - end - - it 'will return an appropriate error message when passed an invalid config file' do - config_file = Tempfile.new('cloud_properties.yml') - File.open(config_file, 'w') do |file| - file.write({}.to_yaml) - end - - command_file = create_cpi_command_file('set_vm_metadata', [1, {}]) - - stdoutput = execute_cpi_command(command_file, config_file) - status = $CHILD_STATUS.exitstatus - - expect(status).to eq(0) - result = JSON.parse(stdoutput) - - expect(result.keys).to eq(%w[result error log]) - - expect(result['result']).to be_nil - - expect(result['error']).to eq( - 'type' => 'Unknown', - 'message' => 'Could not find cloud properties in the configuration', - 'ok_to_retry' => false, - ) - - expect(result['log']).to include('backtrace') - end - end - - describe '#calculate_vm_cloud_properties' do - it 'raises an error if calculate_vm_cloud_properties fields are missing' do - config_file = create_config_file('0.0.0.0:5000/v2.0') - command_file = create_cpi_command_file('calculate_vm_cloud_properties', [{}]) - - stdoutput = execute_cpi_command(command_file, config_file) - status = $CHILD_STATUS.exitstatus - - expect(status).to eq(0) - result = JSON.parse(stdoutput) - - expect(result.keys).to eq(%w[result error log]) - - expect(result['result']).to be_nil - - expect(result['error']).to eq( - 'type' => 'Unknown', - 'message' => "Missing VM cloud properties: 'cpu', 'ram', 'ephemeral_disk_size'", - 'ok_to_retry' => false, - ) - - expect(result['log']).to include('backtrace') - end - end -end - -def create_config_file(auth_url = 'http://0.0.0.0:5000/v2.0', registry_endpoint = '0.0.0.0:5000') - config_file = Tempfile.new('cloud_properties.yml') - File.open(config_file, 'w') do |file| - file.write( - { - 'cloud' => { - 'properties' => { - 'openstack' => { - 'auth_url' => auth_url, - 'username' => 'openstack-user', - 'api_key' => 'openstack-password', - 'tenant' => 'dev', - 'region' => 'west-coast', - 'endpoint_type' => 'publicURL', - 'state_timeout' => 300, - 'boot_from_volume' => false, - 'stemcell_public_visibility' => false, - 'connection_options' => {}, - 'default_key_name' => nil, - 'default_security_groups' => nil, - 'wait_resource_poll_interval' => 5, - 'config_drive' => 'disk', - }, - 'registry' => { - 'endpoint' => registry_endpoint, - 'user' => 'registry-user', - 'password' => 'registry-password', - }, - }, - }, - }.to_yaml, - ) - end - config_file -end - -def create_cpi_command_file(method_name, method_args) - command_file = Tempfile.new('command.json') - File.open(command_file, 'w') do |file| - file.write({ 'method' => method_name, 'arguments' => method_args, 'context' => { 'director_uuid' => 'abc123' } }.to_json) - end - command_file -end - -def execute_cpi_command(command_file, config_file) - `bin/openstack_cpi #{config_file.path} < #{command_file.path} 2> /dev/null` -end diff --git a/src/bosh_openstack_cpi/spec/unit/bosh_release/jobs/cpi/templates/cpi.json.erb_spec.rb b/src/bosh_openstack_cpi/spec/unit/bosh_release/jobs/cpi/templates/cpi.json.erb_spec.rb deleted file mode 100644 index 1e1347557..000000000 --- a/src/bosh_openstack_cpi/spec/unit/bosh_release/jobs/cpi/templates/cpi.json.erb_spec.rb +++ /dev/null @@ -1,175 +0,0 @@ -require 'spec_helper' -require 'json' -require 'yaml' -require 'bosh/template/test' - -describe 'cpi.json.erb' do - let(:cpi_v1_json) { JSON.parse(template.render(manifest_cpi_v1)) } - let(:cpi_v2_json) { JSON.parse(template.render(manifest_cpi_v2)) } - - let(:release) { Bosh::Template::Test::ReleaseDir.new(File.join(File.dirname(__FILE__), '../../../../../../../../')) } - let(:job) { release.job('openstack_cpi') } - let(:template) { job.template('config/cpi.json') } - - let(:manifest_cpi_v1) do - { - 'openstack' => { - 'auth_url' => 'openstack.auth_url', - 'username' => 'openstack.username', - 'api_key' => 'openstack.api_key', - 'tenant' => 'openstack.tenant', - 'default_key_name' => 'openstack.default_key_name', - 'default_security_groups' => 'openstack.default_security_groups', - 'wait_resource_poll_interval' => 'openstack.wait_resource_poll_interval', - 'human_readable_vm_names' => false, - 'ignore_server_availability_zone' => 'openstack.ignore_server_availability_zone', - }, - 'ntp' => [], - 'registry' => { - 'username' => 'registry.username', - 'password' => 'registry.password', - 'host' => 'registry.host', - 'endpoint' => 'http://registry.host:25777', - }, - 'nats' => { - 'address' => 'nats_address.example.com', - 'password' => 'nats-password', - 'user' => 'nats-user', - }, - } - end - - let(:manifest_cpi_v2) do - { - 'openstack' => { - 'auth_url' => 'openstack.auth_url', - 'username' => 'openstack.username', - 'api_key' => 'openstack.api_key', - 'tenant' => 'openstack.tenant', - 'default_key_name' => 'openstack.default_key_name', - 'default_security_groups' => 'openstack.default_security_groups', - 'wait_resource_poll_interval' => 'openstack.wait_resource_poll_interval', - 'human_readable_vm_names' => false, - 'ignore_server_availability_zone' => 'openstack.ignore_server_availability_zone', - }, - } - end - - it 'is able to render the erb given most basic manifest properties' do - expect(cpi_v2_json).to eq( - 'cloud' => { - 'plugin' => 'openstack', - 'properties' => { - 'openstack' => { - 'api_key' => 'openstack.api_key', - 'auth_url' => 'openstack.auth_url', - 'boot_from_volume' => false, - 'default_key_name' => 'openstack.default_key_name', - 'default_security_groups' => 'openstack.default_security_groups', - 'endpoint_type' => 'publicURL', - 'ignore_server_availability_zone' => 'openstack.ignore_server_availability_zone', - 'state_timeout' => 300, - 'stemcell_public_visibility' => false, - 'tenant' => 'openstack.tenant', - 'use_dhcp' => true, - 'username' => 'openstack.username', - 'wait_resource_poll_interval' => 'openstack.wait_resource_poll_interval', - 'human_readable_vm_names' => false, - 'use_nova_networking' => false, - 'default_volume_type' => nil, - }, - }, - }, - ) - end - - context 'when using human readable VM names' do - it 'template rendering succeeds' do - manifest_cpi_v2['openstack']['human_readable_vm_names'] = true - - expect(cpi_v2_json['cloud']['properties']['openstack']['human_readable_vm_names']).to be true - end - end - - context 'when registry is configured for bosh-init' do - it 'concatinates host and port as registry endpoint and template rendering succeeds' do - manifest_cpi_v2['registry'] = {} - manifest_cpi_v2['registry']['username'] = 'registry.username' - manifest_cpi_v2['registry']['password'] = 'registry.password' - manifest_cpi_v2['registry']['host'] = '127.0.0.1' - manifest_cpi_v2['registry']['port'] = 6901 - - expect(cpi_v2_json['cloud']['properties']['registry']['endpoint']).to eq('http://127.0.0.1:6901') - end - - it 'it raises an error if some registry properties are missing' do - manifest_cpi_v2['registry'] = {} - manifest_cpi_v2['registry']['username'] = '' - manifest_cpi_v2['registry']['password'] = '' - manifest_cpi_v2['registry']['endpoint'] = 'http://registry.host:25777' - manifest_cpi_v2['registry']['port'] = 6901 - - expect { cpi_v2_json }.to raise_error Bosh::Template::UnknownProperty - end - - it 'it does not raise an error if no registry properties are set' do - manifest_cpi_v2['registry'] = {} - - expect { cpi_v2_json }.to_not raise_error - expect(cpi_v2_json['cloud']['properties']['registry']).to eq(nil) - end - end - - describe 'when anti-affinity is configured' do - [false, true].each do |prop| - context "when anti-affinity is set to #{prop}" do - it 'errors to inform the user this is no longer supported' do - manifest_cpi_v1['openstack']['enable_auto_anti_affinity'] = prop - - expect { cpi_v1_json }.to raise_error RuntimeError, - "Property 'enable_auto_anti_affinity' is no longer supported. Please remove it from your configuration." - end - end - end - end - - context 'when cpi api v1' do - it 'is able to render the erb given most basic manifest properties' do - expect(cpi_v1_json).to eq( - 'cloud' => { - 'plugin' => 'openstack', - 'properties' => { - 'agent' => { - 'mbus' => 'nats://nats-user:nats-password@nats_address.example.com:4222', - 'ntp' => [], - }, - 'openstack' => { - 'api_key' => 'openstack.api_key', - 'auth_url' => 'openstack.auth_url', - 'boot_from_volume' => false, - 'default_key_name' => 'openstack.default_key_name', - 'default_security_groups' => 'openstack.default_security_groups', - 'endpoint_type' => 'publicURL', - 'ignore_server_availability_zone' => 'openstack.ignore_server_availability_zone', - 'state_timeout' => 300, - 'stemcell_public_visibility' => false, - 'tenant' => 'openstack.tenant', - 'use_dhcp' => true, - 'username' => 'openstack.username', - 'wait_resource_poll_interval' => 'openstack.wait_resource_poll_interval', - 'human_readable_vm_names' => false, - 'use_nova_networking' => false, - 'default_volume_type' => nil, - }, - 'registry' => { - 'address' => 'registry.host', - 'endpoint' => 'http://registry.host:25777', - 'password' => 'registry.password', - 'user' => 'registry.username', - }, - } - } - ) - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/cloud_spec.rb b/src/bosh_openstack_cpi/spec/unit/cloud_spec.rb deleted file mode 100644 index aae6d6dcf..000000000 --- a/src/bosh_openstack_cpi/spec/unit/cloud_spec.rb +++ /dev/null @@ -1,370 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - let(:default_connection_options) { - { 'instrumentor' => Bosh::OpenStackCloud::ExconLoggingInstrumentor } - } - let(:cpi_api_version) { 1 } - - describe :new do - let(:cloud_options) { mock_cloud_options } - let(:cloud_options_stemcell_v2) do - cloud_options['properties']['openstack']['vm'] = { - 'stemcell' => { - 'api_version' => 2, - }, - } - cloud_options - end - - before { - expect(Fog::OpenStack::Compute).to_not receive(:new) - expect(Fog::OpenStack::Image).to_not receive(:new) - expect(Fog::OpenStack::Volume).to_not receive(:new) - expect(Fog::Network).to_not receive(:new) - } - - context 'when CPI API v2 is called' do - let(:cpi_api_version) { 2 } - - context 'when stemcell API v1 is used' do - it 'creates a RegistryClient' do - registry = Bosh::OpenStackCloud::Cloud.new(cloud_options['properties'], cpi_api_version).registry - expect(registry).to be_instance_of(Bosh::Cpi::RegistryClient) - end - end - - context 'when stemcell API v2 is used' do - it 'creates a NoopRegistry' do - registry = Bosh::OpenStackCloud::Cloud.new(cloud_options_stemcell_v2['properties'], cpi_api_version).registry - expect(registry).to be_instance_of(Bosh::OpenStackCloud::NoopRegistry) - end - end - end - - context 'when CPI API v1 is called' do - context 'when stemcell API v1 is used' do - it 'creates a RegistryClient' do - registry = Bosh::OpenStackCloud::Cloud.new(cloud_options['properties'], cpi_api_version).registry - expect(registry).to be_instance_of(Bosh::Cpi::RegistryClient) - end - end - - context 'when stemcell API v2 is used' do - it 'creates a RegistryClient' do - registry = Bosh::OpenStackCloud::Cloud.new(cloud_options_stemcell_v2['properties'], cpi_api_version).registry - expect(registry).to be_instance_of(Bosh::Cpi::RegistryClient) - end - end - - describe 'validation' do - let(:options) do - { - 'openstack' => { - 'username' => 'fake-username', - 'api_key' => 'fake-api-key', - }, - 'registry' => { - 'endpoint' => 'fake-registry', - 'user' => 'fake-user', - 'password' => 'fake-password', - }, - } - end - subject { Bosh::OpenStackCloud::Cloud.new(options, cpi_api_version) } - - context 'when keystone V2 API is used' do - before do - options['openstack']['auth_url'] = 'http://fake-auth-url/v2.0' - options['openstack']['tenant'] = 'fake-tenant' - end - - it 'does not raise an error' do - expect { subject }.to_not raise_error - end - - context 'when connection_options are specified' do - it 'expects connection_options to be a hash' do - options['openstack']['connection_options'] = { 'any-key' => 'any-value' } - - expect { subject }.to_not raise_error - end - - it 'raises an error if connection_options is not a Hash' do - options['openstack']['connection_options'] = 'connection_options' - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - end - - context 'when boot_from_volume is specified' do - it 'expects boot_from_volume to be a boolean' do - options['openstack']['boot_from_volume'] = true - - expect { subject }.to_not raise_error - end - - it 'raises an error if boot_from_volume is not a boolean' do - options['openstack']['boot_from_volume'] = 'boot_from_volume' - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - end - - context 'config_drive' do - it 'accepts cdrom as a value' do - options['openstack']['config_drive'] = 'cdrom' - expect { subject }.to_not raise_error - end - - it 'accepts disk as a value' do - options['openstack']['config_drive'] = 'disk' - expect { subject }.to_not raise_error - end - - it 'accepts nil as a value' do - options['openstack']['config_drive'] = nil - expect { subject }.to_not raise_error - end - - it 'raises an error if config_drive is not cdrom or disk or nil' do - options['openstack']['config_drive'] = 'incorrect-value' - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - end - end - - context 'when keystone V3 API is used' do - before do - options['openstack']['auth_url'] = 'http://127.0.0.1:5000/v3' - end - - it 'raises an error when no project or project_id is specified' do - options['openstack']['domain'] = 'fake_domain' - expect { subject } - .to raise_error(ArgumentError, - Regexp.new('Invalid OpenStack cloud properties: ' \ - '# { project => Missing key } }')) - end - - it 'raises an error when no domain is specified' do - options['openstack']['project'] = 'fake_project' - expect { subject } - .to raise_error(ArgumentError, - Regexp.new('Invalid OpenStack cloud properties: ' \ - '# { domain => Missing key } }')) - end - - context 'when project and domain are specified' do - before do - options['openstack']['project'] = 'fake_project' - options['openstack']['domain'] = 'fake_domain' - end - - it 'does not raise an error' do - expect { subject }.to_not raise_error - end - end - - context 'when project_id and domain are specified' do - before do - options['openstack']['project_id'] = 'fake_project_id' - options['openstack']['domain'] = 'fake_domain' - end - - it 'does not raise an error' do - expect { subject }.to_not raise_error - end - end - - context 'when project and user_domain_name and project_domain_name are specified' do - before do - options['openstack']['project'] = 'fake_project' - options['openstack']['user_domain_name'] = 'fake_user_domain' - options['openstack']['project_domain_name'] = 'fake_project_domain' - end - - it 'does not raise an error' do - expect { subject }.to_not raise_error - end - end - - context 'when checking authentication' do - before do - options['openstack']['auth_url'] = 'http://fake-auth-url/v2.0' - options['openstack']['tenant'] = 'fake-tenant' - end - - it 'raises ArgumentError if there are no authentication credentials provided' do - options['openstack']['username'] = nil - options['openstack']['api_key'] = nil - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - - it 'raises ArgumentError if only api_key is provided' do - options['openstack']['api_key'] = nil - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - - it 'raises ArgumentError if only username is provided' do - options['openstack']['username'] = nil - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - - it 'raises ArgumentError if both username and application credential id are provided' do - options['openstack']['application_credential_id'] = 'fake-application-credential-id' - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - - it 'does not raise an error if application credential id and secret are provided' do - options['openstack']['api_key'] = nil - options['openstack']['username'] = nil - options['openstack']['application_credential_id'] = 'fake-application-credential-id' - options['openstack']['application_credential_secret'] = 'fake-application-credential-secret' - - expect { subject }.to_not raise_error - end - - it 'raises ArgumentError if only application credential id is provided' do - options['openstack']['api_key'] = nil - options['openstack']['username'] = nil - options['openstack']['application_credential_id'] = 'fake-application-credential-id' - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - - it 'raises ArgumentError if only application credential secret is provided' do - options['openstack']['api_key'] = nil - options['openstack']['username'] = nil - options['openstack']['application_credential_secret'] = 'fake-application-credential-secret' - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - - it 'raises ArgumentError if too many credentials are provided' do - options['openstack']['api_key'] = 'fake_api_key' - options['openstack']['username'] = 'fake_username' - options['openstack']['application_credential_id'] = 'fake-application-credential-id' - options['openstack']['application_credential_secret'] = 'fake-application-credential-secret' - - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - end - - end - - context 'when options are empty' do - let(:options) { Hash.new('options') } - - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - end - - context 'when options are not a Hash' do - let(:options) { 'this is a string' } - - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError, /Invalid OpenStack cloud properties/) - end - end - end - end - end - - describe :update_agent_settings do - let(:cloud_options) { mock_cloud_options } - let(:connection_options) { nil } - let(:merged_connection_options) { default_connection_options } - - let(:compute) { instance_double('Fog::OpenStack::Compute') } - before { allow(Fog::OpenStack::Compute).to receive(:new).and_return(compute) } - - let(:image) { instance_double('Fog::Image') } - before { allow(Fog::OpenStack::Image).to receive(:new).and_return(image) } - - context 'when server has no registry_key tag' do - it 'uses the server name as key' do - cpi = Bosh::OpenStackCloud::Cloud.new(cloud_options['properties'], cpi_api_version) - server = double('server', id: 'id', name: 'name', metadata: double('metadata')) - - allow(server.metadata).to receive(:get).with(:registry_key).and_return(nil) - - expect(cpi.registry).to receive(:read_settings).with('name') - expect(cpi.registry).to receive(:update_settings).with('name', anything) - - cpi.update_agent_settings(server) { - # intentionally empty - } - end - end - - context 'when server has a registry_key tag' do - it 'uses the registry_key tag value as key' do - cpi = Bosh::OpenStackCloud::Cloud.new(cloud_options['properties'], cpi_api_version) - server = double('server', id: 'id', name: 'name', metadata: double('metadata')) - - allow(server.metadata).to receive(:get).with(:registry_key).and_return(double('metadatum', 'value' => 'registry-tag-value')) - - expect(cpi.registry).to receive(:read_settings).with('registry-tag-value') - expect(cpi.registry).to receive(:update_settings).with('registry-tag-value', anything) - - cpi.update_agent_settings(server) { - # intentionally empty - } - end - end - - context 'when registry is used' do - it 'logs that settings get updated' do - cpi = Bosh::OpenStackCloud::Cloud.new(cloud_options['properties'], cpi_api_version) - server = double('server', id: 'id', name: 'name', metadata: double('metadata')) - allow(server.metadata).to receive(:get).with(:registry_key).and_return(nil) - allow(cpi.registry).to receive(:read_settings) - allow(cpi.registry).to receive(:update_settings) - - expect(cpi.logger).to receive(:info).with("Updating settings for server 'id' with registry key 'name'...") - - cpi.update_agent_settings(server) { - # intentionally empty - } - end - end - - context 'when registry is not used' do - let(:cpi_api_version) { 2 } - let(:cloud_options_stemcell_v2) do - cloud_options['properties']['openstack']['vm'] = { - 'stemcell' => { - 'api_version' => 2, - }, - } - cloud_options - end - it 'does not log anything' do - cpi = Bosh::OpenStackCloud::Cloud.new(cloud_options_stemcell_v2['properties'], cpi_api_version) - server = double('server', id: 'id', name: 'name', metadata: double('metadata')) - allow(server.metadata).to receive(:get).with(:registry_key).and_return(nil) - expect(cpi.logger).to_not receive(:info) - - cpi.update_agent_settings(server) { - # intentionally empty - } - end - end - end - - describe :info do - let(:cloud_options) { mock_cloud_options } - - it 'returns correct info' do - cpi = Bosh::OpenStackCloud::Cloud.new(cloud_options['properties'], cpi_api_version) - expect(cpi.info).to eq('api_version' => 2, 'stemcell_formats' => ['openstack-raw', 'openstack-qcow2', 'openstack-light']) - end - end - -end diff --git a/src/bosh_openstack_cpi/spec/unit/configure_networks_spec.rb b/src/bosh_openstack_cpi/spec/unit/configure_networks_spec.rb deleted file mode 100644 index d87acefa6..000000000 --- a/src/bosh_openstack_cpi/spec/unit/configure_networks_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - it 'forces recreation always' do - expect { - mock_cloud.configure_networks('i-test', 'net_a' => { 'type' => 'foo' }) - }.to raise_error(Bosh::Clouds::NotSupported, /network configuration change requires VM recreation/) - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/cpi_lambda_spec.rb b/src/bosh_openstack_cpi/spec/unit/cpi_lambda_spec.rb deleted file mode 100644 index 929e6bfb1..000000000 --- a/src/bosh_openstack_cpi/spec/unit/cpi_lambda_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::CpiLambda do - subject { described_class.create(cpi_config, cpi_log, ssl_ca_file, ca_cert_from_context) } - let(:cpi_config) { - { - 'cloud' => { - 'properties' => { - 'openstack' => { - 'key1' => 'value1', - 'key2' => 'value2', - }, - }, - }, - } - } - let(:ssl_ca_file) { 'feel-free-to-change' } - let(:cpi_log) { StringIO.new } - let(:ca_cert_from_context) { Tempfile.new('ca_cert').path } - - describe 'when creating a cloud' do - it 'passes parts of the cpi config to openstack' do - expect(Bosh::Clouds::Openstack).to receive(:new).with({ 'openstack' => cpi_config['cloud']['properties']['openstack'], - 'cpi_log' => cpi_log }, 1) - subject.call({}, 1) - end - - context 'if invalid cpi config is given' do - let(:cpi_config) { { 'empty' => 'config' } } - - it 'raises an error' do - expect { - subject.call({}, 1) - }.to raise_error(/Could not find cloud properties in the configuration/) - end - end - - context 'if using ca_certs in config' do - let(:cpi_config) do - { 'cloud' => { 'properties' => { 'openstack' => { 'connection_options' => { 'ca_cert' => 'xyz' } } } } } - end - - it 'sets ssl_ca_file that is passed and removes ca_certs' do - expect(Bosh::Clouds::Openstack).to receive(:new) - .with({ 'openstack' => { 'connection_options' => { 'ssl_ca_file' => ssl_ca_file } }, 'cpi_log' => cpi_log }, 1) - subject.call({}, 1) - end - end - - context 'if openstack properties are provided in the context' do - it 'merges the openstack properties' do - context = { - 'newkey' => 'newvalue', - 'newkey2' => 'newvalue2', - } - - expect(Bosh::Clouds::Openstack).to receive(:new).with({ 'openstack' => { 'key1' => 'value1', - 'key2' => 'value2', - 'newkey' => 'newvalue', - 'newkey2' => 'newvalue2' }, - 'cpi_log' => cpi_log }, 1) - subject.call(context, 1) - end - - it 'writes the given ca_cert to the disk and sets ssl_ca_file to its path' do - context = { - 'newkey' => 'newvalue', - 'connection_options' => { 'ca_cert' => 'xyz' }, - } - - expect(Bosh::Clouds::Openstack).to receive(:new) - .with({ 'openstack' => { 'newkey' => 'newvalue', - 'key1' => 'value1', - 'key2' => 'value2', - 'connection_options' => { 'ssl_ca_file' => ca_cert_from_context } }, - 'cpi_log' => cpi_log }, 1) - - subject.call(context, 1) - expect(File.read(ca_cert_from_context)).to eq('xyz') - end - - context 'when the context does not include a ca_cert' do - it 'does not write into the file' do - allow(Bosh::Clouds::Openstack).to receive(:new) - - subject.call({}, 1) - - expect(File.read(ca_cert_from_context)).to eq('') - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/create_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/create_disk_spec.rb deleted file mode 100644 index e8870d803..000000000 --- a/src/bosh_openstack_cpi/spec/unit/create_disk_spec.rb +++ /dev/null @@ -1,180 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - describe 'connecting to the OpenStack Volume Service' do - it 'connects when creating a volume' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 2, - } - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - allow(cloud).to receive(:generate_unique_name).and_return(unique_name) - allow(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - expect(Fog::OpenStack::Volume).to receive(:new) - expect(cloud.create_disk(2048, {})).to eq('v-foobar') - end - end - - it 'creates an OpenStack volume' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 2, - } - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - allow(cloud).to receive(:generate_unique_name).and_return(unique_name) - allow(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - expect(cloud.create_disk(2048, {})).to eq('v-foobar') - end - - it 'creates an OpenStack volume with the specified volume_type' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 2, - volume_type: 'foo', - } - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - expect(cloud.create_disk(2048, 'type' => 'foo')).to eq('v-foobar') - end - - it 'creates an OpenStack volume with the default volume_type' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 2, - volume_type: 'default-type', - } - volume = double('volume', id: 'v-foobar') - - global_properties = mock_cloud_options['properties'] - global_properties['openstack']['default_volume_type'] = 'default-type' - cloud = mock_cloud(global_properties) do |fog| - expect(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - expect(cloud.create_disk(2048, {})).to eq('v-foobar') - end - - it 'rounds up disk size' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 3, - } - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - cloud.create_disk(2049, {}) - end - - it 'check min disk size' do - expect { - mock_cloud.create_disk(100, {}) - }.to raise_error(Bosh::Clouds::CloudError, /Minimum disk size is 1 GiB/) - end - - it 'puts disk in the same AZ as a server' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 1, - availability_zone: 'foobar-land', - } - server = double('server', id: 'i-test', - availability_zone: 'foobar-land') - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - expect(fog.compute.servers).to receive(:get) - .with('i-test').and_return(server) - expect(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - cloud.create_disk(1024, {}, 'i-test') - end - - it 'does not put disk in the same AZ as a server if asked not to' do - unique_name = SecureRandom.uuid - disk_params = { - display_name: "volume-#{unique_name}", - display_description: '', - name: "volume-#{unique_name}", - description: '', - size: 1, - } - double('server', id: 'i-test', - availability_zone: 'foobar-land') - volume = double('volume', id: 'v-foobar') - - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['ignore_server_availability_zone'] = true - - cloud = mock_cloud(cloud_options['properties']) do |fog| - expect(fog.volume.volumes).to receive(:create) - .with(disk_params).and_return(volume) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - cloud.create_disk(1024, {}, 'i-test') - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/create_stemcell_spec.rb b/src/bosh_openstack_cpi/spec/unit/create_stemcell_spec.rb deleted file mode 100644 index 0ead5c70d..000000000 --- a/src/bosh_openstack_cpi/spec/unit/create_stemcell_spec.rb +++ /dev/null @@ -1,266 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - let(:image) { double('image', id: 'i-bar') } - let(:unique_name) { SecureRandom.uuid } - - before { @tmp_dir = Dir.mktmpdir } - - describe 'Image upload based flow' do - let(:cloud_options) { nil } - - context 'given a heavy stemcell' do - before do - @cloud = mock_glance(cloud_options) do |glance| - @glance = glance - end - end - - it 'creates an image and uploads data using a stemcell file' do - image_params = { - name: 'stemcell-name/x.y.z', - disk_format: 'qcow2', - container_format: 'bare', - visibility: 'private', - version: 'x.y.z', - } - - expect(@glance.images).to receive(:create).with(image_params).and_return(image) - allow(image).to receive(:reload).and_return({}) - allow(image).to receive(:status).and_return(:not_queued, :queued, :not_active, :active) - expect(@cloud.openstack).to receive(:sleep).with(3).twice - - expect(Dir).to receive(:mktmpdir).and_yield(@tmp_dir) - expect_any_instance_of(Bosh::OpenStackCloud::HeavyStemcellCreator).to receive(:unpack_image).with(@tmp_dir, '/tmp/foo') - - fake_file = double(File) - - expect(File).to receive(:open).with("#{@tmp_dir}/root.img", 'rb').and_return(fake_file) - - expect(image).to receive(:upload_data).with(fake_file) - - sc_id = @cloud.create_stemcell('/tmp/foo', - 'name' => 'stemcell-name', - 'version' => 'x.y.z', - 'container_format' => 'bare', - 'disk_format' => 'qcow2') - - expect(sc_id).to eq 'i-bar' - end - - it 'sets custom image properties from cloud_properties' do - image_params = { - name: 'stemcell-name/x.y.z', - disk_format: 'qcow2', - container_format: 'bare', - visibility: 'private', - version: 'x.y.z', - os_type: 'linux', - os_distro: 'ubuntu', - architecture: 'x86_64', - auto_disk_config: 'true', - } - - expect(@glance.images).to receive(:create).with(image_params).and_return(image) - - expect(Dir).to receive(:mktmpdir).and_yield(@tmp_dir) - expect_any_instance_of(Bosh::OpenStackCloud::HeavyStemcellCreator).to receive(:unpack_image).with(@tmp_dir, '/tmp/foo') - expect(@cloud.openstack).to receive(:wait_resource).with(image, :queued) - - fake_file = double(File) - - expect(File).to receive(:open).with("#{@tmp_dir}/root.img", 'rb').and_return(fake_file) - - expect(image).to receive(:upload_data).with(fake_file) - - expect(@cloud.openstack).to receive(:wait_resource).with(image, :active) - - sc_id = @cloud.create_stemcell('/tmp/foo', - 'name' => 'stemcell-name', - 'version' => 'x.y.z', - 'os_type' => 'linux', - 'os_distro' => 'ubuntu', - 'architecture' => 'x86_64', - 'auto_disk_config' => 'true', - 'foo' => 'bar', - 'container_format' => 'bare', - 'disk_format' => 'qcow2') - - expect(sc_id).to eq 'i-bar' - end - - it 'passes through whitelisted glance properties from cloud_properties to glance when making a stemcell' do - extra_properties = { - 'name' => 'stemcell-name', - 'version' => 'x.y.z', - 'os_type' => 'linux', - 'os_distro' => 'ubuntu', - 'architecture' => 'x86_64', - 'auto_disk_config' => 'true', - 'foo' => 'bar', - 'container_format' => 'bare', - 'disk_format' => 'qcow2', - 'hw_vif_model' => 'fake-hw_vif_model', - 'hypervisor' => 'fake-hypervisor_type', # hypervisor turns into hypervisor_type - } - - image_params = { - name: 'stemcell-name/x.y.z', - disk_format: 'qcow2', - container_format: 'bare', - visibility: 'private', - version: 'x.y.z', - os_type: 'linux', - os_distro: 'ubuntu', - architecture: 'x86_64', - auto_disk_config: 'true', - hw_vif_model: 'fake-hw_vif_model', - hypervisor_type: 'fake-hypervisor_type', - } - - expect(@glance.images).to receive(:create).with(image_params).and_return(image) - - allow(Dir).to receive(:mktmpdir).and_yield(@tmp_dir) - allow_any_instance_of(Bosh::OpenStackCloud::HeavyStemcellCreator).to receive(:unpack_image) - allow(@cloud.openstack).to receive(:wait_resource).with(image, :queued) - - fake_file = double(File) - expect(File).to receive(:open).with("#{@tmp_dir}/root.img", 'rb').and_return(fake_file) - expect(image).to receive(:upload_data).with(fake_file) - - allow(@cloud.openstack).to receive(:wait_resource).with(image, :active) - - @cloud.create_stemcell('/tmp/foo', extra_properties) - end - - it 'should throw an error for non existent root image in stemcell archive' do - result = Bosh::Exec::Result.new('cmd', 'output', 0) - expect(Bosh::Exec).to receive(:sh).and_return(result) - - allow(File).to receive(:exists?).and_return(false) - - expect { - @cloud.create_stemcell('/tmp/foo', - 'name' => 'stemcell-name', - 'version' => 'x.y.z', - 'container_format' => 'bare', - 'disk_format' => 'qcow2') - }.to raise_exception(Bosh::Clouds::CloudError, 'Root image is missing from stemcell archive') - end - - it 'should fail if cannot extract root image' do - result = Bosh::Exec::Result.new('cmd', 'output', 1) - expect(Bosh::Exec).to receive(:sh).and_return(result) - - expect(Dir).to receive(:mktmpdir).and_yield(@tmp_dir) - - expect { - @cloud.create_stemcell('/tmp/foo', - 'name' => 'stemcell-name', - 'version' => 'x.y.z', - 'container_format' => 'ami', - 'disk_format' => 'ami') - }.to raise_exception(Bosh::Clouds::CloudError, - 'Extracting stemcell root image failed. Check task debug log for details.') - end - - context 'stemcell_public_visibility is true' do - let(:cloud_options) do - cloud_options = mock_cloud_options['properties'] - cloud_options['openstack']['stemcell_public_visibility'] = true - cloud_options - end - - it 'sets stemcell visibility to public when required' do - image_params = { - name: 'stemcell-name/x.y.z', - disk_format: 'qcow2', - container_format: 'bare', - visibility: 'public', - version: 'x.y.z', - } - - expect(@glance.images).to receive(:create).with(image_params).and_return(image) - - expect(Dir).to receive(:mktmpdir).and_yield(@tmp_dir) - expect_any_instance_of(Bosh::OpenStackCloud::HeavyStemcellCreator).to receive(:unpack_image).with(@tmp_dir, '/tmp/foo') - expect(@cloud.openstack).to receive(:wait_resource).with(image, :queued) - - fake_file = double(File) - expect(File).to receive(:open).with("#{@tmp_dir}/root.img", 'rb').and_return(fake_file) - expect(image).to receive(:upload_data).with(fake_file) - - expect(@cloud.openstack).to receive(:wait_resource).with(image, :active) - - sc_id = @cloud.create_stemcell('/tmp/foo', - 'name' => 'stemcell-name', - 'version' => 'x.y.z', - 'container_format' => 'bare', - 'disk_format' => 'qcow2') - - expect(sc_id).to eq 'i-bar' - end - end - end - - context 'given a light stemcell' do - let(:images) { double('images', get: image) } - let(:image) { double('image', id: 'image_id', status: 'active') } - - before(:each) do - @cloud = mock_glance(cloud_options) do |glance| - @glance = glance - allow(glance).to receive(:images).and_return(images) - end - end - - it 'returns the image id with ` light` suffix' do - cloud_properties = { - 'image_id' => 'image_id', - } - - stemcell_id = @cloud.create_stemcell('/tmp/foo', cloud_properties) - - expect(stemcell_id).to eq('image_id light') - end - - it 'checks if image with id exists in OpenStack' do - cloud_properties = { - 'image_id' => 'image_id', - } - - @cloud.create_stemcell('/tmp/foo', cloud_properties) - - expect(images).to have_received(:get).with('image_id') - end - - context 'when image with given id does not exist in OpenStack' do - let(:images) { double('images', get: nil) } - - it 'raises a cloud error' do - cloud_properties = { - 'image_id' => 'non_existing_image_id', - } - - expect { - @cloud.create_stemcell('/tmp/foo', cloud_properties) - }.to raise_error(Bosh::Clouds::CloudError, 'No active image with id \'non_existing_image_id\' referenced by light stemcell found in OpenStack.') - end - end - - context 'when image with given id is not in state `active`' do - let(:image) { double('image', id: 'image_id', status: 'not-active') } - - it 'raises a cloud error' do - cloud_properties = { - 'image_id' => 'image_id', - } - - expect { - @cloud.create_stemcell('/tmp/foo', cloud_properties) - }.to raise_error(Bosh::Clouds::CloudError, 'No active image with id \'image_id\' referenced by light stemcell found in OpenStack.') - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/create_vm_spec.rb b/src/bosh_openstack_cpi/spec/unit/create_vm_spec.rb deleted file mode 100644 index 7da9f4778..000000000 --- a/src/bosh_openstack_cpi/spec/unit/create_vm_spec.rb +++ /dev/null @@ -1,1055 +0,0 @@ -require 'spec_helper' -require 'excon' - -describe Bosh::OpenStackCloud::Cloud, 'create_vm' do - def agent_settings(unique_name, network_spec = dynamic_network_spec, ephemeral = '/dev/sdb') - { - 'vm' => { - 'name' => "vm-#{unique_name}", - }, - 'agent_id' => 'agent-id', - 'networks' => { 'network_a' => network_spec }, - 'disks' => { - 'system' => '/dev/sda', - 'ephemeral' => ephemeral, - 'persistent' => {}, - }, - 'env' => { - 'test_env' => 'value', - }, - 'foo' => 'bar', # Agent env - 'baz' => 'zaz', - } - end - - def openstack_params(network_spec = { 'network_a' => dynamic_network_spec }, boot_from_volume = false) - params = { - name: "vm-#{unique_name}", - image_ref: 'sc-id', - flavor_ref: 'f-test', - key_name: 'test_key', - security_groups: configured_security_groups, - os_scheduler_hints: scheduler_hints, - nics:, - config_drive: false, - user_data: JSON.dump(user_data(unique_name, network_spec, nameserver, false)), - availability_zone: 'foobar-1a', - } - - if boot_from_volume - params.delete(:image_ref) - params[:block_device_mapping_v2] = [{ - uuid: 'sc-id', - source_type: 'image', - destination_type: 'volume', - volume_size: 2, - boot_index: '0', - delete_on_termination: '1', - device_name: '/dev/vda', - }] - end - - params - end - - def user_data(unique_name, network_spec, nameserver = nil, openssh = false) - user_data = { - 'server' => { - 'name' => "vm-#{unique_name}", - }, - } - user_data['openssh'] = { 'public_key' => 'public openssh key' } if openssh - user_data['networks'] = network_spec - user_data['dns'] = { 'nameserver' => [nameserver] } if nameserver - user_data['registry'] = { 'endpoint' => 'http://registry:3333' } - user_data - end - - let(:unique_name) { SecureRandom.uuid } - let(:server) { double('server', id: 'i-test', name: 'i-test') } - let(:image) { double('image', id: 'sc-id', name: 'sc-id') } - let(:flavor) { double('flavor', id: 'f-test', name: 'm1.tiny', ram: 1024, disk: 2, ephemeral: 2) } - let(:key_pair) { - double('key_pair', id: 'k-test', name: 'test_key', - fingerprint: '00:01:02:03:04', public_key: 'public openssh key') - } - let(:configured_security_groups) { %w[default] } - let(:nameserver) { nil } - let(:nics) { [] } - let(:scheduler_hints) { nil } - let(:options) { mock_cloud_options['properties'] } - let(:environment) { { 'test_env' => 'value' } } - let(:cpi_api_version) { 1 } - let(:cloud) do - mock_cloud(options, cpi_api_version) do |fog| - allow(fog.image.images).to receive(:find_by_id).and_return(image) - allow(fog.compute.servers).to receive(:create).and_return(server) - allow(fog.compute.flavors).to receive(:find).and_return(flavor) - allow(fog.compute.key_pairs).to receive(:find).and_return(key_pair) - end - end - let(:network_configuration) { { 'network_a' => dynamic_network_spec } } - - - before(:each) do - @registry = mock_registry - Bosh::Clouds::Config.configure(double('config', uuid: 'director-uuid')) - allow(@registry).to receive(:delete_settings) - allow(@registry).to receive(:update_settings) - allow(cloud).to receive(:generate_unique_name).and_return(unique_name) - allow(cloud.openstack).to receive(:wait_resource) - allow(Bosh::OpenStackCloud::TagManager).to receive(:tag_server) - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:port_ids).and_return([]) - end - - it 'redacts user_data' do - allow(Bosh::Clouds::Config.logger).to receive(:debug) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with(/Using boot params:.*"user_data" => ""/) - end - - it "creates an OpenStack server and polls until it's ready" do - vm_id = cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - expect(cloud.openstack).to have_received(:wait_resource).with(server, :active, :state) - expect(vm_id).to eq('i-test') - end - - describe 'multi-homed VMs' do - let(:cloud) do - cloud_options = mock_cloud_options['properties'] - cloud_options['openstack'].merge!( - 'config_drive' => 'cdrom', - 'use_dhcp' => false, - 'use_nova_networking' => false, - ) - - mock_cloud(cloud_options) do |openstack| - allow(openstack.compute.servers).to receive(:create).and_return(server) - allow(openstack.image.images).to receive(:find_by_id).and_return(image) - allow(openstack.compute.flavors).to receive(:find).and_return(flavor) - allow(openstack.compute.key_pairs).to receive(:find).and_return(key_pair) - port_result_net = double('ports1', id: '117717c1-81cb-4ac4-96ab-99aaf1be9ca8', network_id: 'net', mac_address: 'AA:AA:AA:AA:AA:AA') - ports = double('Fog::OpenStack::Network::Ports') - allow(ports).to receive(:create).with({network_id: 'net', fixed_ips: [{ ip_address: '10.0.0.1' }], security_groups: ['default_sec_group_id']}).and_return(port_result_net) - allow(openstack.network).to receive(:ports).and_return(ports) - end - end - let(:network_spec) { { 'network_a' => manual_network_spec(ip: '10.0.0.1') } } - let(:expected_network_spec) { { 'network_a' => manual_network_spec(ip: '10.0.0.1', overwrites: { 'mac' => 'AA:AA:AA:AA:AA:AA', 'use_dhcp' => false }) } } - let(:nics) { [{ 'net_id' => 'net', 'port_id' => '117717c1-81cb-4ac4-96ab-99aaf1be9ca8' }] } - - it 'creates an OpenStack server with config drive and mac addresses' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_spec, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(expected_network_spec).merge(config_drive: true)) - end - end - - context 'with nameserver' do - let(:nameserver) { '1.2.3.4' } - let(:network_spec) do - network_spec = dynamic_network_spec - network_spec['dns'] = [nameserver] - network_spec - end - - it 'passes dns servers in server user data when present' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => network_spec }, nil, environment) - - expect(cloud.openstack.compute.servers).to have_received(:create).with(openstack_params('network_a' => network_spec)) - expect(@registry).to have_received(:update_settings).with("vm-#{unique_name}", agent_settings(unique_name, network_spec)) - end - end - - context 'with security groups' do - let(:network_with_security_group) { - ns = dynamic_network_spec - ns['cloud_properties'] ||= {} - ns['cloud_properties']['security_groups'] = %w[net-group-1 net-group-2] - { 'network_a' => ns } - } - - let(:resource_pool_with_security_group_spec) { - rps = resource_pool_spec - rps['security_groups'] = %w[pool-group-1 pool-group-2] - rps - } - - before(:each) do - mock_sec_groups(cloud.network, openstack_security_groups) - end - - context 'defined in both network and resource_pools spec' do - let(:openstack_security_groups) { - [ - double('net-group-1', id: 'net-group-1_id', name: 'net-group-1'), - double('net-group-2', id: 'net-group-2_id', name: 'net-group-2'), - double('pool-group-1', id: 'pool-group-1_id', name: 'pool-group-1'), - double('pool-group-2', id: 'pool-group-2_id', name: 'pool-group-2'), - ] - } - - it 'uses the resource pool security groups to create vm' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_with_security_group_spec, network_with_security_group, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(security_groups: %w[pool-group-1 pool-group-2])) - end - end - - context 'defined in network spec' do - let(:openstack_security_groups) { - [ - double('net-group-1', id: 'net-group-1_id', name: 'net-group-1'), - double('net-group-2', id: 'net-group-2_id', name: 'net-group-2'), - ] - } - - let(:configured_security_groups) { %w[net-group-1 net-group-2] } - - it 'creates an OpenStack server' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_with_security_group, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(network_with_security_group)) - expect(@registry).to have_received(:update_settings).with("vm-#{unique_name}", agent_settings(unique_name, network_with_security_group['network_a'])) - end - end - - context 'defined in resource_pools spec' do - let(:openstack_security_groups) { - [ - double('pool-group-1', id: 'pool-group-1_id', name: 'pool-group-1'), - double('pool-group-2', id: 'pool-group-2_id', name: 'pool-group-2'), - ] - } - - let(:configured_security_groups) { %w[pool-group-1 pool-group-2] } - - let(:dynamic_network_without_security_group) do - ns = dynamic_network_spec - ns['cloud_properties'] = {} - { 'network_a' => ns } - end - - it 'creates an OpenStack server' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_with_security_group_spec, dynamic_network_without_security_group, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(dynamic_network_without_security_group)) - expect(@registry).to have_received(:update_settings).with("vm-#{unique_name}", agent_settings(unique_name, dynamic_network_without_security_group['network_a'])) - end - end - end - - context 'with dynamic network' do - context 'with nic' do - let(:nics) do - [ - { 'net_id' => 'foo' }, - ] - end - - it 'creates an OpenStack server with nic for dynamic network' do - network_spec = dynamic_network_spec - network_spec['cloud_properties'] ||= {} - network_spec['cloud_properties']['net_id'] = nics[0]['net_id'] - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => network_spec }, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params('network_a' => network_spec)) - expect(@registry).to have_received(:update_settings).with("vm-#{unique_name}", agent_settings(unique_name, network_spec)) - end - end - - context 'when vrrp ip is configured' do - let(:resource_pool_with_vrrp) { - rps = resource_pool_spec - rps['allowed_address_pairs'] = '10.0.0.10' - rps - } - - it 'raises an cloud error' do - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_with_vrrp, { 'network' => dynamic_network_with_netid_spec }, nil, environment) - }.to raise_error Bosh::Clouds::CloudError, "Network with id 'net' is a dynamic network. VRRP is not supported for dynamic networks" - end - end - end - - context 'with manual network' do - let(:several_manual_networks) do - { - 'network_a' => manual_network_spec(ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'bar', ip: '10.0.0.2'), - } - end - let(:manual_network) { { 'network_a' => manual_network_spec(ip: '10.0.0.1') } } - - let(:nics) { [{ 'net_id' => 'net', 'v4_fixed_ip' => '10.0.0.1' }, { 'net_id' => 'bar', 'v4_fixed_ip' => '10.0.0.2' }] } - let(:configured_security_groups) { %w[default default] } - let(:options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['config_drive'] = 'cdrom' - cloud_options['properties']['openstack']['use_dhcp'] = false - cloud_options['properties'] - end - - it 'calls NetworkConfigurator#prepare and NetworkConfigurator#nics' do - expect(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:new).with(anything, nil).and_call_original - expect_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:prepare).with(anything) - expect_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:nics).and_return(nics) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, several_manual_networks, nil, environment) - end - - context 'when vrrp ip is configured' do - let(:resource_pool_with_vrrp) { - rps = resource_pool_spec - rps['allowed_address_pairs'] = '10.0.0.10' - rps - } - - it 'calls NetworkConfigurator#prepare and NetworkConfigurator#nics' do - expect(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:new).with(anything, '10.0.0.10').and_call_original - expect_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:prepare).with(anything) - expect_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:nics).and_return(nics) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_with_vrrp, several_manual_networks, nil, environment) - end - end - - context 'when vm_destroy fails' do - before(:each) do - allow(server).to receive(:destroy).and_raise 'BOOM!!!' - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:prepare) - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup) - end - - it 'calls NetworkConfigurator#cleanup and fails with VMCreationFailed error' do - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:configure).and_raise 'BOOM configure!!!' - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, manual_network, nil, environment) - }.to raise_error Bosh::Clouds::VMCreationFailed, 'BOOM configure!!!' - end - end - end - - context 'with scheduler hints' do - let(:scheduler_hints) do - { group: 'abcd-foo-bar' } - end - - it 'creates an OpenStack server with scheduler hints' do - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:configure) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec.merge('scheduler_hints' => scheduler_hints), combined_network_spec) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(combined_network_spec)) - end - end - - context 'when boot_from_volume is set' do - let(:dynamic_network) { { 'network_a' => dynamic_network_spec } } - - context 'globally' do - let(:options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['boot_from_volume'] = true - cloud_options['properties'] - end - - it 'creates an OpenStack server with a boot volume' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, dynamic_network, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(dynamic_network, true)) - end - end - - context 'for vm type' do - let(:resource_pool_spec_with_boot_from_volume) { resource_pool_spec.merge('boot_from_volume' => true) } - let(:options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['boot_from_volume'] = false - cloud_options['properties'] - end - - it 'creates an OpenStack server with a boot volume' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec_with_boot_from_volume, dynamic_network, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(dynamic_network, true)) - end - end - - context 'and a volume_type as well' do - let(:options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['boot_from_volume'] = true - cloud_options['properties']['openstack']['boot_volume_cloud_properties'] = { 'type' => 'foo' } - cloud_options['properties'] - end - - it 'creates an OpenStack server with a boot volume' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, dynamic_network, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params(dynamic_network, true)) - end - end - end - - context 'when config_drive option is set' do - let(:options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['config_drive'] = 'cdrom' - cloud_options['properties'] - end - - it 'creates an OpenStack server with config drive' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params.merge(config_drive: true)) - end - end - - context 'when OpenStack cannot create the server' do - before do - allow(server).to receive(:destroy) - end - - context 'when OpenStack raises a Timeout error' do - let(:socket_error) { Excon::Error::Timeout.new('read timeout reached') } - - it 'raises a Cloud error with vm information' do - allow(cloud.compute.servers).to receive(:create).and_raise(socket_error) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed, /'vm-#{unique_name}'.*?\nOriginal message: read timeout reached/) - end - end - - context 'when OpenStack raises a Not Found error' do - let(:networks) { double('networks') } - let(:not_found_error) { Excon::Error::NotFound.new('not found: 814bc266-c6de-4fd0-a713-502da09edbe9') } - - before(:each) do - allow(cloud.compute.servers).to receive(:create).and_raise(not_found_error) - allow(cloud.network).to receive(:networks).and_return(networks) - end - - it 'raises a VMCreationFailed error with subnet ID' do - allow(networks).to receive(:get).and_return(nil) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_with_netid_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed, /'vm-#{unique_name}'.*?'net'/) - end - - it 'raises a Not Found error with existing Net IDs' do - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:prepare) - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup) - allow(networks).to receive(:get).and_return('some_network') - network_with_different_net_id = { 'network_b' => manual_network_spec(net_id: 'some_other_id') } - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_with_different_net_id, nil, environment) - }.to raise_error(Excon::Error::NotFound, 'not found: 814bc266-c6de-4fd0-a713-502da09edbe9') - end - - context 'when `openstack.network.networks.get` raises' do - before(:each) do - allow(networks).to receive(:get).and_raise('BOOM!!!') - end - - it 'raises the original error' do - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:prepare) - allow_any_instance_of(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup) - network_with_different_net_id = { 'network_b' => manual_network_spec(net_id: 'some_other_id') } - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_with_different_net_id, nil, environment) - }.to raise_error(Excon::Error::NotFound, 'not found: 814bc266-c6de-4fd0-a713-502da09edbe9') - end - end - - context 'when `use_nova_networking=true`' do - let(:options) { - mocked_options = mock_cloud_options(3) - mocked_options['properties']['openstack']['use_nova_networking'] = true - mocked_options['properties'] - } - - before(:each) do - allow(cloud.compute.servers).to receive(:create).and_raise(not_found_error) - security_groups = [double('default_sec_group', id: 'default_sec_group_id', name: 'default')] - mock_sec_groups(cloud.compute, security_groups) - end - - it 'raises a Not Found error with Network service not available' do - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_with_netid_spec }, nil, environment) - }.to raise_error(Excon::Error::NotFound) - - expect(cloud.network).to_not have_received(:networks) - end - end - end - - context 'when OpenStack raises a BadRequest error' do - let(:networks) { double('networks') } - let(:bad_request_error) { Excon::Error::BadRequest.new('Message does not matter here') } - - before(:each) do - allow(cloud.compute.servers).to receive(:create).and_raise(bad_request_error) - allow(cloud.network).to receive(:networks).and_return(networks) - end - - it 'raises a VMCreationFailed error with subnet ID' do - allow(networks).to receive(:get).and_return(nil) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_with_netid_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed, /'vm-#{unique_name}'.*?'net'/) - end - end - - context 'with a CloudError' do - it 'destroys the server successfully' do - allow(cloud.openstack).to receive(:wait_resource).with(server, :active, :state).and_raise(Bosh::Clouds::CloudError) - expect(cloud.openstack).to receive(:wait_resource).with(server, %i[terminated deleted], :state, true) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed) - end - end - - context 'with a StandardError' do - it 'destroys the server successfully' do - allow(cloud.openstack).to receive(:wait_resource).with(server, :active, :state).and_raise(StandardError) - expect(cloud.openstack).to receive(:wait_resource).with(server, %i[terminated deleted], :state, true) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed) - end - end - - it 'raises a VMCreationFailed error and logs correct failure message when failed to destroy the server' do - allow(server).to receive(:destroy) - allow(cloud.openstack).to receive(:wait_resource).with(server, :active, :state).and_raise(Bosh::Clouds::CloudError) - allow(cloud.openstack).to receive(:wait_resource).with(server, %i[terminated deleted], :state, true).and_raise(Bosh::Clouds::CloudError) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed) - - expect(Bosh::Clouds::Config.logger).to have_received(:warn).with('Failed to create server: Bosh::Clouds::CloudError') - expect(Bosh::Clouds::Config.logger).to have_received(:warn).with(/Failed to destroy server:.*/) - end - end - - context 'when fail to register an OpenStack server after the server is created' do - before(:each) { allow(server).to receive(:destroy) } - - it 'destroys the server successfully and raises a non-retryable Error when CloudError happens' do - allow(@registry).to receive(:update_settings).and_raise(Bosh::Clouds::CloudError) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to(raise_error { |error| - expect(error).to be_a(Bosh::Clouds::VMCreationFailed) - expect(error.ok_to_retry).to eq(false) - }) - expect(cloud.openstack).to have_received(:wait_resource).with(server, %i[terminated deleted], :state, true) - end - - it 'destroys the server successfully and raises a non-retryable Error when StandardError happens' do - allow(@registry).to receive(:update_settings).and_raise(StandardError) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to(raise_error { |error| - expect(error).to be_a(Bosh::Clouds::VMCreationFailed) - expect(error.ok_to_retry).to eq(false) - }) - expect(cloud.openstack).to have_received(:wait_resource).with(server, %i[terminated deleted], :state, true) - end - - it 'logs correct failure message when failed to destroy the server' do - allow(@registry).to receive(:update_settings).and_raise(Bosh::Clouds::CloudError) - allow(cloud.openstack).to receive(:wait_resource).with(server, %i[terminated deleted], :state, true).and_raise(Bosh::Clouds::CloudError) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::VMCreationFailed) - expect(Bosh::Clouds::Config.logger).to have_received(:warn).with('Failed to register server: Bosh::Clouds::CloudError') - expect(Bosh::Clouds::Config.logger).to have_received(:warn).with(/Failed to destroy server:.*/) - end - end - - context "when security group doesn't exist" do - let(:openstack_security_groups) { [double('foo-sec-group', id: 'foo-sec-group-id', name: 'foo')] } - - it 'raises an error' do - mock_sec_groups(cloud.network, openstack_security_groups) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::CloudError, "Security group `default' not found") - end - end - - it "raises an error when flavor doesn't have enough ephemeral disk capacity" do - flavor = double('flavor', id: 'f-test', name: 'm1.tiny', ram: 1024, ephemeral: 1) - allow(cloud.compute.flavors).to receive(:find).and_return(flavor) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::CloudError, "Flavor `m1.tiny' should have at least 2Gb of ephemeral disk") - end - - context 'when use_dhcp is set to false' do - let(:options) do - cloud_options = mock_cloud_options - cloud_options['properties']['openstack']['use_dhcp'] = false - cloud_options['properties'] - end - - it 'updates network settings to include use_dhcp as false' do - expected_network_spec = dynamic_network_spec - expected_network_spec['use_dhcp'] = false - expected_openstack_params = openstack_params('network_a' => expected_network_spec) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - expect(cloud.compute.servers).to have_received(:create).with(expected_openstack_params) - end - end - - describe 'key_name configuration' do - let(:resource_pool_spec_no_key) do - { - 'availability_zone' => 'foobar-1a', - 'instance_type' => 'm1.tiny', - } - end - - context 'when key_name is only defined in resource pool' do - it 'takes the key_name from resource pool' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params) - end - end - - context 'when default_key_name is only defined in CPI cloud properties' do - let(:options) do - cloud_options_with_default_key_name = mock_cloud_options['properties'] - cloud_options_with_default_key_name['openstack']['default_key_name'] = 'default_key_name' - cloud_options_with_default_key_name - end - - before(:each) do - allow(cloud).to receive(:validate_key_exists) - end - - it 'takes the key_name from CPI cloud properties' do - expected_openstack_params = openstack_params - expected_openstack_params[:key_name] = 'default_key_name' - - expect_any_instance_of(Bosh::OpenStackCloud::VmFactory).to receive(:validate_key_exists).with(options['openstack']['default_key_name']) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec_no_key, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(expected_openstack_params) - end - end - - context 'when default_key_name is defined in CPI cloud properties and key_name in resource pool' do - let(:options) do - cloud_options_with_default_key_name = mock_cloud_options['properties'] - cloud_options_with_default_key_name['openstack']['default_key_name'] = 'default_key_name' - cloud_options_with_default_key_name - end - - before(:each) do - allow(cloud).to receive(:validate_key_exists) - end - - it 'takes the key_name from resource pool' do - expect_any_instance_of(Bosh::OpenStackCloud::VmFactory).to receive(:validate_key_exists).with('test_key') - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - end - end - - context 'when no key_name is defined' do - it 'raises cloud error' do - allow(cloud.compute).to receive(:key_pairs).and_return([key_pair]) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec_no_key, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::CloudError, "Key-pair `' not found") - end - end - end - - describe 'use "vm-" as registry key' do - context 'when "human_readable_vm_names" is enabled' do - let(:options) do - options = mock_cloud_options['properties'] - options['openstack']['human_readable_vm_names'] = true - options - end - - it 'logs human_readable_vm_names enabled' do - # Bosh.retryable requires us to return a non-nil value from debug - allow(Bosh::Clouds::Config.logger).to receive(:debug).and_return('logged') - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("'human_readable_vm_names' enabled") - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("Tagged VM 'i-test' with tags '{registry_key: \"vm-#{unique_name}\"}") - end - end - - context 'when "human_readable_vm_names" is disabled' do - let(:options) do - mock_cloud_options['properties'] - end - - it 'does not tag server with registry tag' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(Bosh::OpenStackCloud::TagManager).to_not have_received(:tag_server).with(server, registry_key: "vm-#{unique_name}") - end - - it 'logs human_readable_vm_names disabled' do - # Bosh.retryable requires us to return a non-nil value from debug - allow(Bosh::Clouds::Config.logger).to receive(:debug).and_return('logged') - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("'human_readable_vm_names' disabled") - end - end - end - - describe 'with light stemcell' do - it 'creates the vm with the image id of the heavy stemcell' do - cloud.create_vm('agent-id', 'sc-id light', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(openstack_params) - expect(@registry).to have_received(:update_settings).with("vm-#{unique_name}", agent_settings(unique_name)) - end - end - - describe 'with loadbalancer pool' do - let(:resource_pool_spec_with_lbaas_pools) do - resource_pool_spec.merge( - 'loadbalancer_pools' => [ - { 'name' => 'my-pool-1', 'port' => 443 }, - { 'name' => 'my-pool-2', 'port' => 8080 }, - ], - ) - end - - it 'creates as many loadbalancers as are listed in the manifest' do - network_spec = { 'network_a' => dynamic_network_spec } - pool_membership = Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadbalancerPoolMembership.new('name', 'port', 'pool_id', 'membership_id') - - expect_any_instance_of(Bosh::OpenStackCloud::LoadbalancerConfigurator) - .to receive(:add_vm_to_pool) - .with(server, network_spec, 'name' => 'my-pool-1', 'port' => 443) - .and_return(pool_membership) - expect_any_instance_of(Bosh::OpenStackCloud::LoadbalancerConfigurator) - .to receive(:add_vm_to_pool) - .with(server, network_spec, 'name' => 'my-pool-2', 'port' => 8080) - .and_return(pool_membership) - - cloud.create_vm('agent-id', 'sc-id light', resource_pool_spec_with_lbaas_pools, network_spec, nil, environment) - end - end - - describe 'setting VM metadata' do - context 'when human_readable_vm_names are enabled' do - let(:options) do - options = mock_cloud_options['properties'] - options['openstack']['human_readable_vm_names'] = true - options - end - - it 'tags registry_key with "vm-"' do - allow(Bosh::OpenStackCloud::TagManager).to receive(:tag_server) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(Bosh::OpenStackCloud::TagManager).to have_received(:tag_server).with(server, registry_key: "vm-#{unique_name}") - end - - it 'raises an exception, if tagging fails' do - allow(Bosh::OpenStackCloud::TagManager).to receive(:tag_server).and_raise(StandardError) - - expect(server).to receive(:destroy) - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::CloudError) - end - end - - context 'when loadbalancer_pools are present' do - let(:resource_pool_spec_with_lbaas_pools) do - resource_pool_spec.merge( - 'loadbalancer_pools' => [ - { 'name' => 'my-pool-1', 'port' => 443 }, - { 'name' => 'my-pool-2', 'port' => 8080 }, - ], - ) - end - - let(:loadbalancer_configurator) { instance_double(Bosh::OpenStackCloud::LoadbalancerConfigurator) } - - before(:each) do - allow(Bosh::OpenStackCloud::LoadbalancerConfigurator).to receive(:new).and_return(loadbalancer_configurator) - allow(loadbalancer_configurator).to receive(:create_pool_memberships).and_return( - 'lbaas_pool_1' => 'pool_id/membership_id', - 'lbaas_pool_2' => 'pool_id/membership_id', - ) - allow(Bosh::OpenStackCloud::TagManager).to receive(:tag_server) - end - - it 'tags the vm with the lbaas pool and membership' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec_with_lbaas_pools, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(Bosh::OpenStackCloud::TagManager).to have_received(:tag_server).with(server, - 'lbaas_pool_1' => 'pool_id/membership_id', - 'lbaas_pool_2' => 'pool_id/membership_id') - end - - context 'when tagging fails' do - it 'cleans up the membership and raises an exception' do - allow(Bosh::OpenStackCloud::TagManager).to receive(:tag_server).and_raise(StandardError) - allow(loadbalancer_configurator).to receive(:cleanup_memberships) - allow(server).to receive(:destroy) - - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec_with_lbaas_pools, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::CloudError) - expect(server).to have_received(:destroy) - expect(loadbalancer_configurator).to have_received(:cleanup_memberships).with( - 'lbaas_pool_1' => 'pool_id/membership_id', - 'lbaas_pool_2' => 'pool_id/membership_id', - ) - end - end - end - end - - describe 'when multiple azs are configured' do - let(:options) do - options = mock_cloud_options['properties'] - options['openstack']['ignore_server_availability_zone'] = true - options - end - - let(:azs) { ['az-1', 'az-2'] } - let(:resource_pool_spec) do - { - 'key_name' => 'test_key', - 'availability_zones' => azs, - 'instance_type' => 'm1.tiny', - } - end - - context 'dynamic network' do - it 'creates the vm in one of the configured azs' do - expect(cloud.compute.servers).to receive(:create).with( - include(availability_zone: 'az-1').or(include(availability_zone: 'az-2')), - ) - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - end - - it 'iterates over the azs till it can create a vm in one of the configured azs' do - allow_any_instance_of(Bosh::OpenStackCloud::AvailabilityZoneProvider).to receive(:select_azs).and_return(azs) - allow(cloud.compute.servers) - .to receive(:create) - .with(include(availability_zone: 'az-1')) - .and_raise Bosh::Clouds::CloudError.new('Example exception from infrastructure') - - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(include(availability_zone: 'az-1')) - expect(cloud.compute.servers).to have_received(:create).with(include(availability_zone: 'az-2')) - end - - context 'last try fails' do - let(:err) { Bosh::Clouds::CloudError.new('Example exception from infrastructure') } - before { allow(cloud.compute.servers).to receive(:create).and_raise err } - - it 'raises an error if last try fails' do - expect { - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, { 'network_a' => dynamic_network_spec }, nil, environment) - }.to raise_error(Bosh::Clouds::CloudError) - expect(cloud.compute.servers).to have_received(:create).with(include(availability_zone: 'az-1')) - expect(cloud.compute.servers).to have_received(:create).with(include(availability_zone: 'az-2')) - end - end - end - end - - describe 'requests cpi api' do - let(:expected_user_data) do - Regexp.new('\"networks\":{\"network_a\":{\"type\":\"dynamic\",\"cloud_properties\":' \ - '{\"security_groups\":\[\"default\"\]},\"use_dhcp\":true}}') - end - - context 'with cpi api version 1' do - it 'sets networks in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(user_data: expected_user_data)) - end - - it 'logs that settings get updated' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.logger).to have_received(:info).with(/Updating settings/) - end - - context 'when stemcell API v1' do - it 'does not set agent_id in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create) - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"agent_id\":/)) - end - - it 'does not set env in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create) - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"env\":/)) - end - end - - context 'when stemcell API v2' do - let(:options) do - options = mock_cloud_options['properties'] - options['openstack']['vm'] = { - 'stemcell' => { - 'api_version' => 2, - }, - } - options - end - - it 'does not set agent_id in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"agent_id\":/)) - end - - it 'does not set env in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"env\":/)) - end - - it 'sets networks in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(user_data: expected_user_data)) - end - end - end - - context 'with cpi api version 2' do - let(:cpi_api_version) { 2 } - - it 'sets networks in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(user_data: expected_user_data)) - end - - it 'returns instance_id and network configuration' do - res = cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - expect(res).to be_a(Array) - expect(res[0]).to eq('i-test') - expect(res[1]).to eq(network_configuration) - end - - context 'when stemcell API v1' do - it 'does not set agent settings in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"foo\":/)) - end - - it 'does not set agent_id in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create) - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"agent_id\":/)) - end - - it 'does not set env in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create) - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"env\":/)) - end - - it 'does set registry' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(user_data: /\"registry\":/)) - end - end - - context 'when stemcell API v2' do - let(:options) do - options = mock_cloud_options['properties'] - options['openstack']['vm'] = { - 'stemcell' => { - 'api_version' => 2, - }, - } - options - end - - it 'sets agent settings in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(user_data: /\"foo\":\"bar\"/)) - end - - it 'sets agent_id in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with(hash_including(user_data: /\"agent_id\":\"agent-id\"/)) - end - - it 'sets env in user data' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).to have_received(:create).with( - hash_including(user_data: /\"env\":{\"test_env\":\"value\"}/), - ) - end - - it 'does not set registry' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.compute.servers).not_to have_received(:create).with(hash_including(user_data: /\"registry\":/)) - end - - it 'does not log that settings get updated' do - cloud.create_vm('agent-id', 'sc-id', resource_pool_spec, network_configuration, nil, environment) - - expect(cloud.logger).not_to have_received(:info).with(/Updating settings/) - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/delete_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/delete_disk_spec.rb deleted file mode 100644 index 62248d675..000000000 --- a/src/bosh_openstack_cpi/spec/unit/delete_disk_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - it 'deletes an OpenStack volume' do - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get) - .with('v-foobar').and_return(volume) - end - - expect(volume).to receive(:status).and_return(:available) - expect(volume).to receive(:destroy).and_return(true) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :deleted, :status, true) - - cloud.delete_disk('v-foobar') - end - - it "doesn't delete an OpenStack volume unless it's state is `available'" do - volume = double('volume', id: 'v-foobar') - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - end - - expect(volume).to receive(:status).and_return(:busy) - - expect { - cloud.delete_disk('v-foobar') - }.to raise_error(Bosh::Clouds::CloudError, - "Cannot delete volume `v-foobar', state is busy") - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/delete_snapshot_spec.rb b/src/bosh_openstack_cpi/spec/unit/delete_snapshot_spec.rb deleted file mode 100644 index 3a6bad49d..000000000 --- a/src/bosh_openstack_cpi/spec/unit/delete_snapshot_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - it 'deletes an OpenStack snapshot' do - snapshot = double('snapshot', id: 'snap-foobar') - - cloud = mock_cloud do |fog| - expect(fog.volume.snapshots).to receive(:get).with('snap-foobar').and_return(snapshot) - end - - expect(snapshot).to receive(:status).and_return(:available) - expect(snapshot).to receive(:destroy).and_return(true) - expect(cloud.openstack).to receive(:wait_resource).with(snapshot, :deleted, :status, true) - - cloud.delete_snapshot('snap-foobar') - end - - it "doesn't delete an OpenStack snapshot unless its state is `available'" do - snapshot = double('snapshot', id: 'snap-foobar') - - cloud = mock_cloud do |fog| - expect(fog.volume.snapshots).to receive(:get).with('snap-foobar').and_return(snapshot) - end - - expect(snapshot).to receive(:status).and_return(:busy) - - expect { - cloud.delete_snapshot('snap-foobar') - }.to raise_error(Bosh::Clouds::CloudError, "Cannot delete snapshot `snap-foobar', state is busy") - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/delete_stemcell_spec.rb b/src/bosh_openstack_cpi/spec/unit/delete_stemcell_spec.rb deleted file mode 100644 index f901219b6..000000000 --- a/src/bosh_openstack_cpi/spec/unit/delete_stemcell_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - it 'deletes stemcell' do - image = double('image', id: 'i-foo', name: 'i-foo', properties: {}) - - cloud = mock_glance do |glance| - allow(glance.images).to receive(:find_by_id).with('i-foo').and_return(image) - end - - expect(image).to receive(:destroy) - - cloud.delete_stemcell('i-foo') - end - - describe 'with light stemcell' do - it 'does no operation' do - image = double('image', id: 'i-foo', name: 'i-foo', properties: {}) - - cloud = mock_glance do |glance| - allow(glance.images).to receive(:find_by_id).with('i-foo').and_return(image) - end - - expect(image).not_to receive(:destroy) - - cloud.delete_stemcell('i-foo light') - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/delete_vm_spec.rb b/src/bosh_openstack_cpi/spec/unit/delete_vm_spec.rb deleted file mode 100644 index bf2ef650c..000000000 --- a/src/bosh_openstack_cpi/spec/unit/delete_vm_spec.rb +++ /dev/null @@ -1,211 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - let(:registry_key) { 'vm-registry-key' } - - let(:server_metadata) do - [ - double('metadatum', key: 'lbaas_pool_0', value: 'pool-id-0/membership-id-0'), - double('metadatum', key: 'job', value: 'bosh'), - double('metadatum', key: 'registry_key', value: registry_key), - ] - end - - let(:server) { double('server', id: 'i-foobar', name: 'i-foobar', metadata: server_metadata) } - let(:cloud) do - mock_cloud do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - end - end - - let(:loadbalancer_configurator) { instance_double(Bosh::OpenStackCloud::LoadbalancerConfigurator) } - - before(:each) do - @registry = mock_registry - Bosh::Clouds::Config.configure(double('config', uuid: 'director-uuid')) - - allow(Bosh::Clouds::Config).to receive(:uuid).and_return('fake-uuid') - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:port_ids).and_return(['port_id']) - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup_ports) - allow(server).to receive(:destroy) - allow(cloud.openstack).to receive(:wait_resource) - allow(@registry).to receive(:delete_settings) - allow(Bosh::OpenStackCloud::LoadbalancerConfigurator).to receive(:new).and_return(loadbalancer_configurator) - allow(loadbalancer_configurator).to receive(:cleanup_memberships) - end - - context 'when server retrieval fails' do - let(:cloud) do - mock_cloud do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_raise('BOOM!') - end - end - - it 'stops and raises' do - expect { - cloud.delete_vm('i-foobar') - }.to raise_error('BOOM!') - end - end - - context 'when server cannot be found' do - let(:cloud) do - mock_cloud do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(nil) - end - end - - before(:each) do - allow(Bosh::Clouds::Config.logger).to receive(:info) - end - - it 'stops and logs' do - cloud.delete_vm('i-foobar') - - expect(Bosh::Clouds::Config.logger).to have_received(:info).with('Server `i-foobar\' not found. Skipping.') - end - end - - it 'deletes an OpenStack server after removing the lbaas pool membership' do - cloud.delete_vm('i-foobar') - - expect(loadbalancer_configurator).to have_received(:cleanup_memberships).with( - 'lbaas_pool_0' => 'pool-id-0/membership-id-0', - 'job' => 'bosh', - 'registry_key' => 'vm-registry-key', - ).ordered - expect(server).to have_received(:destroy).ordered - expect(cloud.openstack).to have_received(:wait_resource).with(server, %i[terminated deleted], :state, true).ordered - expect(Bosh::OpenStackCloud::NetworkConfigurator).to have_received(:cleanup_ports).with(any_args, ['port_id']).ordered - expect(cloud.logger).to have_received(:info).with(/Deleting settings/) - expect(@registry).to have_received(:delete_settings).with(registry_key) - end - - context 'when registry-less' do - let(:cpi_api_version) { 2 } - let(:cloud_options) { mock_cloud_options } - let(:cloud_options_stemcell_v2) do - cloud_options['properties']['openstack']['vm'] = { - 'stemcell' => { - 'api_version' => 2, - }, - } - cloud_options - end - let(:cloud) do - mock_cloud(cloud_options_stemcell_v2['properties'], cpi_api_version) do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - end - end - - it 'does not log deleting settings' do - expect(cloud.logger).to_not receive(:info).with(/Deleting settings/) - expect(cloud.registry).to receive(:delete_settings).with(registry_key) - - cloud.delete_vm('i-foobar') - end - end - - context 'when server destroy fails' do - it 'stops and raises' do - allow(server).to receive(:destroy).and_raise('BOOM!') - - expect { - cloud.delete_vm('i-foobar') - }.to raise_error('BOOM!') - end - end - - context 'when getting ports fails' do - it 'stops and raises' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:port_ids).and_raise('BOOM!') - - expect { - cloud.delete_vm('i-foobar') - }.to raise_error('BOOM!') - end - end - - context 'when port cleanup fails' do - it 'does everything else but fails in the end' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup_ports).and_raise('BOOM!') - - expect { - cloud.delete_vm('i-foobar') - }.to raise_error(/BOOM!/) - - expect(@registry).to have_received(:delete_settings).with(registry_key) - expect(loadbalancer_configurator).to have_received(:cleanup_memberships).with( - 'lbaas_pool_0' => 'pool-id-0/membership-id-0', - 'job' => 'bosh', - 'registry_key' => 'vm-registry-key', - ) - end - end - - context 'when destruction of LBaaS membership fails' do - it 'does everything else and fails' do - allow(loadbalancer_configurator).to receive(:cleanup_memberships).and_raise('BOOM!') - - expect { - cloud.delete_vm('i-foobar') - }.to raise_error(/BOOM!/) - - expect(Bosh::OpenStackCloud::NetworkConfigurator).to have_received(:cleanup_ports).with(any_args, ['port_id']) - expect(@registry).to have_received(:delete_settings).with(registry_key) - end - end - - context 'when port cleanup and LBaaS membership cleanup fails' do - it 'fails with both errors, but deletes the registry settings' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup_ports).and_raise('BOOM!') - allow(loadbalancer_configurator).to receive(:cleanup_memberships).and_raise('BOOM!') - - expect { - cloud.delete_vm('i-foobar') - }.to raise_error(Bosh::Clouds::CloudError) - - expect(Bosh::OpenStackCloud::NetworkConfigurator).to have_received(:cleanup_ports).with(any_args, ['port_id']) - expect(loadbalancer_configurator).to have_received(:cleanup_memberships).with( - 'lbaas_pool_0' => 'pool-id-0/membership-id-0', - 'job' => 'bosh', - 'registry_key' => 'vm-registry-key', - ) - expect(@registry).to have_received(:delete_settings).with(registry_key) - end - end - - context 'when port cleanup, LBaaS membership cleanup and deleting settings from registry fails' do - it 'fails with all errors and an aggregated error message containing the right prefixes' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup_ports).and_raise('BOOM1!') - allow(loadbalancer_configurator).to receive(:cleanup_memberships).and_raise('BOOM2!') - allow(@registry).to receive(:delete_settings).and_raise('BOOM3!') - - expected_error_msg = <<~EOF - Multiple cloud errors occurred: - Removing ports: BOOM1! - Removing lbaas pool memberships: BOOM2! - Deleting registry settings: BOOM3! - EOF - - expect { - cloud.delete_vm('i-foobar') - }.to raise_error(Bosh::Clouds::CloudError, expected_error_msg.chomp) - end - end - - context 'when server is not tagged with `registry_key`' do - let(:server_metadata) do - [ - double('metadatum', key: 'lbaas_pool_0', value: 'pool-id-0/membership-id-0'), - double('metadatum', key: 'job', value: 'bosh'), - ] - end - - it 'uses the server name to delete registry settings' do - cloud.delete_vm('i-foobar') - - expect(@registry).to have_received(:delete_settings).with(server.name) - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/detach_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/detach_disk_spec.rb deleted file mode 100644 index 81d32cdb0..000000000 --- a/src/bosh_openstack_cpi/spec/unit/detach_disk_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - let(:server) { double('server', id: 'i-test', name: 'i-test', metadata: double('metadata')) } - - before(:each) do - allow(server.metadata).to receive(:get).with(:registry_key).and_return(double('metadatum', 'value' => 'i-test')) - @registry = mock_registry - end - - it 'detaches an OpenStack volume from a server' do - volume = double('volume', id: 'v-foobar') - volume_attachments = [{ 'id' => 'a1', 'volumeId' => 'v-foobar' }, { 'id' => 'a2', 'volumeId' => 'v-barfoo' }] - - cloud = mock_cloud do |fog| - expect(fog.compute.servers).to receive(:get).with('i-test').and_return(server) - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - end - - expect(server).to receive(:volume_attachments).and_return(volume_attachments) - expect(server).to receive(:detach_volume).with(volume.id).and_return(true) - expect(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - old_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-foobar' => '/dev/vdc', - 'v-barfoo' => '/dev/vdd', - }, - }, - } - - new_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-barfoo' => '/dev/vdd', - }, - }, - } - - expect(@registry).to receive(:read_settings).with('i-test').and_return(old_settings) - expect(@registry).to receive(:update_settings).with('i-test', new_settings) - - cloud.detach_disk('i-test', 'v-foobar') - end - - it 'bypasses the detaching process when volume is not attached to a server' do - volume = double('volume', id: 'v-barfoo') - volume_attachments = [{ 'volumeId' => 'v-foobar' }] - - cloud = mock_cloud do |fog| - expect(fog.compute.servers).to receive(:get).with('i-test').and_return(server) - expect(fog.volume.volumes).to receive(:get).with('v-barfoo').and_return(volume) - end - - expect(server).to receive(:volume_attachments).and_return(volume_attachments) - expect(volume).not_to receive(:detach) - - old_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-foobar' => '/dev/vdc', - 'v-barfoo' => '/dev/vdd', - }, - }, - } - - new_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'v-foobar' => '/dev/vdc', - }, - }, - } - - expect(@registry).to receive(:read_settings).with('i-test').and_return(old_settings) - expect(@registry).to receive(:update_settings).with('i-test', new_settings) - - cloud.detach_disk('i-test', 'v-barfoo') - end - - it 'bypasses the detaching process when volume is missing' do - cloud = mock_cloud do |fog| - allow(fog.compute.servers).to receive(:get).with('i-test').and_return(server) - allow(fog.volume.volumes).to receive(:get).with('non-exist-volume-id').and_return(nil) - end - - old_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'non-exist-volume-id' => '/dev/vdc', - 'exist-volume-id' => '/dev/vdd', - }, - }, - } - - new_settings = { - 'foo' => 'bar', - 'disks' => { - 'persistent' => { - 'exist-volume-id' => '/dev/vdd', - }, - }, - } - - expect(@registry).to receive(:read_settings).with('i-test').and_return(old_settings) - expect(@registry).to receive(:update_settings).with('i-test', new_settings) - - expect { - cloud.detach_disk('i-test', 'non-exist-volume-id') - }.to_not raise_error - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/dynamic_network_spec.rb b/src/bosh_openstack_cpi/spec/unit/dynamic_network_spec.rb deleted file mode 100644 index 8d454409c..000000000 --- a/src/bosh_openstack_cpi/spec/unit/dynamic_network_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::DynamicNetwork do - subject { Bosh::OpenStackCloud::DynamicNetwork.new('default', dynamic_network_with_netid_spec) } - - context 'allowed_address_pair is configured' do - it 'raises an error' do - allowed_address_pairs = '10.0.0.10' - subject.allowed_address_pairs = allowed_address_pairs - - expect { - subject.prepare(nil, []) - }.to raise_error Bosh::Clouds::CloudError, "Network with id '#{subject.net_id}' is a dynamic network. VRRP is not supported for dynamic networks" - end - end - - context 'allowed_address_pair is not configured' do - it 'does not raise an error' do - expect { - subject.prepare(nil, []) - }.to_not raise_error - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/excon_logging_instrumentor_spec.rb b/src/bosh_openstack_cpi/spec/unit/excon_logging_instrumentor_spec.rb deleted file mode 100644 index b90e50c68..000000000 --- a/src/bosh_openstack_cpi/spec/unit/excon_logging_instrumentor_spec.rb +++ /dev/null @@ -1,273 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::ExconLoggingInstrumentor do - describe '.instrument' do - let(:name) { 'foo' } - let(:params) { { foo: 'bar' } } - let(:logger) { instance_double('Logger') } - - subject { Bosh::OpenStackCloud::ExconLoggingInstrumentor } - - before do - allow(Bosh::Clouds::Config).to receive(:logger).and_return(logger) - allow(logger).to receive(:debug) - end - - it 'logs requests' do - subject.instrument(name, params) - - expect(logger).to have_received(:debug).with("#{name} #{params}") - end - - it 'yields' do - expect { |b| Bosh::OpenStackCloud::ExconLoggingInstrumentor.instrument(name, params, &b) }.to yield_control - end - - it 'does not manipulate the original hash' do - params_with_body = { body: '{}' } - old_body = params_with_body.fetch(:body) - - subject.instrument(name, params_with_body) - - expect(params_with_body.fetch(:body)).to be(old_body) - end - - context 'excon log messages are pretty' do - before(:each) do - subject.instrument(name, params) - end - - context 'response' do - let(:name) { 'excon.response' } - let(:params) { - { - port: '1234', - host: 'hostname.fqdn', - path: '/api', - headers: { 'fake' => 'fake-fake' }, - status_line: '200 1/1', - body: { text: 'fake-text' }.to_json, - cookies: [], - status: '200', - reason_phrase: 'OK', - other_important_key: '10.1', - } - } - - it 'returns a formatted response message' do - expect(logger).to have_received(:debug).with('excon.response 200 1/1 /api params: {"port":"1234","host":"hostname.fqdn","other_important_key":"10.1"} headers: {"fake":"fake-fake"} body: {"text":"fake-text"}') - end - end - - context 'request' do - let(:name) { 'excon.request' } - let(:params) { - { - ciphers: 'fake-ciphers', - connection: 'connection-class', - stack: 'stack-class', - hostname: 'fake-host.fqdn', - method: 'PUT', - scheme: 'https', - port: '8774', - path: '/v1', - headers: { 'fake-headers' => 'fake-value' }, - body: '{"fake-body":"fake-value"}', - other_important_key: '10.1', - } - } - - it 'returns a formatted message' do - expect(logger).to have_received(:debug).with('excon.request PUT https://fake-host.fqdn:8774/v1 params: {"other_important_key":"10.1"} headers: {"fake-headers":"fake-value"} body: {"fake-body":"fake-value"}') - end - end - - context 'error' do - let(:name) { 'excon.error' } - let(:error_double) { - double('excon_error', response: double('response', data: { - body: '{"itemNotFound": {"message": "Volume 123 could not be found.", "code": 404}}', - cookies: [], - headers: { 'fake-headers' => 'fake-value' }, - host: 'fake-host.fqdn', - local_address: '1.2.2', - local_port: 123, - path: '/v2', - port: 8776, - reason_phrase: 'Not Found', - remote_ip: '1.1.2', - status: 404, - status_line: "HTTP/1.1 404 Not Found\r\n", - })) - } - let(:params) { { error: error_double } } - - it 'returns a formatted response message' do - expect(logger).to have_received(:debug).with('excon.error HTTP/1.1 404 Not Found /v2 params: {"host":"fake-host.fqdn","local_address":"1.2.2","local_port":123,"port":8776,"remote_ip":"1.1.2"} headers: {"fake-headers":"fake-value"} body: {"itemNotFound": {"message": "Volume 123 could not be found.", "code": 404}}') - end - end - end - - context 'with sensitive data' do - context 'with non-json text in body' do - let(:params) { - { - body: 'non-json', - headers: { - 'Content-Type' => 'text/plain', - }, - } } - - it 'returns original body' do - redacted_params = subject.redact(params) - - expect(redacted_params[:body]).to eq(params[:body]) - end - end - - context 'when content is not valid JSON' do - let(:params) { - { - body: 'non-json', - headers: { - 'Content-Type' => 'application/json', - }, - } } - - it 'returns the original body' do - redacted_params = subject.redact(params) - - expect(redacted_params[:body]).to eq(params[:body]) - end - end - - context 'with v2 password in body' do - let(:body) { - { - auth: { - passwordCredentials: { - password: 'my-password', - }, - }, - } } - let(:params) { - { - body: JSON.dump(body), - headers: { - 'Content-Type' => 'application/json', - }, - } } - - it 'redacts v2 password' do - redacted_params = subject.redact(params) - - parsed_body = JSON.parse(redacted_params[:body]) - expect(parsed_body['auth']['passwordCredentials']['password']).to eq('') - end - end - - context 'with v3 password in body' do - let(:body) { - { - auth: { - identity: { - password: { - user: { - password: 'my-password', - }, - }, - }, - }, - } } - let(:params) { - { - body: JSON.dump(body), - headers: { - 'Content-Type' => 'application/json', - }, - } } - - it 'redacts v3 password' do - redacted_params = subject.redact(params) - - parsed_body = JSON.parse(redacted_params[:body]) - expect(parsed_body['auth']['identity']['password']['user']['password']).to eq('') - end - end - - context 'with application_credential.secret in body' do - let(:body) { - { - auth: { - identity: { - application_credential: { - secret: 'my-application-credential-secret', - }, - }, - }, - } } - let(:params) { - { - body: JSON.dump(body), - headers: { - 'Content-Type' => 'application/json', - }, - } } - - it 'redacts application_credential.secret' do - redacted_params = subject.redact(params) - - parsed_body = JSON.parse(redacted_params[:body]) - expect(parsed_body['auth']['identity']['application_credential']['secret']).to eq('') - end - end - - context 'with server.user_data in body' do - let(:body) { - { - server: { - user_data: 'user data', - }, - } } - let(:params) { - { - body: JSON.dump(body), - headers: { - 'Content-Type' => 'application/json', - }, - } } - - it 'redacts server.user_data' do - redacted_params = subject.redact(params) - - parsed_body = JSON.parse(redacted_params[:body]) - expect(parsed_body['server']['user_data']).to eq('') - end - end - - context 'with x-auth-token in header' do - let(:headers) { - { - 'X-Auth-Token' => 'token', - } } - let(:params) { { headers: } } - - it 'redacts params' do - redacted_params = subject.redact(params) - - expect(redacted_params[:headers]['X-Auth-Token']).to eq('') - end - end - end - - context 'with non-string body (could be File)' do - it 'does nothing' do - params_with_file_body = { body: 5 } - - subject.instrument(name, params_with_file_body) - - expect(logger).to have_received(:debug).with("#{name} #{params_with_file_body}") - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/floating_ip_spec.rb b/src/bosh_openstack_cpi/spec/unit/floating_ip_spec.rb deleted file mode 100644 index 688f68a99..000000000 --- a/src/bosh_openstack_cpi/spec/unit/floating_ip_spec.rb +++ /dev/null @@ -1,253 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::FloatingIp do - let(:logger) { double('logger', info: nil) } - - before(:each) { - allow(Bosh::Clouds::Config).to receive(:logger).and_return(logger) - allow(openstack).to receive(:with_openstack) { |&block| block.call } - } - let(:network) { double('network', get_server: nil, list_floating_ips: nil, associate_floating_ip: nil, disassociate_floating_ip: nil, get_port: nil, ports: nil) } - let(:compute) { double('compute', addresses: nil) } - let(:openstack) { double('openstack', use_nova_networking?: use_nova_networking, network:, compute:) } - - context 'when `use_nova_networking=false`' do - let(:use_nova_networking) { false } - - let(:floating_ip_port_id) { 'old-server-port-id' } - - let(:floating_ips_response) { - Struct.new(:body).new({ - 'floatingips' => floating_ips, - }) - } - let(:floating_ips) { - [ - { - 'floating_network_id' => 'some-floating-network-id', - 'router_id' => 'some-router-id', - 'fixed_ip_address' => 'some-fixed-ip-address', - 'floating_ip_address' => '1.2.3.4', - 'tenant_id' => 'some-tenant-id', - 'status' => 'some-status', - 'port_id' => floating_ip_port_id, - 'id' => 'floating-ip-id', - }, - ] - } - - let(:get_port_response) { - Struct.new(:body).new({ - 'port' => port, - }) - } - - let(:port_device_id) { nil } - - let(:port) { - { - 'device_id' => port_device_id, - } } - before(:each) { - allow(network).to receive(:get_port).with(floating_ip_port_id).and_return(get_port_response) - } - - let(:old_server) { {} } - let(:get_server_response) { - Struct.new(:body).new({ - 'server' => old_server, - }) - } - before(:each) { - allow(compute).to receive(:get_server_details).and_return(get_server_response) - } - - let(:port_collection) { double('Fog::OpenStack::Network::Ports', all: [port_model]) } - let(:port_model) { double('Fog::OpenStack::Network::Port', id: 'port-id') } - - before(:each) { - allow(network).to receive(:ports).and_return(port_collection) - } - - describe '.port_attached?' do - context 'when the floating_ip port_id is nil' do - let(:floating_ip) { - { - 'port_id' => nil, - } - } - it 'returns false' do - expect(Bosh::OpenStackCloud::FloatingIp.port_attached?(floating_ip)).to be false - end - end - context 'when the floating_ip port_id is empty' do - let(:floating_ip) { - { - 'port_id' => '', - } - } - it 'returns false' do - expect(Bosh::OpenStackCloud::FloatingIp.port_attached?(floating_ip)).to be false - end - end - context 'when the floating_ip port_id is non-empty' do - let(:floating_ip) { - { - 'port_id' => floating_ip_port_id, - } - } - it 'returns true' do - expect(Bosh::OpenStackCloud::FloatingIp.port_attached?(floating_ip)).to be true - end - end - end - - describe '.reassociate' do - before(:each) { - allow(network).to receive(:list_floating_ips).with('floating_ip_address' => '1.2.3.4').and_return(floating_ips_response) - } - - let(:server) { - Struct.new(:id, :name).new('server-id', 'server-name') - } - - context 'when the floating ip is already associated with a port' do - let(:port_device_id) { old_server['id'] } - - let(:old_server) { - { - 'id' => 'old-server-id', - 'name' => 'old-server', - } - } - - it 'disassociates the floating ip and associates it with the given server' do - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - - expect(network).to have_received(:list_floating_ips).with('floating_ip_address' => '1.2.3.4') - - expect(network).to have_received(:disassociate_floating_ip).with('floating-ip-id') - expect(logger).to have_received(:info).with("Disassociating floating IP '1.2.3.4' from server 'old-server (old-server-id)'") - expect(port_collection).to have_received(:all).with(device_id: 'server-id', network_id: 'network-id') - expect(logger).to have_received(:info).with("Associating floating IP '1.2.3.4' with server 'server-name (server-id)'") - expect(network).to have_received(:associate_floating_ip).with('floating-ip-id', 'port-id') - end - end - - context 'when the floating ip is not associated with a port' do - let(:floating_ip_port_id) { nil } - - it 'assigns the given floating ip to the given server' do - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - - expect(network).to have_received(:list_floating_ips).with('floating_ip_address' => '1.2.3.4') - expect(port_collection).to have_received(:all).with(device_id: 'server-id', network_id: 'network-id') - - expect(network).to have_received(:associate_floating_ip).with('floating-ip-id', 'port-id') - end - - context 'when multiple ports are connected to the external network' do - before(:each) do - allow(port_collection).to receive(:all).and_return([double('other_port', id: 'other-port-id'), port_model]) - end - - it 'assigns the given floating ip to the first port' do - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - - expect(network).to have_received(:list_floating_ips).with('floating_ip_address' => '1.2.3.4') - expect(port_collection).to have_received(:all).with(device_id: 'server-id', network_id: 'network-id') - - expect(network).to have_received(:associate_floating_ip).with('floating-ip-id', 'other-port-id') - end - end - end - - context "openstack doesn't find the given floating ip" do - let(:floating_ips) { [] } - - it 'raises a cloud error' do - expect { - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - }.to raise_error Bosh::Clouds::CloudError, "Floating IP '1.2.3.4' not allocated" - end - end - - context 'openstack finds the given floating ip more than once' do - let(:floating_ips) { [{ 'floating_network_id' => 'id1' }, { 'floating_network_id' => 'id2' }] } - - it 'raises a cloud error' do - expect { - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - }.to raise_error Bosh::Clouds::CloudError, "Floating IP '1.2.3.4' found in multiple networks: 'id1', 'id2'" - end - end - - context 'server has no port in the given network' do - before(:each) do - allow(port_collection).to receive(:all).and_return([]) - end - it 'raises a cloud error' do - expect { - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id-not-matching-the-port') - }.to raise_error Bosh::Clouds::CloudError, "Server has no port in network 'network-id-not-matching-the-port'" - end - end - end - end - - context 'when `use_nova_networking=true`' do - let(:use_nova_networking) { true } - - describe '.reassociate' do - let(:server) { - Struct.new(:id, :name).new('server-id', 'server-name') - } - - before(:each) do - allow(compute).to receive(:addresses).and_return([address]) - end - - let(:server) { double('server', id: 'i-test') } - - context 'ip already associated with an instance' do - let(:address) do - double('address', :server= => nil, :id => 'network_b', :ip => '1.2.3.4', :instance_id => 'old-instance-id') - end - - it 'adds floating ip to the server for vip network' do - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - - expect(logger).to have_received(:info).with("Disassociating floating IP '1.2.3.4' from server 'old-instance-id'") - expect(address).to have_received(:server=).with(nil) - expect(logger).to have_received(:info).with("Associating floating IP '1.2.3.4' with server 'i-test'") - expect(address).to have_received(:server=).with(server) - end - end - - context 'ip not already associated with an instance' do - let(:address) do - double('address', :server= => nil, :id => 'network_b', :ip => '1.2.3.4', :instance_id => nil) - end - - it 'adds free floating ip to the server for vip network' do - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - - expect(address).to_not have_received(:server=).with(nil) - expect(address).to have_received(:server=).with(server) - end - end - - context 'no floating IP allocated for vip network' do - let(:address) do - double('address', :server= => nil, :id => 'network_b', :ip => '10.0.0.2') - end - - it 'fails' do - expect { - Bosh::OpenStackCloud::FloatingIp.reassociate(openstack, '1.2.3.4', server, 'network-id') - }.to raise_error Bosh::Clouds::CloudError, /Floating IP .* not allocated/ - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/has_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/has_disk_spec.rb deleted file mode 100644 index e8f782791..000000000 --- a/src/bosh_openstack_cpi/spec/unit/has_disk_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - describe 'has_disk?' do - context 'when disk is found' do - let(:disk) { instance_double('Fog::Volume') } - - it 'returns true' do - cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.volume.volumes).to receive(:get).with('fake-disk-uuid').and_return(disk) - end - - expect(cloud.has_disk?('fake-disk-uuid')).to be(true) - end - end - - context 'when disk is not found' do - it 'returns false' do - cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.volume.volumes).to receive(:get).with('fake-disk-uuid').and_return(nil) - end - - expect(cloud.has_disk?('fake-disk-uuid')).to be(false) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/has_vm_spec.rb b/src/bosh_openstack_cpi/spec/unit/has_vm_spec.rb deleted file mode 100644 index 64c5b9476..000000000 --- a/src/bosh_openstack_cpi/spec/unit/has_vm_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - it 'has_vm? returns true if OpenStack server exists' do - server = double('server', id: 'i-foobar', state: :active) - cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - end - expect(cloud.has_vm?('i-foobar')).to be(true) - end - - it "has_vm? returns false if OpenStack server doesn't exists" do - cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(nil) - end - expect(cloud.has_vm?('i-foobar')).to be(false) - end - - it 'has_vm? returns false if OpenStack server state is :terminated' do - server = double('server', id: 'i-foobar', state: :terminated) - cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - end - expect(cloud.has_vm?('i-foobar')).to be(false) - end - - it 'has_vm? returns false if OpenStack server state is :deleted' do - server = double('server', id: 'i-foobar', state: :deleted) - cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - end - expect(cloud.has_vm?('i-foobar')).to be(false) - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/helpers_spec.rb b/src/bosh_openstack_cpi/spec/unit/helpers_spec.rb deleted file mode 100644 index 0f3a87b8a..000000000 --- a/src/bosh_openstack_cpi/spec/unit/helpers_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Helpers do - let(:logger) { double('logger', error: nil) } - - let(:helpers_user) do - Class.new do - include Bosh::OpenStackCloud::Helpers - def initialize(logger) - @logger = logger - end - end - end - - subject { - helpers_user.new(logger) - } - - describe '.fail_on_error' do - context 'when no error is given' do - it 'does not raise an error' do - expect { - subject.fail_on_error - }.not_to raise_error - end - end - - context 'when error is nil' do - it 'does not raise an error' do - expect { - subject.fail_on_error(nil) - }.not_to raise_error - end - end - - context 'when a single error is given' do - it 'creates a cloud error with the message of the single error' do - expect { - subject.fail_on_error(Bosh::OpenStackCloud::Helpers::ErrorWrapper.new(StandardError.new('error1'))) - }.to raise_error(Bosh::Clouds::CloudError, 'error1') - end - end - - context 'when multiple errors' do - it 'creates a cloud error with joined error messages' do - expect { - subject.fail_on_error( - Bosh::OpenStackCloud::Helpers::ErrorWrapper.new(StandardError.new('error1')), - Bosh::OpenStackCloud::Helpers::ErrorWrapper.new(StandardError.new('error2')), - ) - }.to raise_error(Bosh::Clouds::CloudError, "Multiple cloud errors occurred:\nerror1\nerror2") - end - - it 'logs all errors' do - allow(logger).to receive(:error) - error_wrappers = %w[error1 error2].map do |text| - error = StandardError.new(text) - error.set_backtrace("backtrace #{text}") - Bosh::OpenStackCloud::Helpers::ErrorWrapper.new(error) - end - - expect { - subject.fail_on_error(*error_wrappers) - }.to raise_error(Bosh::Clouds::CloudError) - - expect(logger).to have_received(:error).with(error_wrappers[0].error) - expect(logger).to have_received(:error).with(error_wrappers[0].error.backtrace) - expect(logger).to have_received(:error).with(error_wrappers[1].error) - expect(logger).to have_received(:error).with(error_wrappers[1].error.backtrace) - end - end - end - - describe '.catch_error' do - it 'catches an error and return the error object' do - error_wrapper = nil - expected_error = StandardError.new('BAAAM!') - - expect { - error_wrapper = subject.catch_error { raise expected_error } - }.to_not raise_error - - expect(error_wrapper.error).to eq(expected_error) - end - - it 'returns nil if no exception given' do - error = nil - - expect { - error = subject.catch_error { - # intentionally empty - } - }.to_not raise_error - - expect(error).to eq(nil) - end - - context 'when a message prefix is given' do - it 'prefixes the error message' do - initial_error = StandardError.new('BAAM!') - prefix = 'My current situation' - - error_wrapper = subject.catch_error(prefix) { raise initial_error } - - expect(error_wrapper.message).to eq("#{prefix}: BAAM!") - expect(error_wrapper.error.backtrace).to eq(initial_error.backtrace) - end - - context 'when Excon::Error::Socket is raised' do - before(:each) do - allow(logger).to receive(:error) - end - - it 'does return the same exception and logs prefix' do - initial_error = Excon::Error::Socket.new - prefix = 'Does not raise undefined method message' - - error_wrapper = subject.catch_error(prefix) { raise initial_error } - - expect(error_wrapper.error).to be(initial_error) - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/instance_type_mapper_spec.rb b/src/bosh_openstack_cpi/spec/unit/instance_type_mapper_spec.rb deleted file mode 100644 index cba43b3eb..000000000 --- a/src/bosh_openstack_cpi/spec/unit/instance_type_mapper_spec.rb +++ /dev/null @@ -1,169 +0,0 @@ -require 'spec_helper' -require 'fog/openstack/compute/models/flavors' - -describe Bosh::OpenStackCloud::InstanceTypeMapper do - let(:mapper) { described_class.new } - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'too_small', vcpus: 1, ram: 2047, disk: 2, ephemeral: 49, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'too_big', vcpus: 3, ram: 2049, disk: 4, ephemeral: 51, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'just_right', vcpus: 2, ram: 2048, disk: 3, ephemeral: 50, disabled: false), - ] - end - - context 'when user default root disk properties (`boot_from_volume` set to false or unset)' do - context 'when flavors have adequate vCPUs/RAM/root disk/ephemeral disk' do - it 'returns the smallest flavor that meets the vCPU/RAM/root disk/ephemeral disk requirements' do - requirements = { - 'ram' => 2048, - 'cpu' => 2, - 'ephemeral_disk_size' => 10 * 1024, - } - expect(mapper.map(requirements:, flavors:)['instance_type']).to eq('just_right') - end - end - - context 'when adequate flavors only differ by root disk/ephemeral disk sizes' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'no_good', vcpus: 1, ram: 2048, disk: 2, ephemeral: 10, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'good_but_big', vcpus: 1, ram: 2048, disk: 3, ephemeral: 13, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'good_yet_big', vcpus: 1, ram: 2048, disk: 4, ephemeral: 12, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '4', name: 'second_best', vcpus: 1, ram: 2048, disk: 5, ephemeral: 10, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '5', name: 'best', vcpus: 1, ram: 2048, disk: 4, ephemeral: 11, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '6', name: 'good_and_big', vcpus: 1, ram: 2048, disk: 5, ephemeral: 11, disabled: false), - ] - end - it 'returns the flavor that has the smallest total disk usage, and, in the event of a tie, the smallest root disk' do - requirements = { - 'ram' => 2048, - 'cpu' => 1, - 'ephemeral_disk_size' => 10 * 1024, - } - expect(mapper.map(requirements:, flavors:)['instance_type']).to eq('best') - end - end - - context 'when no flavor meets the ephemeral requirements but the root disk is big enough to accommodate' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'too_small', vcpus: 1, ram: 2048, disk: 3, ephemeral: 9, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'too_small', vcpus: 1, ram: 2048, disk: 12, ephemeral: 9, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'zero_ephem', vcpus: 2, ram: 2048, disk: 14, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '4', name: 'big_root', vcpus: 1, ram: 2048, disk: 13, ephemeral: 1, disabled: false), - ] - end - it 'returns the flavor that has the smallest big-enough root disk and has zero-size ephemeral disk' do - requirements = { - 'ram' => 2048, - 'cpu' => 1, - 'ephemeral_disk_size' => 10 * 1024, - } - expect(mapper.map(requirements:, flavors:)['instance_type']).to eq('zero_ephem') - end - end - - context 'when flavors are inadequate' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'way_too_small', vcpus: 1, ram: 2048, disk: 10, ephemeral: 47, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'too_small', vcpus: 1, ram: 2048, disk: 10, ephemeral: 48, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'barely_too_small', vcpus: 1, ram: 2048, disk: 10, ephemeral: 49, disabled: false), - ] - end - it 'raises an error' do - requirements = { - 'ram' => 2048, - 'cpu' => 1, - 'ephemeral_disk_size' => 50 * 1024, - } - expect { mapper.map(requirements:, flavors:)['instance_type'] }.to raise_error(/Unable to meet requested VM requirements/) - end - end - end - - context 'when user sets root disk properties (`boot_from_volume` set to true)' do - context 'when only flavors with ephemeral disks exist' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'big_enough_but_with_ephemeral_disk', vcpus: 1, ram: 2048, disk: 1, ephemeral: 1, disabled: false), - ] - end - it 'raises an error even if ephemeral disk size would be big enough' do - requirements = { - 'ram' => 2048, - 'cpu' => 1, - 'ephemeral_disk_size' => 1 * 1024, - } - expect { mapper.map(requirements:, flavors:, boot_from_volume: true)['instance_type'] }.to raise_error(/Unable to meet requested VM requirements/) - end - end - - context 'when the flavors have the same CPU and RAM specs' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'root_disk_small', vcpus: 2, ram: 2048, disk: 2, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'root_disk_medium', vcpus: 2, ram: 2048, disk: 13, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'root_disk_big', vcpus: 2, ram: 2048, disk: 20, ephemeral: 0, disabled: false), - ] - end - it 'returns the smallest flavor and sets root_disk size to OS size plus ephemeral_disk_size' do - ephemeral_disk_size = 10 - root_disk_size = 3 - - requirements = { - 'ram' => 2048, - 'cpu' => 2, - 'ephemeral_disk_size' => ephemeral_disk_size * 1024, - } - cloud_props = mapper.map(requirements:, flavors:, boot_from_volume: true) - expect(cloud_props['instance_type']).to eq('root_disk_small') - expect(cloud_props).to have_key('root_disk') - expect(cloud_props['root_disk']['size']).to eq(root_disk_size + ephemeral_disk_size) #=> 13 - end - end - - context 'when flavors are adequate (except root is too small)' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'too_small', vcpus: 1, ram: 1024, disk: 2, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'just_right', vcpus: 2, ram: 2048, disk: 2, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'too_big', vcpus: 3, ram: 4096, disk: 2, ephemeral: 0, disabled: false), - ] - end - it 'returns the smallest flavor, setting the root_disk large enough for the OS and ephemeral disk' do - ephemeral_disk_size = 10 - root_disk_size = 3 - - requirements = { - 'ram' => 2048, - 'cpu' => 2, - 'ephemeral_disk_size' => ephemeral_disk_size * 1024, - } - - cloud_props = mapper.map(requirements:, flavors:, boot_from_volume: true) - expect(cloud_props['instance_type']).to eq('just_right') - expect(cloud_props).to have_key('root_disk') - expect(cloud_props['root_disk']['size']).to eq(root_disk_size + ephemeral_disk_size) #=> 13 - end - end - - context 'when flavors are inadequate' do - let(:flavors) do - [ - instance_double(Fog::OpenStack::Compute::Flavor, id: '1', name: 'way_too_small', vcpus: 1, ram: 1024, disk: 20, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '2', name: 'too_small', vcpus: 1, ram: 2046, disk: 20, ephemeral: 0, disabled: false), - instance_double(Fog::OpenStack::Compute::Flavor, id: '3', name: 'barely_too_small', vcpus: 1, ram: 2047, disk: 20, ephemeral: 0, disabled: false), - ] - end - it 'raises an error' do - requirements = { - 'ram' => 2048, - 'cpu' => 1, - 'ephemeral_disk_size' => 10 * 1024, - } - expect { mapper.map(requirements:, flavors:)['instance_type'] }.to raise_error(/Unable to meet requested VM requirements/) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/loadbalancer_configurator_spec.rb b/src/bosh_openstack_cpi/spec/unit/loadbalancer_configurator_spec.rb deleted file mode 100644 index ded71d5dc..000000000 --- a/src/bosh_openstack_cpi/spec/unit/loadbalancer_configurator_spec.rb +++ /dev/null @@ -1,635 +0,0 @@ -require 'spec_helper' -require 'fog/openstack/compute/models/server' - -describe Bosh::OpenStackCloud::LoadbalancerConfigurator do - shared_examples 'changing load balancer resource' do - context 'when load balancer is in status PENDING_UPDATE' do - let(:body) { { 'NeutronError' => { 'message' => 'Invalid state PENDING_UPDATE of loadbalancer resource 1234-abcd' } } } - let(:response) { instance_double('response', body: JSON.dump(body)) } - let(:state_timeout) { 10 } - - before do - Timecop.freeze - allow(network).to receive(openstack_method_name, &raise_times(2)) - allow(openstack).to receive(:state_timeout).and_return(state_timeout) - end - - after do - Timecop.return - end - - it 'attempts to change resource again after load balancer has become active again' do - expect(network).to receive(openstack_method_name).exactly(3) - expect(openstack).to receive(:wait_resource).exactly(4) - - action.call - end - - it 'logs the attempts' do - allow(logger).to receive(:debug) - expected_message = "Changing load balancer resource failed with 'omg pending_update', unsuccessful attempts: " - - action.call - - expect(logger).to have_received(:debug) - .with(expected_message + "'1'") - .with(expected_message + "'2'") - end - - context 'when state_timeout has been reached exactly' do - let(:expected_retries) { 2 } - let(:start_time) { Time.local(2017) } - let(:time_increment) { 1 } - let(:state_timeout) { time_increment * expected_retries } - - before do - allow(network).to receive(openstack_method_name, &raise_times(expected_retries)) - Timecop.freeze(start_time) - allow(openstack).to receive(:wait_resource) do - Timecop.freeze(Time.now + time_increment) - end - end - - it 'fails with a CloudError, containing elapsed time and number of attempts' do - expect { - action.call - }.to raise_error Bosh::Clouds::CloudError, /Reason: Bosh::Clouds::CloudError Failed after #{expected_retries}.0s with #{expected_retries} attempts with 'omg pending_update'/ - - expect(network).to have_received(openstack_method_name).exactly(expected_retries) - expect(openstack).to have_received(:wait_resource).exactly(expected_retries) - end - end - - def raise_times(times) - times_called = 0 - proc { - times_called += 1 - raise Excon::Error::Conflict.new('omg pending_update', '', response) if times_called <= times - success_response - } - end - end - end - - subject(:subject) { Bosh::OpenStackCloud::LoadbalancerConfigurator.new(openstack, logger) } - let(:logger) { instance_double(Logger, debug: nil) } - let(:network_spec) { {} } - let(:options) { { 'auth_url' => '' } } - let(:openstack) { Bosh::OpenStackCloud::Openstack.new(options) } - let(:network) { double('network', list_lbaas_pools: loadbalancer_pools_response, get_lbaas_listener: lb_listener, get_lbaas_pool: lb_pool) } - let(:lb_listener) { double('lb_listener', body: { 'listener' => { 'loadbalancers' => [{ 'id' => 'loadbalancer-id' }] } }) } - let(:lb_pool) { double('lb_pool', body: { 'pool' => { 'loadbalancers' => [{ 'id' => 'loadbalancer-id' }] } }) } - let(:loadbalancer_pools_response) { double('response', body:) } - let(:body) { - { - 'pools' => [ - { 'name' => 'my-lb-pool', 'id' => 'pool-id' }, - ], - } - } - let(:server) { instance_double(Fog::OpenStack::Compute::Server, id: 1234) } - let(:pool) { - { - 'name' => 'my-lb-pool', - 'port' => 8080, - } - } - - before(:each) do - allow(openstack).to receive(:network).and_return(network) - allow(openstack).to receive(:wait_resource) - end - - describe '#create_pool_memberships' do - context 'when a list of loadbalancer_pools is given' do - let(:load_balancer_pools) { - [ - { 'name' => 'my-pool-1', 'port' => 443 }, - { 'name' => 'my-pool-2', 'port' => 8080 }, - ] - } - - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'sub-net-id', ip: '10.10.10.10'), - 'vip_network' => vip_network_spec, - } } - - let(:lb_member) { double('lb_member', body: { 'member' => { 'id' => 'membership-id' } }) } - - it 'creates the memberships and returns the corresponding server_tags' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:matching_gateway_subnet_ids_for_ip).and_return(['sub-net-id']) - allow(network).to receive(:create_lbaas_pool_member).and_return(lb_member) - - server_tags = subject.create_pool_memberships(server, network_spec, load_balancer_pools) - - expect(server_tags).to eq( - 'lbaas_pool_1' => 'pool-id/membership-id', - 'lbaas_pool_2' => 'pool-id/membership-id', - ) - end - end - - context 'when the given list is empty' do - it 'does nothing' do - allow(network).to receive(:create_lbaas_pool_member) - - server_tags = subject.create_pool_memberships(server, network_spec, []) - - expect(server_tags).to eq({}) - expect(network).to_not have_received(:create_lbaas_pool_member) - end - end - end - - describe '#create_membership' do - let(:lb_member) { double('lb_member', body: { 'member' => { 'id' => 'member-id' } }) } - - before(:each) do - allow(logger).to receive(:info) - allow(network).to receive(:create_lbaas_pool_member).and_return(lb_member) - end - - it 'waits for loadbalancer to be active and returns the id of the existing membership' do - loadbalancer = instance_double(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource, 'loadbalancer', provisioning_status: 'ACTIVE') - allow(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to receive(:new).and_return(loadbalancer) - - membership_id = subject.create_membership('pool-id', '10.0.0.1', '8080', 'subnet-id') - - expect(membership_id).to eq('member-id') - expect(network).to have_received(:get_lbaas_pool).with('pool-id') - expect(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to have_received(:new).with('loadbalancer-id', openstack) - expect(openstack).to have_received(:wait_resource).with(loadbalancer, :active, :provisioning_status).twice - end - - it 'runs into time out while waiting for load balancer to be active before creating pool member' do - loadbalancer = instance_double(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource, 'loadbalancer', provisioning_status: 'PENDING_UPDATE') - allow(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to receive(:new).and_return(loadbalancer) - allow(openstack).to receive(:wait_resource).with(loadbalancer, :active, :provisioning_status).and_raise(Bosh::Clouds::CloudError.new('Timed out waiting for load balancer to be ACTIVE')) - - expect { - subject.create_membership('pool-id', '10.0.0.1', '8080', 'subnet-id') - }.to raise_error(Bosh::Clouds::CloudError, /Timed out waiting for load balancer to be ACTIVE/) - - expect(network).to have_received(:get_lbaas_pool).with('pool-id') - expect(network).to_not have_received(:create_lbaas_pool_member) - expect(openstack).to have_received(:wait_resource).with(loadbalancer, :active, :provisioning_status).once - end - - context 'when lbaas resource cannot be found' do - before(:each) do - allow(subject).to receive(:loadbalancer_id).and_raise(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotFound, 'some-error-message') - end - - it 'raises an error' do - expect { - subject.create_membership('pool-id', '10.0.0.1', '8080', 'subnet-id') - }.to raise_error(Bosh::Clouds::VMCreationFailed, 'some-error-message') - - expect(network).to_not have_received(:create_lbaas_pool_member) - end - end - - context 'when lbaas resources are not in a valid state e.g. pool has more than one loadbalancer associated' do - before(:each) do - allow(subject).to receive(:loadbalancer_id).and_raise(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotSupportedConfiguration, 'some-error-message') - end - - it 'raises an error' do - expect { - subject.create_membership('pool-id', '10.0.0.1', '8080', 'subnet-id') - }.to raise_error(Bosh::Clouds::VMCreationFailed, 'some-error-message') - expect(network).to_not have_received(:create_lbaas_pool_member) - end - end - - context 'when pool membership cannot be created' do - let(:members) { - double('lb_member', body: { - 'members' => [ - { 'id' => 'member-id-1', 'subnet_id' => 'subnet-id', 'address' => '10.0.0.2', 'protocol_port' => '8080' }, - { 'id' => 'member-id-2', 'subnet_id' => 'subnet-id', 'address' => '10.0.0.1', 'protocol_port' => '8080' }, - ], - }) } - - let(:body) { {} } - let(:response) { instance_double('response', body: JSON.dump(body)) } - - before(:each) do - allow(network).to receive(:create_lbaas_pool_member).and_raise(Excon::Error::Conflict.new('BAM!', '', response)) - allow(network).to receive(:list_lbaas_pool_members).with('pool-id').and_return(members) - end - - context 'when the membership already exists' do - it 'returns the id of the existing membership' do - membership_id = subject.create_membership('pool-id', '10.0.0.1', '8080', 'subnet-id') - - expect(membership_id).to eq('member-id-2') - end - - it 'logs the fact' do - subject.create_membership('pool-id', '10.0.0.1', '8080', 'subnet-id') - - expect(logger).to have_received(:info).with("Load balancer pool membership with pool id 'pool-id', ip '10.0.0.1', and port '8080' already exists. The membership has the id 'member-id-2'.") - end - end - - context 'when the membership supposedly exists, but cannot be matched' do - it 'returns an error' do - expect { - subject.create_membership('pool-id', 'wrong-ip', '8080', 'subnet-id') - }.to raise_error(Bosh::Clouds::CloudError, "Load balancer pool membership with pool id 'pool-id', ip 'wrong-ip', and port '8080' supposedly exists, but cannot be found.") - end - end - - it_behaves_like('changing load balancer resource') do - let(:success_response) { double('lb_member', body: { 'member' => { 'id' => 'member-id' } }) } - let(:openstack_method_name) { :create_lbaas_pool_member } - let(:action) { - -> { subject.create_membership('pool-id', 'wrong-ip', '8080', 'subnet-id') } - } - end - end - end - - describe '#add_vm_to_pool' do - context 'when pool input invalid' do - it 'raises an error' do - expect { - subject.add_vm_to_pool(server, network_spec, 'name' => 'foo') - }.to raise_error(Bosh::Clouds::VMCreationFailed, "VM with id '1234' cannot be attached to load balancer pool 'foo'. Reason: Load balancer pool 'foo' has no port definition") - - expect { - subject.add_vm_to_pool(server, network_spec, 'port' => 8080) - }.to raise_error(Bosh::Clouds::VMCreationFailed, "VM with id '1234' cannot be attached to load balancer pool ''. Reason: Load balancer pool defined without a name") - end - end - - context 'when create_membership raises an error' do - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'sub-net-id', ip: '10.10.10.10'), - } - } - - before(:each) do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:matching_gateway_subnet_ids_for_ip).and_return(['sub-net-id']) - allow(subject).to receive(:create_membership).and_raise(Bosh::Clouds::VMCreationFailed.new(false), 'some-error-message') - end - - it 'raises an VMCreationFailed error' do - expect { - subject.add_vm_to_pool(server, network_spec, pool) - }.to raise_error(Bosh::Clouds::VMCreationFailed, "VM with id '1234' cannot be attached to load balancer pool 'my-lb-pool'. Reason: some-error-message") - end - end - - context 'when load balancer pool can not be associated to the VM' do - context 'when load balancer pool name does not exist' do - let(:body) { { 'pools' => [] } } - - it 'raises an error' do - expect { - subject.add_vm_to_pool(server, network_spec, pool) - }.to raise_error(Bosh::Clouds::VMCreationFailed, "VM with id '1234' cannot be attached to load balancer pool 'my-lb-pool'. Reason: Load balancer pool 'my-lb-pool' does not exist") - end - end - - context 'when load balancer pool name exists multiple times' do - let(:body) { - { - 'pools' => [ - { 'name' => 'my-lb-pool' }, - { 'name' => 'my-lb-pool' }, - ], - } - } - it 'raises an error' do - expect { - subject.add_vm_to_pool(server, network_spec, pool) - }.to raise_error(Bosh::Clouds::VMCreationFailed, "VM with id '1234' cannot be attached to load balancer pool 'my-lb-pool'. Reason: Load balancer pool 'my-lb-pool' exists multiple times. Make sure to use unique naming.") - end - end - end - - context 'when load balancer pool name exist exactly once' do - before(:each) do - allow(network).to receive(:create_lbaas_pool_member).and_return(lb_member) - end - - let(:lb_member) { double('lb_member', body: { 'member' => { 'id' => 'id' } }) } - - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'sub-net-id', ip: '10.10.10.10'), - 'vip_network' => vip_network_spec, - } - } - - it 'adds the VM as a member of the specified pool' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:matching_gateway_subnet_ids_for_ip).and_return(['sub-net-id']) - - loadbalancer_membership = subject.add_vm_to_pool(server, network_spec, pool) - - expect(network).to have_received(:create_lbaas_pool_member).with('pool-id', '10.10.10.10', 8080, subnet_id: 'sub-net-id') - expect(loadbalancer_membership.membership_id).to eq('id') - expect(loadbalancer_membership.port).to eq(8080) - expect(loadbalancer_membership.name).to eq('my-lb-pool') - expect(loadbalancer_membership.pool_id).to eq('pool-id') - end - end - - context 'when ip address cannot be determined' do - before(:each) do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:gateway_ip).and_raise(Bosh::Clouds::VMCreationFailed.new(false), 'Original message.') - end - - it 'raises an error' do - expect { - subject.add_vm_to_pool(server, network_spec, pool) - }.to raise_error(Bosh::Clouds::VMCreationFailed, "VM with id '1234' cannot be attached to load balancer pool 'my-lb-pool'. Reason: Original message.") - end - end - - context 'when multiple subnet ids match' do - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'net-id', ip: '10.10.10.10'), - } - } - - it 'errors' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:matching_gateway_subnet_ids_for_ip).and_return(['subnet-id', 'other-subnet-id']) - expect { - subject.add_vm_to_pool(server, network_spec, pool) - }.to raise_error(Bosh::Clouds::VMCreationFailed, /Reason: In network 'net-id' more than one subnet CIDRs match the IP '10\.10\.10\.10'/) - end - end - - context 'when no subnet id match or no subnet exists' do - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'net-id', ip: '10.10.10.10'), - } - } - - it 'errors' do - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:matching_gateway_subnet_ids_for_ip).and_return([]) - expect { - subject.add_vm_to_pool(server, network_spec, pool) - }.to raise_error(Bosh::Clouds::VMCreationFailed, /Network 'net-id' does not contain any subnet to match the IP '10\.10\.10\.10'/) - end - end - end - - describe '#remove_vm_from_pool' do - let(:pool_id) { 'pool-id' } - let(:membership_id) { 'membership-id' } - - before do - allow(network).to receive(:delete_lbaas_pool_member) - end - - it 'deletes load balancer pool membership' do - loadbalancer = instance_double(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource, 'loadbalancer', provisioning_status: 'ACTIVE') - allow(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to receive(:new).and_return(loadbalancer) - - subject.remove_vm_from_pool(pool_id, membership_id) - - expect(network).to have_received(:delete_lbaas_pool_member).with(pool_id, membership_id) - expect(network).to have_received(:get_lbaas_pool).with('pool-id') - expect(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to have_received(:new).with('loadbalancer-id', openstack) - expect(openstack).to have_received(:wait_resource).with(loadbalancer, :active, :provisioning_status).twice - end - - it 'runs into time out while waiting for load balancer to be active before deleting pool member' do - loadbalancer = instance_double(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource, 'loadbalancer', provisioning_status: 'PENDING_UPDATE') - allow(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to receive(:new).and_return(loadbalancer) - allow(openstack).to receive(:wait_resource).with(loadbalancer, :active, :provisioning_status).and_raise(Bosh::Clouds::CloudError.new('Timed out waiting for load balancer to be ACTIVE')) - - expect { - subject.remove_vm_from_pool(pool_id, membership_id) - }.to raise_error(Bosh::Clouds::CloudError) - - expect(network).to_not have_received(:delete_lbaas_pool_member) - expect(network).to have_received(:get_lbaas_pool).with('pool-id') - expect(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource).to have_received(:new).with('loadbalancer-id', openstack) - expect(openstack).to have_received(:wait_resource).with(loadbalancer, :active, :provisioning_status) - end - - context 'when load balancer resources are not in a valid state e.g. pool has more than one loadbalancer associated' do - before(:each) do - allow(subject).to receive(:loadbalancer_id).and_raise(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotSupportedConfiguration) - end - - it 'raises an error' do - expect { - subject.remove_vm_from_pool(pool_id, membership_id) - }.to raise_error(Bosh::Clouds::CloudError, /Deleting load balancer pool membership with pool_id 'pool-id' and membership_id 'membership-id' failed/) - - expect(network).to_not have_received(:delete_lbaas_pool_member) - end - end - - context 'when load balancer resource cannot be found' do - before(:each) do - allow(subject).to receive(:loadbalancer_id).and_raise(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotFound) - end - - it 'does not raise' do - expect { - subject.remove_vm_from_pool(pool_id, membership_id) - }.to_not raise_error - end - - it 'does not try to delete the membership' do - subject.remove_vm_from_pool(pool_id, membership_id) - - expect(network).to_not have_received(:delete_lbaas_pool_member) - end - - it 'logs error' do - subject.remove_vm_from_pool(pool_id, membership_id) - - expect(logger).to have_received(:debug).with(/Skipping deletion of load balancer pool membership because load balancer resource cannot be found./) - end - end - - context 'when membership not found' do - before do - allow(network).to receive(:delete_lbaas_pool_member).and_raise(Fog::OpenStack::Network::NotFound) - end - - it 'does not raise' do - expect { - subject.remove_vm_from_pool(pool_id, membership_id) - }.to_not raise_error - end - - it 'logs error' do - expect { - subject.remove_vm_from_pool(pool_id, membership_id) - }.to_not raise_error - - expect(logger).to have_received(:debug).with("Skipping deletion of load balancer pool membership. Member with pool_id 'pool-id' and membership_id 'membership-id' does not exist.") - end - end - - context 'when membership deletion fails' do - it 're-raises as CloudError' do - allow(network).to receive(:delete_lbaas_pool_member).and_raise(Fog::OpenStack::Network::Error.new('BOOM!!!')) - - expect { - subject.remove_vm_from_pool(pool_id, membership_id) - }.to raise_error(Bosh::Clouds::CloudError, "Deleting load balancer pool membership with pool_id 'pool-id' and membership_id 'membership-id' failed. Reason: Fog::OpenStack::Network::Error BOOM!!!") - end - end - - it_behaves_like('changing load balancer resource') do - let(:success_response) { double('lb_member', body: { 'member' => { 'id' => 'member-id' } }) } - let(:openstack_method_name) { :delete_lbaas_pool_member } - let(:action) { - -> { subject.remove_vm_from_pool(pool_id, membership_id) } - } - end - end - - describe '#loadbalancer_id' do - let(:pool_id) { 'pool-id' } - - context 'when pool exists' do - it 'returns the loadbalancer_id' do - loadbalancer_id = subject.loadbalancer_id(pool_id) - - expect(loadbalancer_id).to eq('loadbalancer-id') - end - - context 'when pool has no loadbalancer associated' do - let(:lb_pool) { double('lb_pool', body: { 'pool' => { 'loadbalancers' => [] } }) } - - it 'raises an error' do - expect { - subject.loadbalancer_id(pool_id) - }.to raise_error Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotFound, "No load balancers associated with load balancer pool 'pool-id'" - end - end - - context 'when pool has more than one loadbalancer associated' do - let(:lb_pool) { double('lb_pool', body: { 'pool' => { 'loadbalancers' => [{ 'id' => 'loadbalancer-id-1' }, { 'id' => 'loadbalancer-id-2' }] } }) } - - it 'raises an error' do - expect { - subject.loadbalancer_id(pool_id) - }.to raise_error(Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotSupportedConfiguration, "More than one load balancer is associated with load balancer pool 'pool-id'. It is not possible to verify the status of the load balancer responsible for the pool membership.") - end - end - - context 'when pool does not have loadbalancer property (before Newton)' do - context 'when listener does exist' do - let(:lb_pool) { double('lb_pool', body: { 'pool' => { 'loadbalancers' => nil, 'listeners' => [{ 'id' => 'listener-id' }] } }) } - - it 'returns loadbalancer id ' do - loadbalancer_id = subject.loadbalancer_id(pool_id) - - expect(loadbalancer_id).to eq('loadbalancer-id') - expect(network).to have_received(:get_lbaas_listener).with('listener-id') - end - end - - context 'when listener does not exist' do - let(:lb_pool) { double('lb_pool', body: { 'pool' => { 'loadbalancers' => nil, 'listeners' => [] } }) } - - it 'raises an error' do - expect { - subject.loadbalancer_id(pool_id) - }.to raise_error Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotFound, "No listeners associated with load balancer pool 'pool-id'" - end - end - - context 'when multiple listeners do exist' do - let(:lb_pool) do - double('lb_pool', body: { 'pool' => { 'loadbalancers' => nil, 'listeners' => [ - { 'id' => 'listener-id' }, - { 'id' => 'other-listener-id' }, - ] } }) - end - - it 'raises an error' do - expect { - subject.loadbalancer_id(pool_id) - }.to raise_error Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotSupportedConfiguration, "More than one listener is associated with load balancer pool 'pool-id'. It is not possible to verify the status of the load balancer responsible for the pool membership." - end - end - end - end - - context 'when pool does not exist' do - before(:each) do - allow(network).to receive(:get_lbaas_pool).and_raise(Fog::OpenStack::Network::NotFound.new, 'some-error-message') - end - - it 'raises an error' do - expect { - subject.loadbalancer_id(pool_id) - }.to raise_error Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource::NotFound, "Load balancer ID could not be determined because pool with ID 'pool-id' was not found. Reason: some-error-message" - end - end - end - - describe '#cleanup_memberships' do - let(:server_metadata) { - { - 'lbaas_pool_0' => 'pool-id-0/membership-id-0', - 'lbaas_pool_1' => 'pool-id-1/membership-id-1', - 'index' => 0, - 'job' => 'bosh', - } - } - - it 'removes all memberships found in server metadata' do - allow(network).to receive(:delete_lbaas_pool_member).with('pool-id-0', 'membership-id-0') - allow(network).to receive(:delete_lbaas_pool_member).with('pool-id-1', 'membership-id-1') - - subject.cleanup_memberships(server_metadata) - - expect(network).to have_received(:delete_lbaas_pool_member).with('pool-id-0', 'membership-id-0') - expect(network).to have_received(:delete_lbaas_pool_member).with('pool-id-1', 'membership-id-1') - end - - it 're-raises the exception' do - allow(network).to receive(:delete_lbaas_pool_member).and_raise(Fog::OpenStack::Network::Error.new('BOOM!!!')) - - expect { - subject.cleanup_memberships(server_metadata) - }.to raise_error(Bosh::Clouds::CloudError) - end - end - - describe Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource do - let(:subject) { Bosh::OpenStackCloud::LoadbalancerConfigurator::LoadBalancerResource.new('loadbalancer-id', openstack) } - let(:loadbalancer) { double('loadbalancer', body: { 'loadbalancer' => { 'id' => 'loadbalancer-id', 'provisioning_status' => 'ACTIVE' } }) } - - describe '#provisioning_status' do - it 'returns the status of the loadbalancer' do - allow(network).to receive(:get_lbaas_loadbalancer).and_return(loadbalancer) - - provisioning_status = subject.provisioning_status - - expect(provisioning_status).to eq('ACTIVE') - expect(network).to have_received(:get_lbaas_loadbalancer).with('loadbalancer-id') - end - end - - describe '#id' do - it 'returns the id of the loadbalancer' do - expect(subject.id).to eq('loadbalancer-id') - end - end - - describe '#reload' do - it 'returns the id of the loadbalancer' do - expect(subject.reload).to eq(true) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/manual_network_spec.rb b/src/bosh_openstack_cpi/spec/unit/manual_network_spec.rb deleted file mode 100644 index bfd09adbf..000000000 --- a/src/bosh_openstack_cpi/spec/unit/manual_network_spec.rb +++ /dev/null @@ -1,260 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::ManualNetwork do - subject { Bosh::OpenStackCloud::ManualNetwork.new(network_name, network_spec) } - let(:openstack) { double(Bosh::OpenStackCloud::Openstack) } - - before { allow(openstack).to receive(:with_openstack) { |&block| block.call } } - - describe '#initialize' do - context 'when spec is not a hash' do - let(:network_name) { 'default' } - let(:network_spec) { [] } - it 'should fail' do - expect { - subject - }.to raise_error ArgumentError, /Invalid spec, Hash expected/ - end - end - - context 'when spec is a hash' do - let(:network_name) { 'default' } - let(:network_spec) { - network_spec = manual_network_spec - network_spec['ip'] = '172.20.214.10' - network_spec - } - - it 'should set the IP' do - expect(subject.private_ip).to eq('172.20.214.10') - end - end - end - - describe '#prepare' do - let(:network_name) { 'network_a' } - let(:network_spec) { manual_network_spec(ip: '10.0.0.1') } - let(:security_groups_to_be_used) { ['default-security-group-id'] } - - context "with 'use_nova_networking=false'" do - before(:each) do - port_result_net = double('ports1', id: '117717c1-81cb-4ac4-96ab-99aaf1be9ca8', network_id: 'net', mac_address: 'AA:AA:AA:AA:AA:AA') - allow(openstack).to receive(:network).and_return(neutron) - allow(ports).to receive(:create).and_return(port_result_net) - allow(neutron).to receive(:ports).and_return(ports) - end - - let(:neutron) { double(Fog::Network) } - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: false) } - let(:ports) { double('Fog::OpenStack::Network::Ports') } - - it 'adds port_ids to nic' do - subject.prepare(openstack, security_groups_to_be_used) - - expect(subject.nic).to eq('net_id' => 'net', 'port_id' => '117717c1-81cb-4ac4-96ab-99aaf1be9ca8') - end - - it 'adds MAC addresses to network spec' do - subject.prepare(openstack, security_groups_to_be_used) - - expect(subject.spec['mac']).to eq('AA:AA:AA:AA:AA:AA') - end - - it 'sets the given security groups for the port' do - subject.prepare(openstack, security_groups_to_be_used) - - expect(ports).to have_received(:create).once.with(network_id: anything, fixed_ips: anything, security_groups: ['default-security-group-id']) - end - - context 'allowed_address_pair' do - context 'is configured to disable IP source check' do - let(:network_spec) { manual_network_spec(ip: '10.0.0.1', overwrites: { 'cloud_properties' => { "security_groups"=>["default"], "net_id"=>"net",'vrrp_port_check'=> false} }) } - let(:allowed_address_pairs) { '1.1.1.1/0' } - - before(:each) do - subject.allowed_address_pairs = allowed_address_pairs - end - - it 'configures to disable port check allowed_address_pair' do - allow(ports).to receive(:all) - subject.prepare(openstack, security_groups_to_be_used) - - expect(ports).to_not have_received(:all).with(fixed_ips: "ip_address=#{allowed_address_pairs}") - end - end - - context 'is configured' do - let(:network_spec) { manual_network_spec(ip: '10.0.0.1') } - let(:allowed_address_pairs) { '10.0.0.10' } - - before(:each) do - subject.allowed_address_pairs = allowed_address_pairs - end - - it 'configures allowed_address_pair to the port' do - allow(ports).to receive(:all).and_return([{ 'name' => 'vrrp-port' }]) - - subject.prepare(openstack, security_groups_to_be_used) - - expect(ports).to have_received(:create).once.with(network_id: anything, fixed_ips: anything, security_groups: ['default-security-group-id'], allowed_address_pairs: [{ ip_address: allowed_address_pairs }]) - expect(ports).to have_received(:all).with(fixed_ips: "ip_address=#{allowed_address_pairs}") - end - - context 'and vrrp port does not exist' do - it 'raises an error' do - allow(ports).to receive(:all).and_return([]) - - expect { - subject.prepare(openstack, security_groups_to_be_used) - }.to raise_error(Bosh::Clouds::CloudError, "Configured VRRP port with ip '#{allowed_address_pairs}' does not exist.") - end - end - end - - context 'is not configured' do - it 'configures allowed_address_pair to the port' do - subject.prepare(openstack, security_groups_to_be_used) - - expect(ports).to have_received(:create).once.with(network_id: anything, fixed_ips: anything, security_groups: ['default-security-group-id']) - end - end - end - - context 'when Excon::Error::Conflict is raised' do - let(:error) { Excon::Error::Conflict.new('some error message') } - let(:network_spec) { manual_network_spec(net_id: 'net', ip: '10.0.0.1') } - let(:all_ports) { - [ - double('port1', id: 'id-1', network_id: 'net', status: 'DOWN', device_id: '', device_owner: ''), - double('port2', id: 'id-2', network_id: 'net', status: 'ACTIVE', device_id: '', device_owner: ''), - double('port3', id: 'id-3', network_id: 'net', status: 'DOWN', device_id: 'd_id', device_owner: ''), - double('port4', id: 'id-4', network_id: 'net', status: 'DOWN', device_id: '', device_owner: 'd_owner'), - double('port5', id: 'id-5', network_id: 'net', status: 'ACTIVE', device_id: 'd_id', device_owner: 'd_owner'), - double('port6', id: 'id-6', network_id: 'net', status: 'DOWN', device_id: '', device_owner: ''), - double('port7', id: 'id-7', network_id: 'net', status: 'DOWN', device_id: 'd_id', device_owner: 'd_owner'), - double('port8', id: 'id-8', network_id: 'net', status: 'ACTIVE', device_id: 'd_id', device_owner: ''), - double('port9', id: 'id-9', network_id: 'net', status: 'ACTIVE', device_id: '', device_owner: 'd_owner'), - ] - } - let(:new_port) { double('new-port', id: 'new-port-id', mac_address: 'mac') } - - before(:each) do - retried = false - allow(ports).to receive(:create) do - unless retried - retried = true - raise error - end - - new_port - end - - allow(ports).to receive(:all).and_return(all_ports) - allow(openstack).to receive(:parse_openstack_response).and_return('type' => 'IpAddressAlreadyAllocated') - allow(Bosh::OpenStackCloud::NetworkConfigurator).to receive(:cleanup_ports) - end - - it 'retrieves ports with the correct filter' do - subject.prepare(openstack, []) - - expect(ports).to have_received(:all).with(fixed_ips: ["ip_address=10.0.0.1", "network_id=net"]) - end - - it 'deletes only detached ports' do - subject.prepare(openstack, []) - - expect(Bosh::OpenStackCloud::NetworkConfigurator).to have_received(:cleanup_ports) - .once.with(openstack, %w[id-1 id-6]) - end - - context 'when it has retried once and the error persists' do - it 're-raises the error' do - allow(ports).to receive(:create).and_raise(error) - allow(openstack).to receive(:parse_openstack_response).and_return('type' => 'IpAddressAlreadyAllocated') - allow(ports).to receive(:all).and_return([]) - - expect { - subject.prepare(openstack, []) - }.to raise_error(error) - - expect(ports).to have_received(:create).exactly(2).times - expect(ports).to have_received(:all).once - end - end - end - end - - context "with 'use_nova_networking=true'" do - let(:network_spec) { manual_network_spec(ip: '10.0.0.1') } - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true, network: double('Fog::Network')) } - - it 'does not use Fog::Network' do - subject.prepare(openstack, security_groups_to_be_used) - - expect(openstack).to_not have_received(:network) - end - - it "adds 'v4_fixed_ip' to nic" do - subject.prepare(openstack, security_groups_to_be_used) - - expect(subject.nic).to eq('net_id' => 'net', 'v4_fixed_ip' => '10.0.0.1') - end - end - end - - describe '#cleanup' do - let(:network_name) { 'network_a' } - let(:network_spec) { manual_network_spec(ip: '10.0.0.1') } - - context "with 'use_nova_networking=false'" do - before(:each) do - allow(openstack).to receive(:network).and_return(neutron) - allow(ports).to receive(:create).with({network_id: 'net', fixed_ips: [{ ip_address: '10.0.0.1' }], security_groups: []}).and_return(port) - allow(ports).to receive(:get).with('117717c1-81cb-4ac4-96ab-99aaf1be9ca8').and_return(port) - allow(neutron).to receive(:ports).and_return(ports) - end - - let(:port) { double('ports1', id: '117717c1-81cb-4ac4-96ab-99aaf1be9ca8', network_id: 'net', mac_address: 'AA:AA:AA:AA:AA:AA', destroy: nil) } - let(:neutron) { double(Fog::Network) } - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: false, network: double('Fog::Network')) } - let(:ports) { double('Fog::OpenStack::Network::Ports') } - - before(:each) do - subject.prepare(openstack, []) - end - - it 'should delete port' do - subject.cleanup(openstack) - - expect(port).to have_received(:destroy) - end - - context 'when ports are destroyed by OpenStack (versions < Mitaka)' do - before(:each) do - allow(ports).to receive(:get).with('117717c1-81cb-4ac4-96ab-99aaf1be9ca8').and_return(nil) - end - - it 'should not fail try to destroy port' do - expect { - subject.cleanup(openstack) - }.to_not raise_error - end - end - end - - context "with 'use_nova_networking=true'" do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true, network: double('Fog::Network')) } - let(:security_groups_to_be_used) { [] } - - before(:each) do - subject.prepare(openstack, []) - end - - it 'should not call Fog::Network' do - subject.cleanup(openstack) - - expect(openstack).to_not have_received(:network) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/network_configurator_spec.rb b/src/bosh_openstack_cpi/spec/unit/network_configurator_spec.rb deleted file mode 100644 index 9cd0a0e8a..000000000 --- a/src/bosh_openstack_cpi/spec/unit/network_configurator_spec.rb +++ /dev/null @@ -1,667 +0,0 @@ -require 'spec_helper' -require 'fog/openstack/compute/models/server' - -describe Bosh::OpenStackCloud::NetworkConfigurator do - def set_security_groups(spec, security_groups) - spec['cloud_properties'] ||= {} - spec['cloud_properties']['security_groups'] = security_groups - end - - def set_nics(spec, _net_id) - spec['cloud_properties'] ||= {} - end - - let(:several_manual_networks) do - { - 'network_a' => manual_network_spec(ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'bar', ip: '10.0.0.2'), - } - end - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack) } - let(:spec) { - { - 'network_a' => { 'type' => 'dynamic' }, - } - } - - - before { allow(openstack).to receive(:with_openstack) { |&block| block.call } } - - it 'exposes network_spec as attribute' do - nc = Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - expect(nc.network_spec).to eq(spec) - end - - describe '#initialize' do - context 'when spec is not a hash' do - it 'should raise an error' do - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new('foo') - }.to raise_error ArgumentError, /Invalid spec, Hash expected,/ - end - end - - context 'when no net_id defined in manual networks' do - it 'should raise a CloudError' do - spec = {} - spec['network_b'] = manual_network_without_netid_spec - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - }.to raise_error Bosh::Clouds::CloudError, 'Manual network must have net_id' - end - end - - context 'when several manual networks have the same net_id' do - it 'should raise a CloudError' do - spec = several_manual_networks - spec['network_b']['cloud_properties']['net_id'] = 'net' - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - }.to raise_error Bosh::Clouds::CloudError, 'Manual network with id net is already defined' - end - end - - context 'when several dynamic networks are defined' do - it 'should raise a CloudError' do - spec = {} - spec['network_a'] = dynamic_network_spec - spec['network_b'] = dynamic_network_spec - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - }.to raise_error Bosh::Clouds::CloudError, 'Only one dynamic network per instance should be defined' - end - end - - context 'when several VIP networks are defined' do - it 'should raise a CloudError' do - spec = {} - spec['network_a'] = vip_network_spec - spec['network_b'] = vip_network_spec - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - }.to raise_error Bosh::Clouds::CloudError, 'Only one VIP network per instance should be defined' - end - end - - context 'when only VIP network is defined' do - it 'should raise a CloudError' do - spec = {} - spec['network_a'] = vip_network_spec - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - }.to raise_error Bosh::Clouds::CloudError, 'At least one dynamic or manual network should be defined' - end - end - - context 'when vrrp' do - context 'is configured' do - it 'should set allowed_address_pair to the default network' do - vrrp_port = '10.0.0.10' - network_configurator = Bosh::OpenStackCloud::NetworkConfigurator.new({ - 'network_a' => manual_network_spec(ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'bar', ip: '10.0.0.2', default: ['gateway']), - 'network_c' => dynamic_network_spec, - }, vrrp_port) - - network_configurator.networks.each do |network| - if network.name == 'network_b' - expect(network.allowed_address_pairs).to eq(vrrp_port) - else - expect(network.allowed_address_pairs).to eq(nil) - end - end - end - end - - context 'is not configured' do - it 'should set allowed_address_pair to the default network' do - network_configurator = Bosh::OpenStackCloud::NetworkConfigurator.new( - 'network_a' => manual_network_spec(ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'bar', ip: '10.0.0.2', default: ['gateway']), - 'network_c' => dynamic_network_spec, - ) - - network_configurator.networks.each do |network| - expect(network.allowed_address_pairs).to eq(nil) - end - end - end - end - end - - describe '#security_groups' do - context 'when security_groups are defined in all networks' do - it 'extracts all unique ones from all networks' do - spec = {} - spec['network_a'] = dynamic_network_spec - set_security_groups(spec['network_a'], %w[foo]) - spec['network_b'] = vip_network_spec - set_security_groups(spec['network_b'], %w[bar]) - spec['network_c'] = manual_network_spec - set_security_groups(spec['network_c'], %w[foo]) - - nc = Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - expect(nc.security_groups).to eq(%w[bar foo]) - end - end - - context 'when security_groups is not an array' do - it 'should raise an ArgumentError' do - spec = {} - spec['network_a'] = dynamic_network_spec - set_security_groups(spec['network_a'], 'foo') - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - }.to raise_error ArgumentError, 'security groups must be an Array' - end - end - end - - describe '#nics' do - context 'when dynamic network' do - it 'should extract net_id' do - spec = {} - spec['network_a'] = dynamic_network_spec - spec['network_a']['cloud_properties']['net_id'] = 'foo' - - nc = Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - expect(nc.nics).to eq([{ 'net_id' => 'foo' }]) - end - - it 'should not extract ip address' do - spec = {} - spec['network_a'] = dynamic_network_spec - spec['network_a']['ip'] = '10.0.0.1' - spec['network_a']['cloud_properties']['net_id'] = 'foo' - - nc = Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - expect(nc.nics).to eq([{ 'net_id' => 'foo' }]) - end - end - - context 'when manual network' do - let(:nc) do - manual_network = { 'network_a' => manual_network_spec(ip: '10.0.0.1') } - Bosh::OpenStackCloud::NetworkConfigurator.new(manual_network) - end - - context 'and no port id is available in network spec' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true) } - it 'should set fixed ip only' do - nc.prepare(openstack) - - expect(nc.nics).to eq([{ 'net_id' => 'net', 'v4_fixed_ip' => '10.0.0.1' }]) - end - end - - context 'and port id is available in network spec' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: false) } - before(:each) do - allow_any_instance_of(Bosh::OpenStackCloud::ManualNetwork).to receive(:nic).and_return('net_id' => 'net', 'port_id' => '117717c1-81cb-4ac4-96ab-99aaf1be9ca8') - allow_any_instance_of(Bosh::OpenStackCloud::ManualNetwork).to receive(:prepare) - end - - it 'should set port id only' do - nc.prepare(openstack) - - expect(nc.nics).to eq([{ 'net_id' => 'net', 'port_id' => '117717c1-81cb-4ac4-96ab-99aaf1be9ca8' }]) - end - end - - context 'when multiple networks' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true) } - it 'should extract net_id and IP address from all' do - nc = Bosh::OpenStackCloud::NetworkConfigurator.new(several_manual_networks) - nc.prepare(openstack) - - expect(nc.nics).to eq([ - { 'net_id' => 'net', 'v4_fixed_ip' => '10.0.0.1' }, - { 'net_id' => 'bar', 'v4_fixed_ip' => '10.0.0.2' }, - ]) - end - end - end - end - - describe '#prepare' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true) } - let(:networks) { [] } - - before(:each) do - [Bosh::OpenStackCloud::ManualNetwork, Bosh::OpenStackCloud::DynamicNetwork].each do |class_name| - allow(class_name).to receive(:new) do |name, spec| - network = instance_double(class_name, prepare: nil, name:, spec:) - networks << network - network - end - end - end - - it 'should delegate to all private networks' do - nc = Bosh::OpenStackCloud::NetworkConfigurator.new( - 'network_a' => manual_network_spec(ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'bar', ip: '10.0.0.2'), - 'network_c' => dynamic_network_spec, - ) - - nc.prepare(openstack) - - networks.each do |network| - expect(network).to have_received(:prepare).with(anything, anything) - end - end - end - - describe '#pick_groups' do - let(:default_security_groups) { 'fake-group' } - let(:resource_pool_groups) { 'fake-group' } - let(:security_groups) { [double('security_-group', name: 'group')] } - let(:openstack_security_groups) { instance_double(Bosh::OpenStackCloud::SecurityGroups) } - - before(:each) do - allow(Bosh::OpenStackCloud::SecurityGroups).to receive(:new).with(openstack).and_return(openstack_security_groups) - end - - it 'picks a group and logs it' do - allow(openstack_security_groups).to receive(:select_and_retrieve).and_return(security_groups) - nc = Bosh::OpenStackCloud::NetworkConfigurator.new(spec) - nc.pick_groups(openstack, default_security_groups, resource_pool_groups) - expect(openstack_security_groups).to have_received(:select_and_retrieve).with(default_security_groups, - [], - resource_pool_groups) - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("Using security groups: `group'") - end - end - - describe '#cleanup' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true) } - let(:networks) { [] } - - before(:each) do - [Bosh::OpenStackCloud::ManualNetwork, Bosh::OpenStackCloud::DynamicNetwork].each do |class_name| - allow(class_name).to receive(:new) do - network = instance_double(class_name, cleanup: nil) - networks << network - network - end - end - end - - it 'should delegate to all private networks' do - nc = Bosh::OpenStackCloud::NetworkConfigurator.new( - 'network_a' => manual_network_spec(ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'bar', ip: '10.0.0.2'), - 'network_c' => dynamic_network_spec, - ) - - nc.cleanup(openstack) - - networks.each do |network| - expect(network).to have_received(:cleanup) - end - end - end - - describe '#configure' do - let(:vip_network) do - network = double('vip_network') - allow(Bosh::OpenStackCloud::VipNetwork).to receive(:new).and_return(network) - network - end - let(:dynamic_network) do - network = double('dynamic_network') - allow(Bosh::OpenStackCloud::DynamicNetwork).to receive(:new).and_return(network) - network - end - let(:manual_network) do - network = double('manual_network') - allow(Bosh::OpenStackCloud::ManualNetwork).to receive(:new).and_return(network) - network - end - - context 'With vip network' do - let(:network_spec) do - { - 'network_a' => dynamic_network_spec, - 'network_b' => manual_network_spec(net_id: 'net_b', default: ['gateway']), - 'network_c' => vip_network_spec, - } - end - - it 'configures the vip network and all other networks too' do - expect(vip_network).to receive(:configure).with(anything, anything, 'net_b') - expect(manual_network).to receive(:configure) - expect(dynamic_network).to receive(:configure) - network_configurator = Bosh::OpenStackCloud::NetworkConfigurator.new(network_spec) - network_configurator.configure(nil, nil) - end - end - - context 'when no vip network' do - let(:network_spec) do - { - 'network_a' => dynamic_network_spec, - 'network_b' => manual_network_spec, - } - end - - context 'when no floating IP is associated' do - it 'should not configure the vip_network' do - expect(manual_network).to receive(:configure) - expect(dynamic_network).to receive(:configure) - expect(vip_network).to_not receive(:configure) - - network_configurator = Bosh::OpenStackCloud::NetworkConfigurator.new(network_spec) - network_configurator.configure(nil, nil) - end - end - end - end - - describe '.port_ids' do - let(:neutron) { double(Fog::Network) } - - context 'when neutron is available' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: false) } - - it 'should return all device ports' do - port = double('port', id: 'port_id') - ports = double('ports', all: []) - allow(openstack).to receive(:network).and_return(neutron) - allow(neutron).to receive(:ports).and_return(ports) - allow(ports).to receive(:all).with(device_id: 'server_id').and_return([port]) - - expect(Bosh::OpenStackCloud::NetworkConfigurator.port_ids(openstack, 'server_id')).to eq(['port_id']) - end - end - - context 'when neutron returns error or is not available' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true) } - - it 'should return no ports' do - allow(openstack).to receive(:network) - - expect(Bosh::OpenStackCloud::NetworkConfigurator.port_ids(openstack, 'server_id')).to eq([]) - - expect(openstack).to_not have_received(:network) - end - end - end - - describe '.cleanup_ports' do - let(:neutron) { double(Fog::Network) } - let(:port_a) { double('port_a', id: 'port_a_id') } - let(:port_b) { double('port_b', id: 'port_b_id') } - - context 'when neutron is available' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: false) } - it 'should delete all ports' do - ports = double('ports') - allow(openstack).to receive(:network).and_return(neutron).exactly(2).times - allow(neutron).to receive(:ports).and_return(ports).exactly(2).times - allow(ports).to receive(:get).with('port_a_id').and_return(port_a) - allow(ports).to receive(:get).with('port_b_id').and_return(port_b) - allow(port_a).to receive(:destroy) - allow(port_b).to receive(:destroy) - - expect(Bosh::OpenStackCloud::NetworkConfigurator.cleanup_ports(openstack, %w[port_a_id port_b_id])) - end - - it 'should not fail on already deleted ports' do - ports = double('ports') - allow(openstack).to receive(:network).and_return(neutron).exactly(2).times - allow(neutron).to receive(:ports).and_return(ports).exactly(2).times - allow(ports).to receive(:get).with('port_a_id').and_return(nil) - allow(ports).to receive(:get).with('port_b_id').and_return(nil) - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.cleanup_ports(openstack, %w[port_a_id port_b_id]) - }.to_not raise_error - end - end - - context 'when neutron is not available' do - let(:openstack) { instance_double(Bosh::OpenStackCloud::Openstack, use_nova_networking?: true) } - it 'should not raise any error' do - allow(openstack).to receive(:network) - - expect { - Bosh::OpenStackCloud::NetworkConfigurator.cleanup_ports(openstack, [port_a, port_b]) - }.to_not raise_error - - expect(openstack).to_not have_received(:network) - end - end - end - - describe '.get_gateway_network_id' do - context 'only one network configured' do - let(:network_spec) do - { - 'vip_network' => vip_network_spec, - 'network_a' => manual_network_spec(net_id: 'net_id'), - } - end - - it 'should return network id' do - expect(Bosh::OpenStackCloud::NetworkConfigurator.get_gateway_network_id(network_spec)).to eq('net_id') - end - end - - context 'multiple network configured' do - let(:network_spec) do - { - 'network_a' => manual_network_spec(net_id: 'net_id_a'), - 'network_b' => manual_network_spec(net_id: 'net_id_b', default: ['gateway']), - 'vip_network' => vip_network_spec, - } - end - - it 'should return default gateway network id' do - expect(Bosh::OpenStackCloud::NetworkConfigurator.get_gateway_network_id(network_spec)).to eq('net_id_b') - end - end - end - - describe '.gateway_ip' do - let(:server) { instance_double(Fog::OpenStack::Compute::Server) } - - context 'when gateway network is a manual network' do - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'net_id_a', ip: '10.10.10.10', default: ['gateway']), - 'vip_network' => vip_network_spec, - } - } - - it 'returns the IP address from the gateway network' do - expect(Bosh::OpenStackCloud::NetworkConfigurator.gateway_ip(network_spec, openstack, server)).to eq('10.10.10.10') - end - - context 'when other private networks exist' do - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'net_id_a', ip: '10.10.10.10', default: ['gateway']), - 'network_b' => manual_network_spec(net_id: 'net_id_b', ip: '20.20.20.20'), - 'network_c' => dynamic_network_with_netid_spec, - 'vip_network' => vip_network_spec, - } - } - - it 'returns the IP address from the gateway network' do - expect(Bosh::OpenStackCloud::NetworkConfigurator.gateway_ip(network_spec, openstack, server)).to eq('10.10.10.10') - end - end - end - - context 'when gateway network is a dynamic network' do - let(:network_spec) { - { - 'network_a' => dynamic_network_with_netid_spec.merge('default' => ['gateway']), - 'vip_network' => vip_network_spec, - } - } - - before(:each) do - allow(server).to receive(:addresses).and_return('network_a' => [{ 'addr' => '10.20.20.20' }]) - end - - it 'returns the IP address from the gateway network by calling OpenStack' do - expect(Bosh::OpenStackCloud::NetworkConfigurator.gateway_ip(network_spec, openstack, server)).to eq('10.20.20.20') - expect(server).to have_received(:addresses) - end - - context 'when other private networks exist' do - let(:network_spec) { - { - 'network_a' => dynamic_network_with_netid_spec.merge('default' => ['gateway']), - 'network_b' => manual_network_spec(net_id: 'net_id_b', ip: '10.10.10.10'), - 'vip_network' => vip_network_spec, - } - } - - it 'raises an error' do - expect { - Bosh::OpenStackCloud::NetworkConfigurator.gateway_ip(network_spec, openstack, server) - }.to raise_error(Bosh::Clouds::VMCreationFailed, 'Gateway IP address could not be determined. Gateway network is dynamic, but additional private networks exist.') - end - end - end - end - - describe '.matching_gateway_subnet_ids_for_ip' do - let(:neutron) { double(Fog::OpenStack::Network) } - let(:list_subnets_response) { double('list_subnets', body: { 'subnets' => subnets }) } - - before(:each) do - allow(openstack).to receive(:network).and_return(neutron) - allow(neutron).to receive(:list_subnets).and_return(list_subnets_response) - end - - context 'when no sub-networks exist' do - let(:network_spec) { { 'network_a' => manual_network_spec(net_id: 'net_id_a', ip: '10.10.10.10', default: ['gateway']) } } - let(:subnets) { [] } - - it 'returns an empty list' do - subnet_ids = Bosh::OpenStackCloud::NetworkConfigurator.matching_gateway_subnet_ids_for_ip(network_spec, openstack, '10.0.0.4') - - expect(subnet_ids.empty?).to be_truthy - end - end - - context 'when only one sub-network exists' do - let(:network_spec) { { 'network_a' => manual_network_spec(net_id: 'net_id_a', ip: '10.10.10.10', default: ['gateway']) } } - let(:subnets) { [{ 'id' => 'subnet_id', 'cidr' => '10.0.0.0/24' }] } - - it 'returns the gateway sub-network id' do - subnet_ids = Bosh::OpenStackCloud::NetworkConfigurator.matching_gateway_subnet_ids_for_ip(network_spec, openstack, '10.0.0.2') - - expect(subnet_ids).to eq(['subnet_id']) - end - end - - context 'when more than one sub-network exist' do - let(:network_spec) { - { - 'network_a' => manual_network_spec(net_id: 'net_id_a', ip: '10.10.10.10', default: ['gateway']), - } - } - let(:subnets) { [{ 'id' => 'subnet_id', 'cidr' => '10.0.0.0/24' }, { 'id' => 'second_subnet_id', 'cidr' => '20.20.20.0/24' }] } - it 'returns the sub-network id that matches the ip' do - subnet_ids = Bosh::OpenStackCloud::NetworkConfigurator.matching_gateway_subnet_ids_for_ip(network_spec, openstack, '20.20.20.2') - - expect(subnet_ids).to eq(['second_subnet_id']) - end - - context 'when sub-networks are with overlapping ranges' do - let(:subnets) { [{ 'id' => 'subnet_id', 'cidr' => '10.0.0.0/16' }, { 'id' => 'second_subnet_id', 'cidr' => '10.0.0.0/24' }] } - - it 'returns a list of matching subnets' do - subnet_ids = Bosh::OpenStackCloud::NetworkConfigurator.matching_gateway_subnet_ids_for_ip(network_spec, openstack, '10.0.0.4') - - expect(subnet_ids).to eq(%w[subnet_id second_subnet_id]) - end - end - end - end - - describe '#check_preconditions' do - context 'when multiple manual networks' do - subject do - network_spec = { - 'network_a' => manual_network_spec(net_id: 'network_a', ip: '10.0.0.1'), - 'network_b' => manual_network_spec(net_id: 'network_b', ip: '10.1.0.1'), - } - Bosh::OpenStackCloud::NetworkConfigurator.new(network_spec) - end - - let(:use_nova_networking) { false } - let(:use_config_drive) { true } - let(:use_dhcp) { false } - - it 'does not raise error when preconditions are met' do - expect { - subject.check_preconditions(use_nova_networking, use_config_drive, use_dhcp) - }.to_not raise_error - end - - context "when 'use_nova_networking' is true" do - let(:use_nova_networking) { true } - - it 'raises VMCreationFailed' do - expect { - subject.check_preconditions(use_nova_networking, use_config_drive, use_dhcp) - }.to(raise_error { |e| - expect(e).to be_a(Bosh::Clouds::VMCreationFailed) - expect(e.ok_to_retry).to be(false) - expect(e.message).to eq("Multiple manual networks can only be used with 'openstack.use_nova_networking=false'. Multiple networks require Neutron.") - }) - end - end - - context "when 'use_dhcp' is true" do - let(:use_dhcp) { true } - - it 'raises VMCreationFailed' do - expect { - subject.check_preconditions(use_nova_networking, use_config_drive, use_dhcp) - }.to(raise_error { |e| - expect(e).to be_a(Bosh::Clouds::VMCreationFailed) - expect(e.message).to eq("Multiple manual networks can only be used with 'openstack.use_dhcp=false' and 'openstack.config_drive=cdrom|disk'") - }) - end - end - - context "when 'config_drive' is not set" do - let(:use_config_drive) { false } - - it 'raises VMCreationFailed' do - expect { - subject.check_preconditions(use_nova_networking, use_config_drive, use_dhcp) - }.to(raise_error { |e| - expect(e).to be_a(Bosh::Clouds::VMCreationFailed) - expect(e.message).to eq("Multiple manual networks can only be used with 'openstack.use_dhcp=false' and 'openstack.config_drive=cdrom|disk'") - }) - end - end - end - - context 'when single manual network' do - subject do - network_spec = { 'network_a' => manual_network_spec(ip: '10.0.0.1') } - Bosh::OpenStackCloud::NetworkConfigurator.new(network_spec) - end - it 'does not raise' do - expect { - subject.check_preconditions(false, false, false) - }.to_not raise_error - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/noop_registry_spec.rb b/src/bosh_openstack_cpi/spec/unit/noop_registry_spec.rb deleted file mode 100644 index a3d8b91eb..000000000 --- a/src/bosh_openstack_cpi/spec/unit/noop_registry_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -shared_examples 'a registry' do - it { is_expected.to respond_to(:update_settings).with(2).arguments } - it { is_expected.to respond_to(:read_settings).with(1).arguments } - it { is_expected.to respond_to(:delete_settings).with(1).arguments } - it { is_expected.to respond_to(:endpoint) } -end - -describe Bosh::OpenStackCloud::NoopRegistry do - it_behaves_like 'a registry' - describe '#read_settings' do - it 'returns a hash' do - expect(subject.read_settings('registry-key')).to be_instance_of(Hash) - end - end -end - -describe Bosh::Cpi::RegistryClient do - subject { Bosh::Cpi::RegistryClient.new('fake_endpoint', 'fake_user', 'fake_password') } - it_behaves_like 'a registry' -end diff --git a/src/bosh_openstack_cpi/spec/unit/openstack_spec.rb b/src/bosh_openstack_cpi/spec/unit/openstack_spec.rb deleted file mode 100644 index 8217512a0..000000000 --- a/src/bosh_openstack_cpi/spec/unit/openstack_spec.rb +++ /dev/null @@ -1,902 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Openstack do - let(:openstack_options_v2) { mock_cloud_options['properties']['openstack'] } - let(:openstack_options_v2_devstack) { mock_cloud_options(2, true)['properties']['openstack'] } - let(:openstack_options_v3) { mock_cloud_options(3)['properties']['openstack'] } - let(:openstack_options_v3_devstack) { mock_cloud_options(3, true)['properties']['openstack'] } - let(:openstack_options) { openstack_options_v2 } - subject(:subject) { Bosh::OpenStackCloud::Openstack.new(openstack_options) } - - describe 'is_v3' do - it 'should identify keystone v3 URIs' do - expect(Bosh::OpenStackCloud::Openstack.is_v3('http://fake-auth-url/v3')).to be_truthy - end - - it 'should identify keystone v2 URIs' do - expect(Bosh::OpenStackCloud::Openstack.is_v3('http://fake-auth-url/v2.0')).to be_falsey - end - end - - describe :project_name do - - context 'when keystone version is v2' do - let(:openstack_options) { openstack_options_v2 } - it 'returns the project name' do - expect(subject.project_name).to eq('dummy_tenant') - end - end - - context 'when keystone version is v3' do - let(:openstack_options) { openstack_options_v3 } - it 'returns the tenant name' do - expect(subject.project_name).to eq('dummy_project') - end - end - end - - describe :new do - context 'when auth_url does not include tokens' do - context 'when auth_url is v2' do - it 'should remove the auth_url version' do - expect(subject.auth_url).to eq('http://127.0.0.1:5000') - end - end - - context 'when auth_url is a devstack v2' do - let(:openstack_options) { openstack_options_v2_devstack } - - it 'should remove the auth_url version' do - expect(subject.auth_url).to eq('http://127.0.0.1:5000/identity') - end - end - - context 'when auth_url is v3' do - let(:openstack_options) { openstack_options_v3 } - - it 'should remove the auth_url version' do - expect(subject.auth_url).to eq('http://127.0.0.1:5000') - end - end - - context 'when auth_url is a devstack v3 url' do - let(:openstack_options) { openstack_options_v3_devstack } - - it 'should remove the auth_url version' do - expect(subject.auth_url).to eq('http://127.0.0.1:5000/identity') - end - end - end - - context 'when the full auth_url was specified' do - context 'when auth_url is v2' do - before do - openstack_options_v2['auth_url'] = 'http://fake-auth-url/v2.0/tokens' - end - - it 'removes everything beginning from auth_url version' do - expect(subject.auth_url).to eq('http://fake-auth-url') - end - end - - context 'when auth_url is v2 devstack url' do - before do - openstack_options_v2['auth_url'] = 'http://fake-auth-url/identity/v2.0/tokens' - end - - it 'removes everything beginning from the auth_url version' do - expect(subject.auth_url).to eq('http://fake-auth-url/identity') - end - end - - context 'when auth_url is v3' do - let(:openstack_options) { openstack_options_v3 } - before do - openstack_options_v3['auth_url'] = 'http://fake-auth-url/v3/auth/tokens' - end - - it 'removes everything beginning from the auth_url version' do - expect(subject.auth_url).to eq('http://fake-auth-url') - end - end - - context 'when auth_url is v3 devstack url' do - let(:openstack_options) { openstack_options_v3 } - before do - openstack_options_v3['auth_url'] = 'http://fake-auth-url/identity/v3/auth/tokens' - end - - it 'removes everything beginning from the auth_url version' do - expect(subject.auth_url).to eq('http://fake-auth-url/identity') - end - end - - context 'and it ends with a slash' do - before do - openstack_options_v2['auth_url'] = 'http://fake-auth-url/v2.0/' - end - - it 'removes the trailing slash and version' do - expect(subject.auth_url).to eq('http://fake-auth-url') - end - end - end - - context 'excon instrumentor' do - context 'default instrumentor' do - it 'set the default instrumentor' do - openstack = Bosh::OpenStackCloud::Openstack.new(openstack_options) - - expect(openstack.params[:connection_options]['instrumentor']).to eq(Bosh::OpenStackCloud::ExconLoggingInstrumentor) - end - end - - context 'no instrumentor' do - it 'set the default instrumentor' do - openstack = Bosh::OpenStackCloud::Openstack.new(openstack_options, {}, {}) - - expect(openstack.params[:connection_options].key?('instrumentor')).to be(false) - end - end - end - end - - describe :use_nova_networking? do - context 'when the manifest contains `use_nova_networking=true`' do - let(:openstack_options_with_nova) { openstack_options_v3['use_nova_networking'] = true; openstack_options_v3 } - let(:openstack_options) { openstack_options_with_nova } - it 'returns true' do - expect(subject.use_nova_networking?).to eq(true) - end - end - - context 'when the manifest contains `use_nova_networking=false`' do - let(:openstack_options_with_nova) { openstack_options_v3['use_nova_networking'] = false; openstack_options_v3 } - let(:openstack_options) { openstack_options_with_nova } - it 'returns false' do - expect(subject.use_nova_networking?).to eq(false) - end - end - - context 'when the manifest does not contain `use_nova_networking`' do - let(:openstack_options) { openstack_options_v3 } - it 'returns false' do - expect(subject.use_nova_networking?).to eq(false) - end - end - end - - context 'when the service is not available' do - describe 'Network' do - it 'raises a CloudError exception if cannot connect to the service API' do - allow(Fog::OpenStack::Network).to receive(:new).and_raise(Fog::Errors::NotFound, 'Not found message') - expect { - Bosh::OpenStackCloud::Openstack.new(openstack_options).network - }.to raise_error(Bosh::Clouds::CloudError, - 'Unable to connect to the OpenStack Network Service API: Not found message. Check task debug log for details.') - end - end - end - - [{ clazz: Fog::OpenStack::Compute, name: 'Compute', method_name: :compute }, - { clazz: Fog::OpenStack::Image, name: 'Image', method_name: :image }, - { clazz: Fog::OpenStack::Volume, name: 'Volume', method_name: :volume }, - { clazz: Fog::OpenStack::Network, name: 'Network', method_name: :network }].each do |fog| - describe (fog[:name]).to_s do - let(:retry_options_overwrites) { - { - sleep: 0, - } } - - context 'when the service returns Unauthorized' do - it 'raises a CloudError exception if cannot connect to the service API 5 times' do - allow(fog[:clazz]).to receive(:new).and_raise(Excon::Error::Unauthorized, 'Unauthorized') - expect { - Bosh::OpenStackCloud::Openstack.new(openstack_options, retry_options_overwrites).send(fog[:method_name]) - }.to raise_error(Bosh::Clouds::CloudError, - "Unable to connect to the OpenStack #{fog[:name]} Service API: Unauthorized. Check task debug log for details.") - end - end - - context 'when the backend call raises a SocketError' do - let(:socket_error) { Excon::Error::Socket.new(SocketError.new('getaddrinfo: nodename nor servname provided, or not known')) } - let(:expected_error_message) { "Unable to connect to the OpenStack Keystone API http://127.0.0.1:5000\ngetaddrinfo: nodename nor servname provided, or not known (SocketError)" } - - it 'raises a CloudError exception enriched with the targeted OpenStack KeyStone API url for service API' do - allow(fog[:clazz]).to receive(:new).and_raise(socket_error) - - expect { - Bosh::OpenStackCloud::Openstack.new(openstack_options, retry_options_overwrites).send(fog[:method_name]) - }.to raise_error(Bosh::Clouds::CloudError, expected_error_message) - end - end - - context 'with connection options' do - let(:connection_options) { { 'ssl_verify_peer' => false } } - let(:default_connection_options) { - { 'instrumentor' => Bosh::OpenStackCloud::ExconLoggingInstrumentor } - } - let(:merged_connection_options) { - default_connection_options.merge(connection_options) - } - - it 'should add optional options to the Fog connection' do - openstack_options['connection_options'] = connection_options - - allow(fog[:clazz]).to receive(:new).and_return(instance_double(fog[:clazz])) - Bosh::OpenStackCloud::Openstack.new(openstack_options).send(fog[:method_name]) - - expect(fog[:clazz]).to have_received(:new).with(hash_including(connection_options: merged_connection_options)) - end - end - - context 'when keystone V3 API is used' do - let(:openstack_options) { openstack_options_v3 } - it 'should add optional options to the Fog connection' do - allow(fog[:clazz]).to receive(:new).and_return(instance_double(fog[:clazz])) - Bosh::OpenStackCloud::Openstack.new(openstack_options).send(fog[:method_name]) - - expect(fog[:clazz]).to have_received(:new).with(hash_including(openstack_project_name: 'dummy_project')) - expect(fog[:clazz]).to have_received(:new).with(hash_including(openstack_domain_name: 'some_domain')) - end - end - - context 'when keystone V2 API is used' do - it 'should add optional options to the Fog connection' do - allow(fog[:clazz]).to receive(:new).and_return(instance_double(fog[:clazz])) - Bosh::OpenStackCloud::Openstack.new(openstack_options).send(fog[:method_name]) - - expect(fog[:clazz]).to have_received(:new).with(hash_including(openstack_tenant: 'dummy_tenant')) - end - end - - context 'when last retry succeeds' do - before do - retry_count = 0 - allow(fog[:clazz]).to receive(:new) do - retry_count += 1 - if retry_count < Bosh::OpenStackCloud::Cloud::CONNECT_RETRY_COUNT - raise Excon::Error::GatewayTimeout, 'Gateway Timeout' - end - instance_double(fog[:clazz]) - end - end - - it 'does not raise a GatewayTimeout error' do - expect { - Bosh::OpenStackCloud::Openstack.new(openstack_options) - }.to_not raise_error - end - end - - context 'when used multiple times' do - it 'creates the connection lazy and caches it' do - expect(fog[:clazz]).to receive(:new).once.and_return(instance_double(fog[:clazz])) - openstack = Bosh::OpenStackCloud::Openstack.new(openstack_options) - - fog_class_1st_call = openstack.send(fog[:method_name]) - fog_class_2nd_call = openstack.send(fog[:method_name]) - - expect(fog_class_1st_call).to eq fog_class_2nd_call - end - end - end - end - - describe 'wait_resource' do - let(:resource) { double('resource', id: 'foobar', reload: {}) } - before { allow(resource).to receive(:status).and_return(:start, :stop) } - before { allow(subject).to receive(:sleep) } - - it 'does not raise if one of the target states is reached' do - expect { - subject.wait_resource(resource, %i[stop deleted], :status, false) - }.to_not raise_error - end - - it 'waits for configured amount of time' do - expect(subject).to receive(:sleep).with(3) - - subject.wait_resource(resource, %i[stop deleted], :status, false) - end - - context 'when the resource status never changes' do - it 'times out' do - start_time = Time.now - timeout_time = start_time + subject.state_timeout + 1 - Timecop.freeze(start_time) - - allow(resource).to receive(:status) do - Timecop.freeze(timeout_time) - :start - end - - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /Timed out/ - end - end - - context 'when the resource status is error' do - before { allow(resource).to receive(:status).and_return(:error) } - context 'when no additional fault is provided by OpenStack' do - before { allow(resource).to receive(:fault).and_return(nil) } - - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is error/ - end - end - - context 'when no additional fault supported by resource' do - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is error/ - end - end - - context 'when additional fault is provided by OpenStack' do - let(:resource) { double('resource', id: 'foobar', reload: {}, fault: { 'message' => 'fault message ', 'details' => 'fault details' }) } - - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is error, expected stop\nfault message fault details/ - end - end - end - - context 'when the resource status is failed' do - before { allow(resource).to receive(:status).and_return(:failed) } - context 'when no additional fault is provided by OpenStack' do - before { allow(resource).to receive(:fault).and_return(nil) } - - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is failed/ - end - end - - context 'when no additional fault supported by resource' do - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is failed/ - end - end - - context 'when additional fault is provided by OpenStack' do - let(:resource) { double('resource', id: 'foobar', reload: {}, fault: { 'message' => 'fault message ', 'details' => 'fault details' }) } - - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is failed, expected stop\nfault message fault details/ - end - end - end - - context 'when the resource status is killed' do - before { allow(resource).to receive(:status).and_return(:killed) } - context 'when no additional fault is provided by OpenStack' do - before { allow(resource).to receive(:fault).and_return(nil) } - - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is killed/ - end - end - - context 'when no additional fault supported by resource' do - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is killed/ - end - end - - context 'when additional fault is provided by OpenStack' do - let(:resource) { double('resource', id: 'foobar', reload: {}, fault: { 'message' => 'fault message ', 'details' => 'fault details' }) } - - it 'raises Bosh::Clouds::CloudError' do - expect { - subject.wait_resource(resource, :stop, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /state is killed, expected stop\nfault message fault details/ - end - end - end - - context 'when the resource is not found' do - before { allow(resource).to receive(:reload).and_return(nil) } - - it 'should raise Bosh::Clouds::CloudError if resource not found' do - expect { - subject.wait_resource(resource, :deleted, :status, false) - }.to raise_error Bosh::Clouds::CloudError, /Resource not found/ - end - - context 'when the resource does not need to be found' do - it 'does not raise' do - expect { subject.wait_resource(resource, :deleted, :status, true) }.not_to raise_error - end - end - end - end - - describe 'with_openstack' do - context 'when openstack raises an unexpected exception' do - before { allow(subject).to receive(:servers).and_raise(NoMemoryError) } - - it 'raises the exception without waiting' do - expect(subject).not_to receive(:sleep) - - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(NoMemoryError) - end - end - - context 'when openstack raises ServiceUnavailable' do - let(:headers) { {} } - let(:body) do - { - 'overLimit' => { - 'message' => 'No server is available to handle this request.', - 'code' => 503, - }, - } - end - let(:response) { Excon::Response.new(body: JSON.dump(body), headers:) } - - before do - allow(subject).to receive(:servers) do - # next time don't raise the same exception to avoid looping - allow(subject).to receive(:servers).and_return(nil) - - raise Excon::Error::ServiceUnavailable.new('', '', response) - end - end - - it 'retries until the max number of retries is reached' do - allow(subject).to receive(:servers).exactly(11).times - .and_raise(Excon::Error::ServiceUnavailable.new('', '', response)) - expect(subject).to receive(:sleep).with(3).exactly(10).times - - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API ServiceUnavailable.\nCheck task debug log for details.") - end - end - - context 'when openstack raises RequestEntityTooLarge' do - let(:headers) { {} } - let(:body) do - { - 'overLimit' => { - 'message' => 'This request was rate-limited.', - 'code' => 413, - 'details' => 'Only 10 POST request(s) can be made to * every minute.', - }, - } - end - let(:response) { Excon::Response.new(body: JSON.dump(body), headers:) } - - before do - allow(subject).to receive(:servers) do - # next time don't raise the same exception to avoid looping - allow(subject).to receive(:servers).and_return(nil) - - raise Excon::Error::RequestEntityTooLarge.new('', '', response) - end - end - - it 'retries after waiting a default number of seconds' do - expect_any_instance_of(Kernel).to receive(:sleep).with(3) - - subject.with_openstack do - subject.servers - end - end - - it 'retries until the max number of retries is reached' do - allow(subject).to receive(:servers).exactly(11).times - .and_raise(Excon::Error::RequestEntityTooLarge.new('', '', response)) - expect_any_instance_of(Kernel).to receive(:sleep).with(3).exactly(10).times - - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - /OpenStack API RequestEntityTooLarge/) - end - - context 'when the response includes a retryAfter in the body' do - before { body['overLimit']['retryAfter'] = 5 } - - it 'retries after waiting the amount of seconds received at the response body' do - expect_any_instance_of(Kernel).to receive(:sleep).with(5) - - subject.with_openstack do - subject.servers - end - end - end - - context 'when the response includes a Retry-After header' do - before { headers['Retry-After'] = 5 } - - it 'retries after waiting the amount of seconds received in the Retry-After header' do - expect_any_instance_of(Kernel).to receive(:sleep).with(5) - - subject.with_openstack do - subject.servers - end - end - end - - context 'when OpenStack error message contains overLimit,' do - let(:body) do - { - 'overLimit' => { - 'message' => 'Specific OpenStack error message', - 'code' => 413, - 'details' => 'Only 10 POST request(s) can be made to * every minute.', - 'retryAfter' => 0, - }, - } - end - - it 'enriches the BOSH error message' do - allow(subject).to receive(:servers).and_raise(Excon::Error::RequestEntityTooLarge.new('', '', response)) - - expected_message = "OpenStack API RequestEntityTooLarge Specific OpenStack error message.\nCheck task debug log for details." - - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, expected_message) - end - end - - context 'when OpenStack error message does not contain overLimit,' do - let(:body) do - { - 'notOverLimit' => 'arbitrary content', - } - end - - it 'enriches the BOSH error message with the whole response body' do - expected_response_body = JSON.dump({'notOverLimit' => 'arbitrary content'}) - expected_message = "OpenStack API RequestEntityTooLarge #{expected_response_body}.\nCheck task debug log for details." - - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, expected_message) - end - end - end - - context 'when openstack raises BadRequest' do - before do - response = Excon::Response.new(body:) - expect(subject).to receive(:servers).and_raise(Excon::Error::BadRequest.new('', '', response)) - end - - let(:body) { JSON.dump({}) } - - context 'when the error includes a `message` property on 2nd level of body' do - let(:body) { JSON.dump({'SomeError' => { 'message' => 'some-message' }}) } - - it 'should raise a CloudError exception with OpenStack API message' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API BadRequest (some-message).\nCheck task debug log for details.") - end - end - - context 'when the error does not include a message' do - let(:body) { JSON.dump({'SomeError' => { 'some_key' => 'some_val' }}) } - - it 'should raise a CloudError exception with OpenStack API message without anything from body' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API BadRequest.\nCheck task debug log for details.") - end - end - - context 'when the response has an empty body' do - let(:body) { '' } - - it 'should raise a CloudError exception without OpenStack API message' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API BadRequest.\nCheck task debug log for details.") - end - end - end - - context 'when openstack raises Conflict' do - before do - response = Excon::Response.new(body:) - expect(subject).to receive(:servers).and_raise(Excon::Error::Conflict.new('', '', response)) - end - - let(:body) { JSON.dump({}) } - - context 'when the error includes a `message` property on 2nd level of body' do - let(:body) { JSON.dump({'SomeError' => { 'message' => 'some-message' }}) } - - it 'should raise a CloudError exception with OpenStack API message' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Conflict (some-message).\nCheck task debug log for details.") - end - end - - context 'when the error does not include a message' do - let(:body) { JSON.dump({'SomeError' => { 'some_key' => 'some_val' }}) } - - it 'should raise a CloudError exception with OpenStack API message without anything from body' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Conflict.\nCheck task debug log for details.") - end - end - - context 'when the response has an empty body' do - let(:body) { '' } - - it 'should raise a CloudError exception without OpenStack API message' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Conflict.\nCheck task debug log for details.") - end - end - end - - context 'when openstack raises InternalServerError' do - it 'should retry the max number of retries before raising a CloudError exception' do - expect(subject).to receive(:servers).exactly(11) - .and_raise(Excon::Error::InternalServerError.new('InternalServerError')) - expect_any_instance_of(Kernel).to receive(:sleep).with(3).exactly(10) - - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API InternalServerError.\nCheck task debug log for details.") - end - end - - context 'when openstack raises Fog::Errors::NotFound' do - it 'should raise a CloudError with the original OpenStack message' do - openstack_error_message = 'Could not find service network. Have compute, compute_legacy, identity, image, volume' - - expect { - subject.with_openstack { raise Fog::Errors::NotFound, openstack_error_message } - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API NotFound #{openstack_error_message}.\nCheck task debug log for details.") - end - - context 'when NotFound is ignored' do - it 'should not raise a CloudError' do - expect { - subject.with_openstack(ignore_not_found: true) { raise Fog::Errors::NotFound } - }.to_not raise_error - end - end - end - - context 'when openstack raises Forbidden' do - before do - response = Excon::Response.new(body:) - expect(subject).to receive(:servers).and_raise(Excon::Error::Forbidden.new('', '', response)) - end - - let(:body) { JSON.dump({}) } - - context 'when the error includes a `message` property on 2nd level of body' do - let(:body) { JSON.dump({'Forbidden' => { 'message' => 'some-message' }}) } - - it 'should raise a CloudError exception with OpenStack API message' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Forbidden (some-message).\nCheck task debug log for details.") - end - end - - context 'when the error does not include a message' do - let(:body) { JSON.dump({'SomeError' => { 'some_key' => 'some_val' }}) } - - it 'should raise a CloudError exception with OpenStack API message without anything from body' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Forbidden.\nCheck task debug log for details.") - end - end - - context 'when the response has an empty body' do - let(:body) { '' } - - it 'should raise a CloudError exception with OpenStack API message without anything from body' do - expect { - subject.with_openstack do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Forbidden.\nCheck task debug log for details.") - end - end - end - - context 'when error is Excon::Error::Timeout' do - before do - allow(subject).to receive(:servers).and_raise(Excon::Error::Timeout.new('foo')) - stub_const("Bosh::OpenStackCloud::Openstack::DEFAULT_RETRY_TIMEOUT", 0) - end - - context 'when request is retryable' do - it 'should retry request MAX_RETRIES times' do - expect { - subject.with_openstack(retryable: true) do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Timeout foo.\nCheck task debug log for details.") - - expect(subject).to have_received(:servers).exactly(Bosh::OpenStackCloud::Openstack::MAX_RETRIES + 1).times - end - end - - context 'when request is not retryable' do - it 'should retry request MAX_RETRIES times' do - expect { - subject.with_openstack(retryable: false) do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Timeout foo.\nCheck task debug log for details.") - - expect(subject).to have_received(:servers).once - end - end - end - - context 'when error is Excon::Error::Socket' do - before do - stub_const("Bosh::OpenStackCloud::Openstack::DEFAULT_RETRY_TIMEOUT", 0) - end - - context "when a SocketError is raised with 'getaddrinfo'" do - before do - allow(subject).to receive(:servers).and_raise(Excon::Error::Socket.new(SocketError.new('getaddrinfo: nodename nor servname provided, or not known'))) - end - - it 'should retry request MAX_RETRIES times' do - expect { - subject.with_openstack(retryable: false) do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Socket getaddrinfo: nodename nor servname provided, or not known (SocketError).\nCheck task debug log for details.") - - expect(subject).to have_received(:servers).exactly(Bosh::OpenStackCloud::Openstack::MAX_RETRIES + 1).times - end - end - - context "when a SocketError is not raised with 'getaddrinfo'" do - before do - allow(subject).to receive(:servers).and_raise(Excon::Error::Socket.new(SocketError.new('foo'))) - end - - context 'when request is retryable' do - it 'should retry request MAX_RETRIES times' do - expect { - subject.with_openstack(retryable: true) do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Socket foo (SocketError).\nCheck task debug log for details.") - - expect(subject).to have_received(:servers).exactly(Bosh::OpenStackCloud::Openstack::MAX_RETRIES + 1).times - end - end - - context 'when request is not retryable' do - it 'should not retry request' do - expect { - subject.with_openstack(retryable: false) do - subject.servers - end - }.to raise_error(Bosh::Clouds::CloudError, - "OpenStack API Socket foo (SocketError).\nCheck task debug log for details.") - - expect(subject).to have_received(:servers).once - end - end - end - end - end - - describe 'parse_openstack_response' do - it 'should return nil if response has no body' do - response = Excon::Response.new - - expect(subject.parse_openstack_response(response, 'key')).to be_nil - end - - it 'should return nil if response has an empty string body' do - response = Excon::Response.new(body: JSON.dump('')) - - expect(subject.parse_openstack_response(response, 'key')).to be_nil - end - - it 'should return nil if response has a nil body' do - response = Excon::Response.new(body: JSON.dump(nil)) - - expect(subject.parse_openstack_response(response, 'key')).to be_nil - end - - it 'should return nil if response is not JSON' do - response = Excon::Response.new(body: 'foo = bar') - - expect(subject.parse_openstack_response(response, 'key')).to be_nil - end - - it 'should return nil if response is no key is found' do - response = Excon::Response.new(body: JSON.dump({'foo' => 'bar'})) - - expect(subject.parse_openstack_response(response, 'key')).to be_nil - end - - it 'should return the contents if key is found' do - response = Excon::Response.new(body: JSON.dump({'key' => 'foo'})) - - expect(subject.parse_openstack_response(response, 'key')).to eql('foo') - end - - it 'should return the contents of the first key found' do - response = Excon::Response.new(body: JSON.dump({'key1' => 'foo', 'key2' => 'bar'})) - - expect(subject.parse_openstack_response(response, 'key2', 'key1')).to eql('bar') - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/reboot_vm_spec.rb b/src/bosh_openstack_cpi/spec/unit/reboot_vm_spec.rb deleted file mode 100644 index 50e0e2d81..000000000 --- a/src/bosh_openstack_cpi/spec/unit/reboot_vm_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - before :each do - @server = double('server', id: 'i-foobar') - - @cloud = mock_cloud(mock_cloud_options['properties']) do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(@server) - end - end - - it 'reboots an OpenStack server (CPI call picks soft reboot)' do - expect(@cloud).to receive(:soft_reboot).with(@server) - @cloud.reboot_vm('i-foobar') - end - - it 'soft reboots an OpenStack server' do - expect(@server).to receive(:reboot) - expect(@cloud.openstack).to receive(:wait_resource).with(@server, :active, :state) - @cloud.send(:soft_reboot, @server) - end - - it 'hard reboots an OpenStack server' do - expect(@server).to receive(:reboot) - expect(@cloud.openstack).to receive(:wait_resource).with(@server, :active, :state) - @cloud.send(:hard_reboot, @server) - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/request_message_spec.rb b/src/bosh_openstack_cpi/spec/unit/request_message_spec.rb deleted file mode 100644 index 4a3a5d9ff..000000000 --- a/src/bosh_openstack_cpi/spec/unit/request_message_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::RequestMessage do - describe '.format' do - subject { Bosh::OpenStackCloud::RequestMessage.new(params) } - - let(:params) { - { - ciphers: 'fake-ciphers', - connection: 'connection-class', - stack: 'stack-class', - hostname: 'fake-host.fqdn', - __construction_args: 'fake-args', - uri_parser: 'URI', - instrumentor: 'CLASS', - method: 'PUT', - scheme: 'https', - port: '8774', - path: '/v1', - headers: { 'fake-headers' => 'fake-value' }, - body: '{"fake-body":"fake-value"}', - other_important_key: '1234', - } - } - let(:output) { subject.format } - - it 'removes clutter from params' do - expect(output).not_to include('"ciphers":') - expect(output).not_to include('"connection":') - expect(output).not_to include('"stack":') - expect(output).not_to include('"instrumentor":') - expect(output).not_to include('"uri_parser":') - expect(output).not_to include('"__construction_args":') - end - - it 'keeps everything else in the params' do - expect(output).to include('params: {"other_important_key":"1234"}') - end - - it 'includes url' do - expect(output).to include('PUT https://fake-host.fqdn:8774/v1') - end - - it 'includes the headers as json' do - expect(output).to include('headers: {"fake-headers":"fake-value"}') - end - - it 'includes the body as json' do - expect(output).to include('body: {"fake-body":"fake-value"}') - end - - it 'orders correctly' do - expect(output).to match(%r(^PUT https://fake-host.fqdn:8774/v1 params: {.*} headers: {.*} body: {.*}$)) - end - - context 'when body is empty' do - it 'outputs null for body' do - params.delete(:body) - expect(output).to match(%r(^PUT https://fake-host.fqdn:8774/v1 params: {.*} headers: {.*} body: null$)) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/resize_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/resize_disk_spec.rb deleted file mode 100644 index c03670584..000000000 --- a/src/bosh_openstack_cpi/spec/unit/resize_disk_spec.rb +++ /dev/null @@ -1,109 +0,0 @@ -require 'spec_helper' -require 'excon' - -describe Bosh::OpenStackCloud::Cloud, 'resize_disk' do - let(:volume) { double('volume', id: 'disk-id') } - let(:cloud) { - mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get) - .with('disk-id').and_return(volume) - end - } - - before do - allow(volume).to receive(:size).and_return(2) - allow(volume).to receive(:attachments).and_return([]) - allow(volume).to receive(:metadata).and_return(double('metadata')) - end - - it 'uses the OpenStack endpoint to resize a disk' do - allow(volume).to receive(:extend) - allow(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - return_value = cloud.resize_disk('disk-id', 4096) - - expect(return_value).to eq(nil) - expect(volume).to have_received(:extend).with(4) - expect(Bosh::Clouds::Config.logger).to have_received(:info).with('Resizing disk-id from 2 GiB to 4 GiB') - expect(cloud.openstack).to have_received(:wait_resource).with(volume, :available) - expect(Bosh::Clouds::Config.logger).to have_received(:info).with('Disk disk-id resized from 2 GiB to 4 GiB') - end - - context 'when trying to resize to the same disk size' do - it 'does not call extend on disk and writes to the log' do - allow(volume).to receive(:extend) - allow(Bosh::Clouds::Config.logger).to receive(:info) - - cloud.resize_disk('disk-id', 2048) - - expect(volume).to_not have_received(:extend).with(2) - expect(Bosh::Clouds::Config.logger).to have_received(:info).with('Skipping resize of disk disk-id because current value 2 GiB is equal new value 2 GiB') - end - end - - context 'when trying to resize disk to a new size with an not even size in MiB' do - it 'does call extend on disk with rounding up to the next higher number and writes to the log' do - allow(volume).to receive(:extend) - allow(Bosh::Clouds::Config.logger).to receive(:info) - allow(cloud.openstack).to receive(:wait_resource).with(volume, :available) - - cloud.resize_disk('disk-id', 4097) - - expect(Bosh::Clouds::Config.logger).to have_received(:info).with('Resizing disk-id from 2 GiB to 5 GiB') - expect(cloud.openstack).to have_received(:wait_resource).with(volume, :available) - expect(volume).to have_received(:extend).with(5) - expect(Bosh::Clouds::Config.logger).to have_received(:info).with('Disk disk-id resized from 2 GiB to 5 GiB') - end - end - - context 'when trying to resize a non existing disk' do - let(:cloud) { - mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:get) - .with('non-existing-disk-id').and_return(nil) - end - } - - it 'fails' do - expect { - cloud.resize_disk('non-existing-disk-id', 1024) - }.to raise_error(Bosh::Clouds::CloudError, 'Cannot resize volume because volume with non-existing-disk-id not found') - end - end - - context 'when trying to resize to a smaller disk' do - it 'fails' do - expect { - cloud.resize_disk('disk-id', 1024) - }.to raise_error(Bosh::Clouds::NotSupported, 'Cannot resize volume to a smaller size from 2 GiB to 1 GiB') - end - end - - context 'when volume is still attached' do - before do - allow(volume).to receive(:attachments).and_return([{}]) - end - - it 'fails' do - expect { - cloud.resize_disk('disk-id', 4096) - }.to raise_error(Bosh::Clouds::CloudError, "Cannot resize volume 'disk-id' it still has 1 attachment(s)") - end - end - - context 'when extending volume fails on IaaS' do - before do - body - response = Excon::Response.new(body:) - allow(volume).to receive(:extend).and_raise(Excon::Error::BadRequest.new('', '', response)) - end - - let(:body) { JSON.dump({'badRequest' => { 'message' => 'some-message' }}) } - - it 'raises an error' do - expect { - cloud.resize_disk('disk-id', 4096) - }.to raise_error(Bosh::Clouds::CloudError, "OpenStack API BadRequest (some-message).\nCheck task debug log for details.") - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/resource_pool_spec.rb b/src/bosh_openstack_cpi/spec/unit/resource_pool_spec.rb deleted file mode 100644 index 920fdbce7..000000000 --- a/src/bosh_openstack_cpi/spec/unit/resource_pool_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::ResourcePool do - describe '.security_groups' do - context 'when given resource_pool_spec is nil' do - it 'returns an empty array' do - expect(Bosh::OpenStackCloud::ResourcePool.security_groups(nil)).to eq([]) - end - end - - context "when given resource_pool_spec contains no 'security_group'" do - it 'returns an empty array' do - expect(Bosh::OpenStackCloud::ResourcePool.security_groups(nil)).to eq([]) - end - end - - context 'when security_group is not an array' do - it 'raises an error' do - expect { - Bosh::OpenStackCloud::ResourcePool.security_groups('security_groups' => 'not an array') - }.to raise_error ArgumentError, 'security groups must be an Array' - end - end - - context 'when security_groups is an array' do - it 'returns the security_group array' do - expect(Bosh::OpenStackCloud::ResourcePool.security_groups( - 'security_groups' => ['some-security-group'], - )).to eq(['some-security-group']) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/response_message_spec.rb b/src/bosh_openstack_cpi/spec/unit/response_message_spec.rb deleted file mode 100644 index 8970ce390..000000000 --- a/src/bosh_openstack_cpi/spec/unit/response_message_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::ResponseMessage do - describe '.format' do - subject { Bosh::OpenStackCloud::ResponseMessage.new(params) } - - let(:params) { - { - port: 1234, - host: 'hostname.fqdn', - path: '/api', - headers: { 'other_important_key' => '' }, - status_line: "HTTP/1.0 200 OK\r\n", - body: {}, - cookies: [], - status: '200', - reason_phrase: 'OK', - other_important_key: '10.1', - } - } - let(:output) { subject.format } - - it 'removes clutter from params' do - expect(output).not_to include('"cookies":') - expect(output).not_to include('"status":') - expect(output).not_to include('"reason_phrase":') - end - - it 'includes host, port and everything else in the params' do - expect(output).to include('params: {"port":1234,"host":"hostname.fqdn","other_important_key":"10.1"}') - end - - it 'includes the response status line' do - expect(output).to include('HTTP/1.0 200 OK') - end - - it 'removes the response status line from headers' do - expect(output).to include('headers: {"other_important_key":""}') - end - - it 'includes the path' do - expect(output).to include('/api') - end - - it 'includes the params which include headers' do - expect(output).to match(/headers: {.*}/) - end - - it 'includes the params which include body' do - expect(output).to match(/body: {.*}/) - end - - it 'orders correctly' do - expect(output).to match(%r(^HTTP/1.0 200 OK /api params: {.*} headers: {.*} body: {.*}$)) - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/security_groups_spec.rb b/src/bosh_openstack_cpi/spec/unit/security_groups_spec.rb deleted file mode 100644 index bbcddfe3b..000000000 --- a/src/bosh_openstack_cpi/spec/unit/security_groups_spec.rb +++ /dev/null @@ -1,148 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::SecurityGroups do - let(:security_groups) { [] } - let(:compute) { double('compute') } - let(:network) { double('network') } - let(:use_nova_networking?) { false } - let(:openstack) { double('openstack', compute:, network:, use_nova_networking?: use_nova_networking?) } - - subject(:openstack_security_groups) { - Bosh::OpenStackCloud::SecurityGroups.new(openstack) - } - - before do - allow(openstack).to receive(:with_openstack) { |&block| block.call } - mock_sec_groups(network, security_groups) - mock_sec_groups(compute, security_groups) - end - - - describe '.retrieve_and_validate_security_groups' do - context 'security group picking' do - let(:security_groups) { - [ - double('default-security-group', name: 'default-security-group', id: 'default-security-group-id'), - double('network-spec-security-group', name: 'network-spec-security-group'), - double('resource-pool-spec-security-group', name: 'resource-pool-spec-security-group'), - ] - } - - context 'when security groups specified in resource pool spec' do - it 'picks those' do - picked_security_groups = openstack_security_groups.select_and_retrieve( - ['default-security-group'], - [], - ['resource-pool-spec-security-group'], - ) - expect(picked_security_groups.size).to eq(1) - expect(picked_security_groups.first.name).to eq('resource-pool-spec-security-group') - end - end - - context 'when security group id is specified instead of name' do - it 'picks the security group by id' do - picked_security_groups = openstack_security_groups.select_and_retrieve( - ['default-security-group-id'], - [], - [], - ) - expect(picked_security_groups.size).to eq(1) - expect(picked_security_groups.first.name).to eq('default-security-group') - end - end - - context 'when security groups specified in network spec' do - it 'picks those' do - picked_security_groups = openstack_security_groups.select_and_retrieve( - ['default-security-group'], - ['network-spec-security-group'], - [], - ) - expect(picked_security_groups.size).to eq(1) - expect(picked_security_groups.first.name).to eq('network-spec-security-group') - end - end - - context 'when resource pool spec and network spec define security groups' do - it 'picks the resource pool security groups' do - picked_security_groups = openstack_security_groups.select_and_retrieve( - ['default security group'], - ['network-spec-security-group'], - ['resource-pool-spec-security-group'], - ) - expect(picked_security_groups.size).to eq(1) - expect(picked_security_groups.first.name).to eq('resource-pool-spec-security-group') - end - end - - context 'when security groups are neither specified in network spec nor resource pool spec' do - it 'picks the default security group' do - picked_security_groups = openstack_security_groups.select_and_retrieve( - ['default-security-group'], - [], - [], - ) - expect(picked_security_groups.size).to eq(1) - expect(picked_security_groups.first.name).to eq('default-security-group') - end - end - end - - context 'when a picked security group does not exist in openstack' do - let(:security_groups) { [] } - - it 'raises an error' do - expect { - openstack_security_groups.select_and_retrieve( - ['default-security-group'], - [], - [], - ) - }.to raise_error Bosh::Clouds::CloudError, "Security group `default-security-group' not found" - end - end - - context 'when openstack is configured with `use_nova_networking`' do - let(:use_nova_networking?) { true } - - let(:security_groups) { - [ - double('default-security-group', name: 'default-security-group'), - ] - } - - it 'uses nova to retrieve the security groups' do - openstack_security_groups.select_and_retrieve( - ['default-security-group'], - [], - [], - ) - - expect(compute).to have_received(:security_groups) - expect(network).to_not have_received(:security_groups) - end - end - - context 'when openstack is configured without `use_nova_networking` (default)' do - let(:use_nova_networking?) { false } - - let(:security_groups) { - [ - double('default-security-group', name: 'default-security-group'), - ] - } - - it 'uses neutron to retrieve the security groups' do - openstack_security_groups.select_and_retrieve( - ['default-security-group'], - [], - [], - ) - - expect(network).to have_received(:security_groups) - expect(compute).to_not have_received(:security_groups) - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/set_disk_metadata_spec.rb b/src/bosh_openstack_cpi/spec/unit/set_disk_metadata_spec.rb deleted file mode 100644 index 9f3565f3d..000000000 --- a/src/bosh_openstack_cpi/spec/unit/set_disk_metadata_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - let(:disk) { double('disk', id: 'i-foobar', metadata: double('metadata')) } - let(:metadata) { { 'deployment' => 'deployment-x' } } - - context 'when disk exists' do - before(:each) do - @cloud = mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:get).with('i-foobar').and_return(disk) - end - allow(@cloud.volume).to receive(:update_metadata) - end - - it 'should tag with given metadata' do - @cloud.set_disk_metadata('i-foobar', metadata) - expect(@cloud.volume).to have_received(:update_metadata).with('i-foobar', 'deployment' => 'deployment-x') - end - end - - context 'when disk does not exist' do - before(:each) do - @cloud = mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:get).with('i-foobar') - end - end - - it 'raises a cloud error' do - expect { - @cloud.set_disk_metadata('i-foobar', metadata) - }.to raise_error(Bosh::Clouds::CloudError, "Disk `i-foobar' not found") - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/set_vm_metadata_spec.rb b/src/bosh_openstack_cpi/spec/unit/set_vm_metadata_spec.rb deleted file mode 100644 index d3bd67527..000000000 --- a/src/bosh_openstack_cpi/spec/unit/set_vm_metadata_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - before(:each) { allow(Bosh::OpenStackCloud::TagManager).to receive(:tag_server) } - - let(:server) { double('server', id: 'i-foobar', metadata: double('metadata')) } - - context 'without registry_key set' do - before(:each) do - @cloud = mock_cloud do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - end - allow(server.metadata).to receive(:get) - end - - it 'should only tag with metadata' do - metadata = { job: 'job', index: 'index' } - - expect(Bosh::OpenStackCloud::TagManager).to receive(:tag_server).with(server, { job: 'job', index: 'index'}) - - @cloud.set_vm_metadata('i-foobar', metadata) - end - - it "logs 'compiling/x'" do - @cloud.set_vm_metadata('i-foobar', {}) - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("VM with id 'i-foobar' has no 'registry_key' tag") - end - end - - context 'with registry_key set' do - before(:each) do - @cloud = mock_cloud do |fog| - allow(fog.compute.servers).to receive(:get).with('i-foobar').and_return(server) - allow(fog.compute).to receive(:update_server) - end - allow(server.metadata).to receive(:get).with(:registry_key).and_return(double('metadatum')) - end - - context 'for normal job' do - context "when bosh provides NO 'name' property" do - let(:metadata) { { 'job' => 'job', 'index' => 'index' } } - - it "sets the vm name 'job/index'" do - @cloud.set_vm_metadata('i-foobar', metadata) - expect(@cloud.compute).to have_received(:update_server).with('i-foobar', 'name' => 'job/index') - end - - it 'logs job name & index' do - @cloud.set_vm_metadata('i-foobar', metadata) - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("Rename VM with id 'i-foobar' to 'job/index'") - end - end - - context "when bosh provides a 'name' property" do - let(:metadata) { { 'name' => 'job/id' } } - - it "sets the vm name 'job/id'" do - @cloud.set_vm_metadata('i-foobar', metadata) - expect(@cloud.compute).to have_received(:update_server).with('i-foobar', 'name' => 'job/id') - end - - it 'logs instance name' do - @cloud.set_vm_metadata('i-foobar', metadata) - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("Rename VM with id 'i-foobar' to 'job/id'") - end - end - end - - context 'for compilation vms' do - let(:metadata) { { 'compiling' => 'x' } } - - it "sets the vm name 'compiling/x'" do - @cloud.set_vm_metadata('i-foobar', metadata) - expect(@cloud.compute).to have_received(:update_server).with('i-foobar', 'name' => 'compiling/x') - end - - it "logs 'compiling/x'" do - @cloud.set_vm_metadata('i-foobar', metadata) - expect(Bosh::Clouds::Config.logger).to have_received(:debug).with("Rename VM with id 'i-foobar' to 'compiling/x'") - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/snapshot_disk_spec.rb b/src/bosh_openstack_cpi/spec/unit/snapshot_disk_spec.rb deleted file mode 100644 index c7e6d05a4..000000000 --- a/src/bosh_openstack_cpi/spec/unit/snapshot_disk_spec.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::Cloud do - it 'creates an OpenStack snapshot' do - unique_name = SecureRandom.uuid - volume = double('volume', id: 'v-foobar') - attachment = { 'device' => '/dev/vdc' } - snapshot = double('snapshot', id: 'snap-foobar', update_metadata: nil) - snapshot_params = { - display_name: "snapshot-#{unique_name}", - display_description: 'deployment/job/0/vdc', - name: "snapshot-#{unique_name}", - description: 'deployment/job/0/vdc', - volume_id: 'v-foobar', - force: true, - } - metadata = { - agent_id: 'agent', - instance_id: 'instance', - director_name: 'Test Director', - director_uuid: '6d06b0cc-2c08-43c5-95be-f1b2dd247e18', - deployment: 'deployment', - job: 'job', - index: '0', - } - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - expect(fog.volume.snapshots).to receive(:new).with(snapshot_params).and_return(snapshot) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - - expect(volume).to receive(:attachments).and_return([attachment]) - - expect(snapshot).to receive(:save) - - expect(cloud.openstack).to receive(:wait_resource).with(snapshot, :available) - - expect(cloud.snapshot_disk('v-foobar', metadata)).to eq('snap-foobar') - end - - context "when volume doesn't have any attachment" do - let(:volume) { double('volume', id: 'v-foobar', attachments: [{}]) } - - it 'creates an OpenStack snapshot' do - unique_name = SecureRandom.uuid - snapshot = double('snapshot', id: 'snap-foobar', update_metadata: nil) - snapshot_params = { - display_name: "snapshot-#{unique_name}", - display_description: 'deployment/job/0', - name: "snapshot-#{unique_name}", - description: 'deployment/job/0', - volume_id: 'v-foobar', - force: true, - } - metadata = { - agent_id: 'agent', - instance_id: 'instance', - director_name: 'Test Director', - director_uuid: '6d06b0cc-2c08-43c5-95be-f1b2dd247e18', - deployment: 'deployment', - job: 'job', - index: '0', - } - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - expect(fog.volume.snapshots).to receive(:new).with(snapshot_params).and_return(snapshot) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - - expect(snapshot).to receive(:save) - - expect(cloud.openstack).to receive(:wait_resource).with(snapshot, :available) - - expect(cloud.snapshot_disk('v-foobar', metadata)).to eq('snap-foobar') - end - end - - it 'handles string keys in metadata' do - unique_name = SecureRandom.uuid - volume = double('volume', id: 'v-foobar') - snapshot = double('snapshot', id: 'snap-foobar', update_metadata: nil) - snapshot_params = { - display_name: "snapshot-#{unique_name}", - display_description: 'deployment/job/0', - name: "snapshot-#{unique_name}", - description: 'deployment/job/0', - volume_id: 'v-foobar', - force: true, - } - metadata = { - 'agent_id' => 'agent', - 'instance_id' => 'instance', - 'director_name' => 'Test Director', - 'director_uuid' => '6d06b0cc-2c08-43c5-95be-f1b2dd247e18', - 'deployment' => 'deployment', - 'job' => 'job', - 'index' => '0', - } - - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(volume) - expect(fog.volume.snapshots).to receive(:new).with(snapshot_params).and_return(snapshot) - end - - expect(cloud).to receive(:generate_unique_name).and_return(unique_name) - - expect(volume).to receive(:attachments).and_return([{}]) - - expect(snapshot).to receive(:save) - - expect(cloud.openstack).to receive(:wait_resource).with(snapshot, :available) - - expect(cloud.snapshot_disk('v-foobar', metadata)).to eq('snap-foobar') - end - - it 'should raise an Exception if OpenStack volume is not found' do - cloud = mock_cloud do |fog| - expect(fog.volume.volumes).to receive(:get).with('v-foobar').and_return(nil) - end - - expect { - cloud.snapshot_disk('v-foobar', {}) - }.to raise_error(Bosh::Clouds::CloudError, "Volume `v-foobar' not found") - end - - it 'creates snapshot metadata with converted metadata' do - volume = double('volume', id: 'v-foobar', attachments: []) - snapshot = double('snapshot', id: 'snap-foobar', save: nil, update_metadata: nil) - cloud = mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:get).and_return(volume) - allow(fog.volume.snapshots).to receive(:new).and_return(snapshot) - end - allow(cloud.openstack).to receive(:wait_resource) - - metadata = { - 'deployment' => 'deployment', - 'job' => 'job', - 'index' => 0, - 'director_name' => 'Test Director', - 'director_uuid' => '1234', - 'agent_id' => 'agent0', - 'instance_id' => 'some-uuid', - } - - expected_snapshot_metadata = { - 'director' => 'Test Director', - 'director_uuid' => '1234', - 'deployment' => 'deployment', - 'instance_id' => 'some-uuid', - 'instance_index' => '0', - 'instance_name' => 'job/some-uuid', - 'agent_id' => 'agent0', - } - - cloud.snapshot_disk('v-foobar', metadata) - - expect(snapshot).to have_received(:update_metadata).with(expected_snapshot_metadata) - end - - it 'creates snapshot metadata keeping user-defined tags without changing them' do - volume = double('volume', id: 'v-foobar', attachments: []) - snapshot = double('snapshot', id: 'snap-foobar', save: nil, update_metadata: nil) - cloud = mock_cloud do |fog| - allow(fog.volume.volumes).to receive(:get).and_return(volume) - allow(fog.volume.snapshots).to receive(:new).and_return(snapshot) - end - allow(cloud.openstack).to receive(:wait_resource) - - metadata = { - 'deployment' => 'deployment', - 'job' => 'job', - 'index' => 0, - 'director_name' => 'Test Director', - 'director_uuid' => '1234', - 'agent_id' => 'agent0', - 'instance_id' => 'some-uuid', - 'tag1' => 'value1', - 'tag2' => 'value2', - } - - expected_snapshot_metadata = { - 'director' => 'Test Director', - 'director_uuid' => '1234', - 'deployment' => 'deployment', - 'instance_id' => 'some-uuid', - 'instance_index' => '0', - 'instance_name' => 'job/some-uuid', - 'agent_id' => 'agent0', - 'tag1' => 'value1', - 'tag2' => 'value2', - } - - cloud.snapshot_disk('v-foobar', metadata) - - expect(snapshot).to have_received(:update_metadata).with(expected_snapshot_metadata) - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/stemcell_creator_spec.rb b/src/bosh_openstack_cpi/spec/unit/stemcell_creator_spec.rb deleted file mode 100644 index c9827243e..000000000 --- a/src/bosh_openstack_cpi/spec/unit/stemcell_creator_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -describe Bosh::OpenStackCloud::HeavyStemcellCreator do - subject { described_class } - - describe '#normalize_image_properties' do - it 'rejects nil values' do - properties = { - 'version' => nil, - } - - expect(subject.normalize_image_properties(properties)).to_not have_key(:version) - end - - it 'converts keys to symbols' do - properties = { - 'version' => '123', - } - - expect(subject.normalize_image_properties(properties)).to have_key(:version) - end - - it 'supports virtio-scsi properties' do - properties = { - 'hw_disk_bus_model' => 'virtio-scsi', - 'hw_scsi_model' => 'virtio-scsi', - 'hw_disk_bus' => 'scsi', - } - - expect(subject.normalize_image_properties(properties)).to include( - :hw_disk_bus_model, - :hw_scsi_model, - :hw_disk_bus, - ) - end - - it 'supports virtio multiqueue network' do - properties = { - 'hw_vif_multiqueue_enabled' => true, - } - - expect(subject.normalize_image_properties(properties)).to include( - :hw_vif_multiqueue_enabled, - ) - end - - it 'maps hypervisor key to hypervisor_type' do - properties = { - 'hypervisor' => 'kvm', - } - - image_properties = subject.normalize_image_properties(properties) - - expect(image_properties[:hypervisor_type]).to eq('kvm') - expect(image_properties).to_not have_key(:hypervisor) - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/tag_manager_spec.rb b/src/bosh_openstack_cpi/spec/unit/tag_manager_spec.rb deleted file mode 100644 index c0af11244..000000000 --- a/src/bosh_openstack_cpi/spec/unit/tag_manager_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::TagManager do - describe '.tag_server' do - let(:server) { double('server', id: 'i-foobar') } - let(:metadata) { double('metadata') } - - before do - allow(server).to receive(:metadata).and_return(metadata) - allow(metadata).to receive(:update) - end - - it 'sets multiple metadata tags' do - Bosh::OpenStackCloud::TagManager.tag_server(server, 'key1' => 'value1', 'key2' => 'value2') - - expect(metadata).to have_received(:update).with('key1' => 'value1', 'key2' => 'value2') - end - - it 'formats the tags' do - Bosh::OpenStackCloud::TagManager.tag_server(server, 'key1' => 'value1', 2 => 2, 'key3' => nil) - - expect(metadata).to have_received(:update).with('key1' => 'value1', '2' => '2') - end - end - - describe '.tag_volume' do - let(:volume) { double('volume') } - - before do - allow(volume).to receive(:update_metadata) - end - - it 'sets multiple metadata tags' do - Bosh::OpenStackCloud::TagManager.tag_volume(volume, 'volume-id', 'key1' => 'value1', 'key2' => 'value2') - - expect(volume).to have_received(:update_metadata).with('volume-id', 'key1' => 'value1', 'key2' => 'value2') - end - - it 'formats the tags' do - Bosh::OpenStackCloud::TagManager.tag_volume(volume, 'volume-id', 'key1' => 'value1', 2 => 2, 'key3' => nil) - - expect(volume).to have_received(:update_metadata).with('volume-id', 'key1' => 'value1', '2' => '2') - end - end - - describe '.tag_snapshot' do - let(:snapshot) { double('snapshot') } - - before do - allow(snapshot).to receive(:update_metadata) - end - - it 'sets multiple metadata tags' do - Bosh::OpenStackCloud::TagManager.tag_snapshot(snapshot, 'key1' => 'value1', 'key2' => 'value2') - - expect(snapshot).to have_received(:update_metadata).with('key1' => 'value1', 'key2' => 'value2') - end - - it 'formats the tags' do - Bosh::OpenStackCloud::TagManager.tag_snapshot(snapshot, 'key1' => 'value1', 2 => 2, 'key3' => nil) - - expect(snapshot).to have_received(:update_metadata).with('key1' => 'value1', '2' => '2') - end - end - - describe '.format' do - it 'trims key and value length' do - formatted_tags = Bosh::OpenStackCloud::TagManager.format('x' * 256 => 'y' * 256) - - expect(formatted_tags).to eq('x' * 255 => 'y' * 255) - end - - it 'converts all keys and value to strings' do - formatted_tags = Bosh::OpenStackCloud::TagManager.format(1 => 5) - - expect(formatted_tags).to eq('1' => '5') - end - - it 'does nothing if key is nil' do - formatted_tags = Bosh::OpenStackCloud::TagManager.format(nil => 'value') - - expect(formatted_tags).to eq({}) - end - - it 'does nothing if value is nil' do - formatted_tags = Bosh::OpenStackCloud::TagManager.format('key' => nil) - - expect(formatted_tags).to eq({}) - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/vip_network_spec.rb b/src/bosh_openstack_cpi/spec/unit/vip_network_spec.rb deleted file mode 100644 index c857b3c38..000000000 --- a/src/bosh_openstack_cpi/spec/unit/vip_network_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::VipNetwork do - describe 'configure' do - subject do - described_class.new('network_b', network_spec) - end - - let(:network_spec) { vip_network_spec } - - context 'no floating IP provided for vip network' do - before(:each) do - network_spec['ip'] = nil - end - - it 'fails' do - expect { - subject.configure(nil, nil, nil) - }.to raise_error Bosh::Clouds::CloudError, /No IP provided for vip network/ - end - end - - context 'floating IP is provided' do - let(:openstack) { double('openstack') } - before { allow(openstack).to receive(:with_openstack) { |&block| block.call } } - - it 'calls FloatingIp.reassiciate' do - server = double('server') - allow(Bosh::OpenStackCloud::FloatingIp).to receive(:reassociate) - - subject.configure(openstack, server, 'network_id') - - expect(Bosh::OpenStackCloud::FloatingIp).to have_received(:reassociate).with(openstack, '10.0.0.1', server, 'network_id') - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/vm_creator_spec.rb b/src/bosh_openstack_cpi/spec/unit/vm_creator_spec.rb deleted file mode 100644 index 467a76f4d..000000000 --- a/src/bosh_openstack_cpi/spec/unit/vm_creator_spec.rb +++ /dev/null @@ -1,130 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::VmCreator do - subject(:vm_creator) do - described_class.new( - cloud, - network_configurator, - server, - az_provider, - cloud_properties, - agent_settings, - create_vm_params, - ) - end - - let(:cloud) { double } - let(:network_configurator) { instance_double(Bosh::OpenStackCloud::NetworkConfigurator, prepare: nil, cleanup: nil) } - let(:server) { double } - let(:multiple_azs) { false } - let(:az_provider) do - instance_double( - Bosh::OpenStackCloud::AvailabilityZoneProvider, - use_multiple_azs?: multiple_azs, - select: nil, - ) - end - let(:cloud_properties) { {} } - let(:agent_settings) { double } - let(:create_vm_params) { {} } - let(:manual_network) { { 'network_a' => manual_network_spec(ip: '10.0.0.1') } } - let(:environment) { { 'test_env' => 'value' } } - - describe 'perform' do - before do - allow(server).to receive(:create) - end - - context 'when vm creation fails' do - before(:each) do - allow(server).to receive(:create).and_raise 'BOOM!!!' - allow(network_configurator).to receive(:prepare) - end - - it 'cleans up network resources' do - expect(network_configurator).to receive(:cleanup) - - expect { - subject.perform - }.to raise_error RuntimeError, 'BOOM!!!' - end - - it 'ignores but warns about network resource cleanup failures' do - allow(network_configurator).to receive(:cleanup).and_raise 'BOOM Cleanup!!!' - - expect { - subject.perform - }.to raise_error RuntimeError, 'BOOM!!!' - end - end - - context 'when using availability_zone' do - before do - allow(az_provider).to receive(:select).and_return('az1') - end - - it 'calls create server once' do - subject.perform - expect(server).to have_received(:create).with(anything, anything, anything, include(availability_zone: 'az1')) - end - end - - context 'when using availability_zones' do - let(:multiple_azs) { true } - - before do - allow(az_provider).to receive(:select_azs).and_return(['az1', 'az2']) - end - - it 'calls create server with the first zone' do - vm_creator.perform - expect(server).to have_received(:create).with(anything, anything, anything, include(availability_zone: 'az1')) - end - - context 'when the first create server fails' do - it 'calls create server with the second zone and logs retrying' do - expect(network_configurator).to receive(:prepare).once - expect(server).to receive(:create) - .with(anything, anything, anything, include(availability_zone: 'az1')) - .and_raise('create-error') - - expect(server).to receive(:create).with(anything, anything, anything, include(availability_zone: 'az2')) - - vm_creator.perform - - expect(Bosh::Clouds::Config.logger).to have_received(:warn) - .with("Failed to create VM in AZ 'az1' with error 'create-error', retrying in a different AZ") - end - end - - context 'when all create servers fails' do - before do - expect(network_configurator).to receive(:cleanup).once - expect(network_configurator).to receive(:prepare).once - - expect(server).to receive(:create) - .with(anything, anything, anything, include(availability_zone: 'az1')) - .and_raise('create-error-1') - expect(server).to receive(:create) - .with(anything, anything, anything, include(availability_zone: 'az2')) - .and_raise('create-error-2') - end - - it 'raises an error and logs the number of retries' do - expect { vm_creator.perform }.to raise_error('create-error-2') - - expect(Bosh::Clouds::Config.logger).to have_received(:error) - .with("Failed to create VM in AZ 'az2' with error 'create-error-2' after 2 retries. No AZs left to retry.") - end - - it 'ignores but warns about network resource cleanup failures' do - allow(network_configurator).to receive(:cleanup).and_raise 'BOOM Cleanup!!!' - expect(Bosh::Clouds::Config.logger).to receive(:warn) - .with('Failed to cleanup network resources: BOOM Cleanup!!!') - - expect { subject.perform }.to raise_error('create-error-2') - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/spec/unit/volume_configurator_spec.rb b/src/bosh_openstack_cpi/spec/unit/volume_configurator_spec.rb deleted file mode 100644 index 82b68252d..000000000 --- a/src/bosh_openstack_cpi/spec/unit/volume_configurator_spec.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'spec_helper' - -describe Bosh::OpenStackCloud::VolumeConfigurator do - let(:logger) do - logger = double('logger') - allow(logger).to receive(:debug) - allow(logger).to receive(:error) - logger - end - subject { Bosh::OpenStackCloud::VolumeConfigurator.new(logger) } - describe 'select_boot_volume_size' do - context 'when the flavor has no disk' do - let(:flavor) { double('flavor', disk: 0, name: 'mock_flavor_without_disk') } - - context 'and resource pool contains root_disk.size > 1' do - let(:resource_pool) { { 'root_disk' => { 'size' => 50 } } } - - it 'takes root disk size from resource pool as boot_volume size' do - boot_volume_size = subject.select_boot_volume_size(flavor, resource_pool) - expect(boot_volume_size).to be(50) - end - end - - context 'and resource pool contains root_disk.size = 1' do - let(:resource_pool) { { 'root_disk' => { 'size' => 1 } } } - - it 'takes root disk size from resource pool as boot_volume size' do - boot_volume_size = subject.select_boot_volume_size(flavor, resource_pool) - expect(boot_volume_size).to be(1) - end - end - - context 'and resource pool contains root_disk.size < 1' do - let(:resource_pool) { { 'root_disk' => { 'size' => 0 } } } - - it 'throw an error' do - expect { - subject.select_boot_volume_size(flavor, resource_pool) - }.to raise_error(ArgumentError, 'Minimum root_disk size is 1 GiB') - end - end - - context 'root_disk not defined in resource pool' do - let(:resource_pool) { {} } - - it 'takes the root disk size from the flavor' do - expect { - subject.select_boot_volume_size(flavor, resource_pool) - }.to raise_error(Bosh::Clouds::CloudError, "Flavor 'mock_flavor_without_disk' has a root disk size of 0. Either pick a different flavor or define root_disk.size in your VM cloud_properties") - end - end - - context 'root_disk.size not defined in resource pool' do - let(:resource_pool) { { 'root_disk' => {} } } - - it 'throw an error' do - expect { - subject.select_boot_volume_size(flavor, resource_pool) - }.to raise_error(ArgumentError, 'Minimum root_disk size is 1 GiB') - end - end - end - - context 'when the flavor has disk' do - let(:flavor) { double('flavor', disk: 10) } - - context 'and resource pool contains root_disk.size > 1' do - let(:resource_pool) { { 'root_disk' => { 'size' => 50 } } } - - it 'takes root disk size from resource pool as boot_volume size' do - boot_volume_size = subject.select_boot_volume_size(flavor, resource_pool) - expect(boot_volume_size).to be(50) - end - end - - context 'and resource pool contains root_disk.size = 1' do - let(:resource_pool) { { 'root_disk' => { 'size' => 1 } } } - - it 'takes root disk size from resource pool as boot_volume size' do - boot_volume_size = subject.select_boot_volume_size(flavor, resource_pool) - expect(boot_volume_size).to be(1) - end - end - - context 'and resource pool contains root_disk.size < 1' do - let(:resource_pool) { { 'root_disk' => { 'size' => 0 } } } - - it 'throw an error' do - expect { - subject.select_boot_volume_size(flavor, resource_pool) - }.to raise_error(ArgumentError, 'Minimum root_disk size is 1 GiB') - end - end - - context 'root_disk not defined in resource pool' do - let(:resource_pool) { {} } - - it 'takes the root disk size from the flavor' do - boot_volume_size = subject.select_boot_volume_size(flavor, resource_pool) - - expect(boot_volume_size).to be(10) - end - end - - context 'root_disk.size not defined in resource pool' do - let(:resource_pool) { { 'root_disk' => {} } } - - it 'throw an error' do - expect { - subject.select_boot_volume_size(flavor, resource_pool) - }.to raise_error(ArgumentError, 'Minimum root_disk size is 1 GiB') - end - end - end - end - - describe 'boot_from_volume?' do - context 'when global setting for boot_from_volume is false' do - context 'when boot_from_volume set for vm type' do - let(:resource_pool) { { 'boot_from_volume' => true } } - - it 'returns true' do - boot_from_volume = subject.boot_from_volume?(false, resource_pool) - - expect(boot_from_volume).to be(true) - end - end - - context 'when boot_from_volume set to false for vm type' do - let(:resource_pool) { { 'boot_from_volume' => false } } - - it 'returns false' do - boot_from_volume = subject.boot_from_volume?(false, resource_pool) - - expect(boot_from_volume).to be(false) - end - end - - context 'when boot_from_volume is not set for vm type' do - let(:resource_pool) { {} } - - it 'returns false' do - boot_from_volume = subject.boot_from_volume?(false, resource_pool) - - expect(boot_from_volume).to be(false) - end - end - end - - context 'when global setting for boot_from_volume is true' do - context 'when boot_from_volume is not set for vm type' do - let(:resource_pool) { {} } - - it 'it returns true' do - boot_from_volume = subject.boot_from_volume?(true, resource_pool) - - expect(boot_from_volume).to be(true) - end - end - - context 'when boot_from_volume is set to false for vm type' do - let(:resource_pool) { { 'boot_from_volume' => false } } - - it 'it returns true' do - boot_from_volume = subject.boot_from_volume?(true, resource_pool) - - expect(boot_from_volume).to be(false) - end - end - - context 'when boot_from_volume is set to true for vm type' do - let(:resource_pool) { { 'boot_from_volume' => true } } - - it 'it returns true' do - boot_from_volume = subject.boot_from_volume?(true, resource_pool) - - expect(boot_from_volume).to be(true) - end - end - end - end -end diff --git a/src/bosh_openstack_cpi/vendor/package/addressable-2.8.7.gem b/src/bosh_openstack_cpi/vendor/package/addressable-2.8.7.gem deleted file mode 100644 index c355e1246..000000000 --- a/src/bosh_openstack_cpi/vendor/package/addressable-2.8.7.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232 -size 106496 diff --git a/src/bosh_openstack_cpi/vendor/package/ast-2.4.3.gem b/src/bosh_openstack_cpi/vendor/package/ast-2.4.3.gem deleted file mode 100644 index d623cef02..000000000 --- a/src/bosh_openstack_cpi/vendor/package/ast-2.4.3.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 -size 11776 diff --git a/src/bosh_openstack_cpi/vendor/package/bigdecimal-3.2.2.gem b/src/bosh_openstack_cpi/vendor/package/bigdecimal-3.2.2.gem deleted file mode 100644 index 84667edcc..000000000 --- a/src/bosh_openstack_cpi/vendor/package/bigdecimal-3.2.2.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39085f76b495eb39a79ce07af716f3a6829bc35eb44f2195e2753749f2fa5adc -size 88576 diff --git a/src/bosh_openstack_cpi/vendor/package/bosh-template-2.4.0.gem b/src/bosh_openstack_cpi/vendor/package/bosh-template-2.4.0.gem deleted file mode 100644 index b51ec4e5f..000000000 --- a/src/bosh_openstack_cpi/vendor/package/bosh-template-2.4.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d8ff9a4bac75ab166c9ab44364b8b4bcc33831238003edf02cc95f99fe201e5 -size 10240 diff --git a/src/bosh_openstack_cpi/vendor/package/bosh_common-2.0.0.gem b/src/bosh_openstack_cpi/vendor/package/bosh_common-2.0.0.gem deleted file mode 100644 index c18a262fe..000000000 --- a/src/bosh_openstack_cpi/vendor/package/bosh_common-2.0.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:364f205d802fd43e15c3583e70176e2d7ce7b3ab9338c4239860fc75308011c2 -size 12288 diff --git a/src/bosh_openstack_cpi/vendor/package/bosh_cpi-2.6.0.gem b/src/bosh_openstack_cpi/vendor/package/bosh_cpi-2.6.0.gem deleted file mode 100644 index 0daa21736..000000000 --- a/src/bosh_openstack_cpi/vendor/package/bosh_cpi-2.6.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6303e85420d0e3a77a4481117f3bed2beb3ffb563e367b2b73beb1496272d6f -size 10752 diff --git a/src/bosh_openstack_cpi/vendor/package/builder-3.3.0.gem b/src/bosh_openstack_cpi/vendor/package/builder-3.3.0.gem deleted file mode 100644 index 8fee94b74..000000000 --- a/src/bosh_openstack_cpi/vendor/package/builder-3.3.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f -size 30208 diff --git a/src/bosh_openstack_cpi/vendor/package/byebug-12.0.0.gem b/src/bosh_openstack_cpi/vendor/package/byebug-12.0.0.gem deleted file mode 100644 index 9244b7d2f..000000000 --- a/src/bosh_openstack_cpi/vendor/package/byebug-12.0.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4a150d291cca40b66ec9ca31f754e93fed8aa266a17335f71bb0afa7fca1a1e -size 84992 diff --git a/src/bosh_openstack_cpi/vendor/package/coderay-1.1.3.gem b/src/bosh_openstack_cpi/vendor/package/coderay-1.1.3.gem deleted file mode 100644 index ea8d4e656..000000000 --- a/src/bosh_openstack_cpi/vendor/package/coderay-1.1.3.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b -size 92672 diff --git a/src/bosh_openstack_cpi/vendor/package/crack-1.0.0.gem b/src/bosh_openstack_cpi/vendor/package/crack-1.0.0.gem deleted file mode 100644 index 50ed66033..000000000 --- a/src/bosh_openstack_cpi/vendor/package/crack-1.0.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c83aefdb428cdc7b66c7f287e488c796f055c0839e6e545fec2c7047743c4a49 -size 10752 diff --git a/src/bosh_openstack_cpi/vendor/package/diff-lcs-1.6.2.gem b/src/bosh_openstack_cpi/vendor/package/diff-lcs-1.6.2.gem deleted file mode 100644 index 3d7b961cd..000000000 --- a/src/bosh_openstack_cpi/vendor/package/diff-lcs-1.6.2.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 -size 59392 diff --git a/src/bosh_openstack_cpi/vendor/package/excon-1.2.8.gem b/src/bosh_openstack_cpi/vendor/package/excon-1.2.8.gem deleted file mode 100644 index dee2814f2..000000000 --- a/src/bosh_openstack_cpi/vendor/package/excon-1.2.8.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:150f57a0f3919b8d2b3f74535596f9876389c6dde157e0bfac8f26631eb135d0 -size 171008 diff --git a/src/bosh_openstack_cpi/vendor/package/fog-core-2.6.0.gem b/src/bosh_openstack_cpi/vendor/package/fog-core-2.6.0.gem deleted file mode 100644 index b1df95156..000000000 --- a/src/bosh_openstack_cpi/vendor/package/fog-core-2.6.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fe08aa83a23cddce42f4ba412040c08f890d7ff04c175c0ee59119371245be6 -size 47104 diff --git a/src/bosh_openstack_cpi/vendor/package/fog-json-1.2.0.gem b/src/bosh_openstack_cpi/vendor/package/fog-json-1.2.0.gem deleted file mode 100644 index 5c90c82ef..000000000 --- a/src/bosh_openstack_cpi/vendor/package/fog-json-1.2.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd4f5ab362dbc72b687240bba9d2dd841d5dfe888a285797533f85c03ea548fe -size 8704 diff --git a/src/bosh_openstack_cpi/vendor/package/fog-openstack-1.1.5.gem b/src/bosh_openstack_cpi/vendor/package/fog-openstack-1.1.5.gem deleted file mode 100644 index 0b235b625..000000000 --- a/src/bosh_openstack_cpi/vendor/package/fog-openstack-1.1.5.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:46472a9a50416b99086d67e038f476d57fc5064805fe6ea60ccdf2ec0f13bd46 -size 233472 diff --git a/src/bosh_openstack_cpi/vendor/package/formatador-1.1.1.gem b/src/bosh_openstack_cpi/vendor/package/formatador-1.1.1.gem deleted file mode 100644 index 4405a7f2d..000000000 --- a/src/bosh_openstack_cpi/vendor/package/formatador-1.1.1.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd0870fb00738204c98e9239f81fadc6f7966724d8037ba471684ed180fabbeb -size 14336 diff --git a/src/bosh_openstack_cpi/vendor/package/hashdiff-1.2.0.gem b/src/bosh_openstack_cpi/vendor/package/hashdiff-1.2.0.gem deleted file mode 100644 index a96465111..000000000 --- a/src/bosh_openstack_cpi/vendor/package/hashdiff-1.2.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c984f13e115bfc9953332e8e83bd9d769cfde9944e2d54e07eb9df7b76e140b5 -size 17408 diff --git a/src/bosh_openstack_cpi/vendor/package/httpclient-2.8.3.gem b/src/bosh_openstack_cpi/vendor/package/httpclient-2.8.3.gem deleted file mode 100644 index 1865ae043..000000000 --- a/src/bosh_openstack_cpi/vendor/package/httpclient-2.8.3.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2951e4991214464c3e92107e46438527d23048e634f3aee91c719e0bdfaebda6 -size 385536 diff --git a/src/bosh_openstack_cpi/vendor/package/json-2.13.0.gem b/src/bosh_openstack_cpi/vendor/package/json-2.13.0.gem deleted file mode 100644 index 0df5124b1..000000000 --- a/src/bosh_openstack_cpi/vendor/package/json-2.13.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4bdf1ce8db5617ec6c59e021db4a398e54b57b335e1fa417ac7badc3fb7c1a0 -size 77312 diff --git a/src/bosh_openstack_cpi/vendor/package/language_server-protocol-3.17.0.5.gem b/src/bosh_openstack_cpi/vendor/package/language_server-protocol-3.17.0.5.gem deleted file mode 100644 index d32baa7c1..000000000 --- a/src/bosh_openstack_cpi/vendor/package/language_server-protocol-3.17.0.5.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc -size 78336 diff --git a/src/bosh_openstack_cpi/vendor/package/lint_roller-1.1.0.gem b/src/bosh_openstack_cpi/vendor/package/lint_roller-1.1.0.gem deleted file mode 100644 index d8a70e584..000000000 --- a/src/bosh_openstack_cpi/vendor/package/lint_roller-1.1.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 -size 9728 diff --git a/src/bosh_openstack_cpi/vendor/package/little-plugger-1.1.4.gem b/src/bosh_openstack_cpi/vendor/package/little-plugger-1.1.4.gem deleted file mode 100644 index 389443155..000000000 --- a/src/bosh_openstack_cpi/vendor/package/little-plugger-1.1.4.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5f347c00d9d648040ef7c17d6eb09d3d0719adf19ca30d1a3b6fb26d0a631bb -size 10240 diff --git a/src/bosh_openstack_cpi/vendor/package/logger-1.7.0.gem b/src/bosh_openstack_cpi/vendor/package/logger-1.7.0.gem deleted file mode 100644 index 6f07b5902..000000000 --- a/src/bosh_openstack_cpi/vendor/package/logger-1.7.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 -size 16896 diff --git a/src/bosh_openstack_cpi/vendor/package/logging-2.2.2.gem b/src/bosh_openstack_cpi/vendor/package/logging-2.2.2.gem deleted file mode 100644 index 4ca0c72f5..000000000 --- a/src/bosh_openstack_cpi/vendor/package/logging-2.2.2.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:963359dbdab725a3320eab179017d20d5b9731d7148e1efa8432c184a48a461a -size 87552 diff --git a/src/bosh_openstack_cpi/vendor/package/membrane-1.1.0.gem b/src/bosh_openstack_cpi/vendor/package/membrane-1.1.0.gem deleted file mode 100644 index b14d7e56a..000000000 --- a/src/bosh_openstack_cpi/vendor/package/membrane-1.1.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab84429a9ea9e90e1a568b81f8ed4b03ad388b0aa2202ab97c5b30d210c9c7ff -size 20480 diff --git a/src/bosh_openstack_cpi/vendor/package/method_source-1.1.0.gem b/src/bosh_openstack_cpi/vendor/package/method_source-1.1.0.gem deleted file mode 100644 index 59abbed7e..000000000 --- a/src/bosh_openstack_cpi/vendor/package/method_source-1.1.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5 -size 13824 diff --git a/src/bosh_openstack_cpi/vendor/package/mime-types-3.7.0.gem b/src/bosh_openstack_cpi/vendor/package/mime-types-3.7.0.gem deleted file mode 100644 index 6cb3bb394..000000000 --- a/src/bosh_openstack_cpi/vendor/package/mime-types-3.7.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56 -size 41984 diff --git a/src/bosh_openstack_cpi/vendor/package/mime-types-data-3.2025.0715.gem b/src/bosh_openstack_cpi/vendor/package/mime-types-data-3.2025.0715.gem deleted file mode 100644 index 6b107a9b1..000000000 --- a/src/bosh_openstack_cpi/vendor/package/mime-types-data-3.2025.0715.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:865f9759d5db11f983086a70379f8282e2e64fba7b82058b928c63a7ab79871d -size 169472 diff --git a/src/bosh_openstack_cpi/vendor/package/multi_json-1.17.0.gem b/src/bosh_openstack_cpi/vendor/package/multi_json-1.17.0.gem deleted file mode 100644 index ba9130ec1..000000000 --- a/src/bosh_openstack_cpi/vendor/package/multi_json-1.17.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76581f6c96aebf2e85f8a8b9854829e0988f335e8671cd1a56a1036eb75e4a1b -size 22016 diff --git a/src/bosh_openstack_cpi/vendor/package/netaddr-2.0.6.gem b/src/bosh_openstack_cpi/vendor/package/netaddr-2.0.6.gem deleted file mode 100644 index f6c98c278..000000000 --- a/src/bosh_openstack_cpi/vendor/package/netaddr-2.0.6.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7c3970ec2c2665aa25c4cbf268011d01f5d98c33182e600759a49961e63f1b4 -size 23552 diff --git a/src/bosh_openstack_cpi/vendor/package/openssl-3.3.0.gem b/src/bosh_openstack_cpi/vendor/package/openssl-3.3.0.gem deleted file mode 100644 index 5cfa59107..000000000 --- a/src/bosh_openstack_cpi/vendor/package/openssl-3.3.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff3a573fc97ab30f69483fddc80029f91669bf36532859bd182d1836f45aee79 -size 207872 diff --git a/src/bosh_openstack_cpi/vendor/package/parallel-1.27.0.gem b/src/bosh_openstack_cpi/vendor/package/parallel-1.27.0.gem deleted file mode 100644 index e4a6ff778..000000000 --- a/src/bosh_openstack_cpi/vendor/package/parallel-1.27.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 -size 10752 diff --git a/src/bosh_openstack_cpi/vendor/package/parser-3.3.8.0.gem b/src/bosh_openstack_cpi/vendor/package/parser-3.3.8.0.gem deleted file mode 100644 index e5c969590..000000000 --- a/src/bosh_openstack_cpi/vendor/package/parser-3.3.8.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2476364142b307fa5a1b1ece44f260728be23858a9c71078e956131a75453c45 -size 911872 diff --git a/src/bosh_openstack_cpi/vendor/package/prism-1.4.0.gem b/src/bosh_openstack_cpi/vendor/package/prism-1.4.0.gem deleted file mode 100644 index 90ce6a1eb..000000000 --- a/src/bosh_openstack_cpi/vendor/package/prism-1.4.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc0e3e00e93160213dc2a65519d9002a4a1e7b962db57d444cf1a71565bb703e -size 632832 diff --git a/src/bosh_openstack_cpi/vendor/package/pry-0.15.2.gem b/src/bosh_openstack_cpi/vendor/package/pry-0.15.2.gem deleted file mode 100644 index 3183ecfb4..000000000 --- a/src/bosh_openstack_cpi/vendor/package/pry-0.15.2.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12d54b8640d3fa29c9211dd4ffb08f3fd8bf7a4fd9b5a73ce5b59c8709385b6b -size 155648 diff --git a/src/bosh_openstack_cpi/vendor/package/pry-byebug-3.11.0.gem b/src/bosh_openstack_cpi/vendor/package/pry-byebug-3.11.0.gem deleted file mode 100644 index 581104428..000000000 --- a/src/bosh_openstack_cpi/vendor/package/pry-byebug-3.11.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b0abb7d309bc7f00044d512a3c8567274f7012b944b38becc8440439a1cea72 -size 15872 diff --git a/src/bosh_openstack_cpi/vendor/package/public_suffix-6.0.2.gem b/src/bosh_openstack_cpi/vendor/package/public_suffix-6.0.2.gem deleted file mode 100644 index acb6e46e2..000000000 --- a/src/bosh_openstack_cpi/vendor/package/public_suffix-6.0.2.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfa7cd5108066f8c9602e0d6d4114999a5df5839a63149d3e8b0f9c1d3558394 -size 104960 diff --git a/src/bosh_openstack_cpi/vendor/package/racc-1.8.1.gem b/src/bosh_openstack_cpi/vendor/package/racc-1.8.1.gem deleted file mode 100644 index 95c198e66..000000000 --- a/src/bosh_openstack_cpi/vendor/package/racc-1.8.1.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f -size 65536 diff --git a/src/bosh_openstack_cpi/vendor/package/rainbow-3.1.1.gem b/src/bosh_openstack_cpi/vendor/package/rainbow-3.1.1.gem deleted file mode 100644 index 261f3d0e6..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rainbow-3.1.1.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a -size 13824 diff --git a/src/bosh_openstack_cpi/vendor/package/rake-13.3.0.gem b/src/bosh_openstack_cpi/vendor/package/rake-13.3.0.gem deleted file mode 100644 index 319c2a13a..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rake-13.3.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 -size 86528 diff --git a/src/bosh_openstack_cpi/vendor/package/rb-readline-0.5.5.gem b/src/bosh_openstack_cpi/vendor/package/rb-readline-0.5.5.gem deleted file mode 100644 index 54142d62f..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rb-readline-0.5.5.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e9bd7e198bdef0822c46902f6c592b882c1f9777894a4c3dcf5b320824a8793 -size 96768 diff --git a/src/bosh_openstack_cpi/vendor/package/regexp_parser-2.10.0.gem b/src/bosh_openstack_cpi/vendor/package/regexp_parser-2.10.0.gem deleted file mode 100644 index 799292169..000000000 --- a/src/bosh_openstack_cpi/vendor/package/regexp_parser-2.10.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61 -size 58880 diff --git a/src/bosh_openstack_cpi/vendor/package/rexml-3.4.1.gem b/src/bosh_openstack_cpi/vendor/package/rexml-3.4.1.gem deleted file mode 100644 index 69ff1e000..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rexml-3.4.1.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca -size 104960 diff --git a/src/bosh_openstack_cpi/vendor/package/rspec-3.13.1.gem b/src/bosh_openstack_cpi/vendor/package/rspec-3.13.1.gem deleted file mode 100644 index 5d42482d3..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rspec-3.13.1.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9f9a58fa915b8d94a1d6b3195fe6dd28c4c34836a6097015142c4a9ace72140 -size 10752 diff --git a/src/bosh_openstack_cpi/vendor/package/rspec-core-3.13.5.gem b/src/bosh_openstack_cpi/vendor/package/rspec-core-3.13.5.gem deleted file mode 100644 index 8a8f32f01..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rspec-core-3.13.5.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab3f682897c6131c67f9a17cfee5022a597f283aebe654d329a565f9937a4fa3 -size 167424 diff --git a/src/bosh_openstack_cpi/vendor/package/rspec-expectations-3.13.5.gem b/src/bosh_openstack_cpi/vendor/package/rspec-expectations-3.13.5.gem deleted file mode 100644 index 7dbc9e488..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rspec-expectations-3.13.5.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 -size 89600 diff --git a/src/bosh_openstack_cpi/vendor/package/rspec-its-2.0.0.gem b/src/bosh_openstack_cpi/vendor/package/rspec-its-2.0.0.gem deleted file mode 100644 index a65c4511e..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rspec-its-2.0.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a88e8bc38149f2835e93533591ec4f5c829aacbfd41269a2e6f9f5b82f5260df -size 15360 diff --git a/src/bosh_openstack_cpi/vendor/package/rspec-mocks-3.13.5.gem b/src/bosh_openstack_cpi/vendor/package/rspec-mocks-3.13.5.gem deleted file mode 100644 index 026c87c75..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rspec-mocks-3.13.5.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81 -size 82944 diff --git a/src/bosh_openstack_cpi/vendor/package/rspec-support-3.13.4.gem b/src/bosh_openstack_cpi/vendor/package/rspec-support-3.13.4.gem deleted file mode 100644 index a6d2213dc..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rspec-support-3.13.4.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:184b1814f6a968102b57df631892c7f1990a91c9a3b9e80ef892a0fc2a71a3f7 -size 40448 diff --git a/src/bosh_openstack_cpi/vendor/package/rubocop-1.78.0.gem b/src/bosh_openstack_cpi/vendor/package/rubocop-1.78.0.gem deleted file mode 100644 index c1aeff66b..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rubocop-1.78.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b74a6f912eb4fd3e6878851f7f7f45dcad8c7185c34250d4f952b0ee80d6bc0 -size 713216 diff --git a/src/bosh_openstack_cpi/vendor/package/rubocop-ast-1.46.0.gem b/src/bosh_openstack_cpi/vendor/package/rubocop-ast-1.46.0.gem deleted file mode 100644 index ed8cd011c..000000000 --- a/src/bosh_openstack_cpi/vendor/package/rubocop-ast-1.46.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0da7f6ad5b98614f89b74f11873c191059c823eae07d6ffd40a42a3338f2232b -size 61440 diff --git a/src/bosh_openstack_cpi/vendor/package/ruby-progressbar-1.13.0.gem b/src/bosh_openstack_cpi/vendor/package/ruby-progressbar-1.13.0.gem deleted file mode 100644 index 04a8e4b00..000000000 --- a/src/bosh_openstack_cpi/vendor/package/ruby-progressbar-1.13.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 -size 19456 diff --git a/src/bosh_openstack_cpi/vendor/package/semi_semantic-1.2.0.gem b/src/bosh_openstack_cpi/vendor/package/semi_semantic-1.2.0.gem deleted file mode 100644 index 4fb71c727..000000000 --- a/src/bosh_openstack_cpi/vendor/package/semi_semantic-1.2.0.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fa2d74b075b0dbd2c055cb2a033685a2b79c9c2207afa5c072c207797aaa040 -size 6656 diff --git a/src/bosh_openstack_cpi/vendor/package/timecop-0.9.10.gem b/src/bosh_openstack_cpi/vendor/package/timecop-0.9.10.gem deleted file mode 100644 index 8032df241..000000000 --- a/src/bosh_openstack_cpi/vendor/package/timecop-0.9.10.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12ba45ce57cdcf6b1043cb6cdffa6381fd89ce10d369c28a7f6f04dc1b0cd8eb -size 12800 diff --git a/src/bosh_openstack_cpi/vendor/package/unicode-display_width-3.1.4.gem b/src/bosh_openstack_cpi/vendor/package/unicode-display_width-3.1.4.gem deleted file mode 100644 index 44c2450ad..000000000 --- a/src/bosh_openstack_cpi/vendor/package/unicode-display_width-3.1.4.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8caf2af1c0f2f07ec89ef9e18c7d88c2790e217c482bfc78aaa65eadd5415ac1 -size 18432 diff --git a/src/bosh_openstack_cpi/vendor/package/unicode-emoji-4.0.4.gem b/src/bosh_openstack_cpi/vendor/package/unicode-emoji-4.0.4.gem deleted file mode 100644 index 37cbb19d7..000000000 --- a/src/bosh_openstack_cpi/vendor/package/unicode-emoji-4.0.4.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a -size 167936 diff --git a/src/bosh_openstack_cpi/vendor/package/webmock-3.25.1.gem b/src/bosh_openstack_cpi/vendor/package/webmock-3.25.1.gem deleted file mode 100644 index d57ec4f0f..000000000 --- a/src/bosh_openstack_cpi/vendor/package/webmock-3.25.1.gem +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab9d5d9353bcbe6322c83e1c60a7103988efc7b67cd72ffb9012629c3d396323 -size 71168 From 8f6a8051ee158599769862b21818fa2ea7ebc7e4 Mon Sep 17 00:00:00 2001 From: Saumya Dudeja Date: Wed, 9 Sep 2026 17:13:13 +0200 Subject: [PATCH 2/2] Remove use-golang-cpi ops file (now dead code) --- ci/ops_files/use-golang-cpi.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 ci/ops_files/use-golang-cpi.yml diff --git a/ci/ops_files/use-golang-cpi.yml b/ci/ops_files/use-golang-cpi.yml deleted file mode 100644 index ffff4fbfd..000000000 --- a/ci/ops_files/use-golang-cpi.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -# Deploy the director with the Go CPI job instead of the Ruby one (bosh-deployment/openstack/cpi.yml -# wires openstack_cpi). -- type: replace - path: /instance_groups/name=bosh/jobs/name=openstack_cpi - value: - name: openstack_cpi_golang - release: bosh-openstack-cpi - -- type: replace - path: /instance_groups/name=bosh/properties/director/cpi_job - value: openstack_cpi_golang - -- type: replace - path: /cloud_provider/template - value: - name: openstack_cpi_golang - release: bosh-openstack-cpi