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/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 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 7c2aa77b3..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_disk.png and /dev/null differ 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 a10bad97d..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_create_vm.png and /dev/null differ diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.graffle deleted file mode 100644 index 51784f760..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.graffle +++ /dev/null @@ -1,2231 +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 - - - 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 fa1a3d4b6..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_createstemcell.png and /dev/null differ 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 1383407c4..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_disk.png and /dev/null differ diff --git a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.graffle b/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.graffle deleted file mode 100644 index ccad8a374..000000000 --- a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.graffle +++ /dev/null @@ -1,3307 +0,0 @@ - - - - - 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 5aa411ab4..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_delete_vm.png and /dev/null differ 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 413ae9ec0..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_deletestemcell.png and /dev/null differ 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 abe7fc82b..000000000 Binary files a/src/bosh_openstack_cpi/docs/images/openstack_cpi_initialize.png and /dev/null differ 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