From d5bd2a7e806d7457e772db52ff1fa39302973d57 Mon Sep 17 00:00:00 2001 From: Jared Smartt Date: Wed, 29 Mar 2017 17:10:07 +0000 Subject: [PATCH 1/3] Added SCMB module and CLI command --- CHANGELOG.md | 33 ++++++++------- README.md | 9 +++- examples/scmb.rb | 45 ++++++++++++++++++++ lib/oneview-sdk.rb | 3 +- lib/oneview-sdk/cli.rb | 27 ++++++++++++ lib/oneview-sdk/scmb.rb | 70 +++++++++++++++++++++++++++++++ lib/oneview-sdk/version.rb | 2 +- oneview-sdk.gemspec | 1 + spec/unit/cli/scmb_spec.rb | 70 +++++++++++++++++++++++++++++++ spec/unit/scmb_spec.rb | 86 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 328 insertions(+), 18 deletions(-) create mode 100644 examples/scmb.rb create mode 100644 lib/oneview-sdk/scmb.rb create mode 100644 spec/unit/cli/scmb_spec.rb create mode 100644 spec/unit/scmb_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index d489f26f8..48fecfb4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -# v4.2.0 +## v4.3.0 + - Added SCMB module and CLI command + +## v4.2.0 #### New Resources: - OS Deployment Plan @@ -16,7 +19,7 @@ #### Design changes: - Architecture for future API500 support. Features for API500 are not yet supported. -# v4.1.0 +## v4.1.0 #### New Resources: Added full support to Image Streamer Rest API version 300: @@ -71,7 +74,7 @@ Added full support to Image Streamer Rest API version 300: - Deployment Plan (unimplemented) - Golden Image (unimplemented) -# v3.1.0 +## v3.1.0 Added full support to OneView Rest API version 300 for the hardware variants C7000 and Synergy to the already existing features: - Interconnect - Logical Interconnect @@ -127,12 +130,12 @@ Added full support to OneView Rest API version 300 for the hardware variants C70 - Unmanaged devices # v3.0.0 -### Notes +#### Notes This is the Third major version of the Ruby SDK for HPE OneView. It features a split in the API support, allowing for C7000 and Synergy hardware variants to be used, while maintaining compatibility to older versions. There are some code improvements applied throughout the release, as well as additional endpoints support. This version of this SDK officially supports OneView appliances version 3.00.00 or higher, using the OneView Rest API version 300. Support is provided for C7000 and Synergy enclosure types. -### Major changes +#### Major changes 1. Added full support to OneView Rest API version 300 for the hardware variants C7000 and Synergy to the already existing features: - Connection template - Datacenter @@ -176,23 +179,23 @@ Added full support to OneView Rest API version 300 for the hardware variants C70 - Split features into API modules for each hardware variant - Fixed/updated/added CLI commands -### v2.2.1 +## v2.2.1 - Fixed issue #88 (firmware bundle file size). Uses multipart-post now -### v2.2.0 +## v2.2.0 - Added the 'rest' and 'update' commands to the CLI - Removed the --force option from the create_from_file CLI command -### v2.1.0 +## v2.1.0 - Fixed issue with the :resource_named method for OneViewSDK::Resource in Ruby 2.3 # v2.0.0 -### Notes +#### Notes This is the second version of the Ruby SDK for HPE OneView. It was given support to the major features of OneView, refactor in some of the already existing code, and also a full set of exceptions to make some common exceptions more explicit in the debugging process. This version of this SDK officially supports OneView appliances version 2.00.00 or higher, using the OneView Rest API version 200. For now only C7000 enclosure types are being supported. -### Major changes +#### Major changes 1. Added full support to the already existing features: - Server Profile - Server Profile Template @@ -213,7 +216,7 @@ Added full support to OneView Rest API version 300 for the hardware variants C70 - Server hardware types 3. New exceptions to address the most common issues (Check them in *lib/oneview-sdk/resource/exceptions.rb*) -### Breaking changes +#### Breaking changes 1. Refactored some method names that may cause incompatibility with older SDK versions. Due to the nature of OneView, the `create` and `delete` methods did not fit the physical infrastructure elements like Enclosures, or Switches, so they now have `add` and `remove` methods that act the same as before, but now it leaves no margin to misunderstand that OneView could actually create these resources. They are: - Datacenters - Enclosure @@ -229,7 +232,7 @@ Added full support to OneView Rest API version 300 for the hardware variants C70 - Switches - Unmanaged devices -### Features supported +#### Features supported - Ethernet network - FC network - FCOE network @@ -265,13 +268,13 @@ Added full support to OneView Rest API version 300 for the hardware variants C70 - Unmanaged devices # v1.0.0 -### Notes +#### Notes This is the first release of the OneView SDK in Ruby and it adds full support to some core features listed bellow, with some execeptions that are explicit. This version of this SDK supports OneView appliances version 2.00.00 or higher, using the OneView Rest API version 200. For now it only supports C7000 enclosure types. -### Features supported +#### Features supported - Ethernet Network - FC Network - FCoE Network @@ -292,7 +295,7 @@ Added full support to OneView Rest API version 300 for the hardware variants C70 - Server Profile Template (CRUD supported) - Server Hardware (CRUD Supported) -### Known issues +#### Known issues The integration tests may warn about 3 issues: 1. OneviewSDK::LogicalInterconnect Firmware Updates perform the actions Stage diff --git a/README.md b/README.md index 096247ad0..17de36191 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The OneView SDK provides a Ruby library to easily interact with HPE OneView and - Require the gem in your Gemfile: ```ruby - gem 'oneview-sdk', '~> 4.2' + gem 'oneview-sdk', '~> 4.3' ``` Then run `$ bundle install` @@ -410,6 +410,13 @@ $ oneview-sdk-ruby cert import https://oneview.example.com Cert added to '/home/users/user1/.oneview-sdk-ruby/trusted_certs.cer' ``` +##### Subscribe to the OneView State Change Message Bus (SCMB): + +```bash +$ oneview-sdk-ruby scmb +$ oneview-sdk-ruby scmb -r 'scmb.ethernet-networks.#' +``` + ## License This project is licensed under the Apache 2.0 license. Please see [LICENSE](LICENSE) for more info. diff --git a/examples/scmb.rb b/examples/scmb.rb new file mode 100644 index 000000000..f97196f4f --- /dev/null +++ b/examples/scmb.rb @@ -0,0 +1,45 @@ +# (c) Copyright 2017 Hewlett Packard Enterprise Development LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require_relative '_client' # Gives access to @client + +# You can create a connection to the SCMB very easily; your client object has everything +# the ::new_connection method needs to connect: +connection = OneviewSDK::SCMB.new_connection(@client) + +# The ::new_connection method above does some setup tasks for you if needed, including +# creating a keypair for the default RabbitMQ user on OneView (rabitmq_readonly) + +# Now you can create a queue that will be used to subscribe to messages. By default it +# will use the least specific routing key possible, so it will respond to ALL events: +q = OneviewSDK::SCMB.new_queue(connection) +puts "Created queue: #{q.name}" + +# More likely, you'll want to subscribe to a smaller subset of events. For example, to +# subscribe to only messages posted when an ethernet network is created: +eth_create_q = OneviewSDK::SCMB.new_queue(connection, 'scmb.ethernet-networks.Created.#') +puts "Created queue: #{eth_create_q.name}\n\n" + +# Here are some other routing key options for the ethernet network resource: +# 'scmb.ethernet-networks.#' -> Any ethernet network event +# 'scmb.ethernet-networks.Updated.' -> Only when a specific network is updated + +puts 'Subscribing to OneView messages. To exit, press Ctrl + c' + +# Then when you're ready to start listening, subscribe to a queue. Here we'll just print +# out the message, but you'll insert your own logic in the block below. Also, see +# http://rubybunny.info/articles/queues.html for more details & options. +q.subscribe(block: true) do |_delivery_info, _properties, payload| + data = JSON.parse(payload) rescue payload + puts 'Received message with payload:' + pp data # Pretty print + puts "\n#{'=' * 50}\n" +end diff --git a/lib/oneview-sdk.rb b/lib/oneview-sdk.rb index e9fa84e13..d948989be 100644 --- a/lib/oneview-sdk.rb +++ b/lib/oneview-sdk.rb @@ -14,8 +14,9 @@ require_relative 'oneview-sdk/client' require_relative 'oneview-sdk/resource' Dir[File.dirname(__FILE__) + '/oneview-sdk/resource/*.rb'].each { |file| require file } -require_relative 'oneview-sdk/cli' +require_relative 'oneview-sdk/scmb' require_relative 'oneview-sdk/image_streamer' +require_relative 'oneview-sdk/cli' # Module for interacting with the HPE OneView API module OneviewSDK diff --git a/lib/oneview-sdk/cli.rb b/lib/oneview-sdk/cli.rb index 02246d830..91fd113a5 100644 --- a/lib/oneview-sdk/cli.rb +++ b/lib/oneview-sdk/cli.rb @@ -428,6 +428,33 @@ def cert(type, url = ENV['ONEVIEWSDK_URL']) fail_nice e.message end + method_option :route, + desc: 'Routing key to filter messages', + type: :string, + aliases: '-r', + default: OneviewSDK::SCMB::DEFAULT_ROUTING_KEY + method_option :format, + desc: 'Output format', + aliases: '-f', + enum: %w(json yaml raw), + default: 'json' + scmb_examples = "\n oneview-sdk-ruby scmb -r 'scmb.ethernet-networks.#'" + scmb_examples << "\n oneview-sdk-ruby scmb -r 'scmb.ethernet-networks.Created'" + scmb_examples << "\n oneview-sdk-ruby scmb -r 'scmb.ethernet-networks.Updated.'" + desc 'scmb', "Subscribe to the OneView State Change Message Bus. Examples:#{scmb_examples}" + # Subscribe to the OneView SCMB + def scmb + client_setup + connection = OneviewSDK::SCMB.new_connection(@client) + q = OneviewSDK::SCMB.new_queue(connection, @options['route']) + puts 'Subscribing to OneView messages. To exit, press Ctrl + c' + q.subscribe(block: true) do |_delivery_info, _properties, payload| + data = JSON.parse(payload) rescue payload + puts "\n#{'=' * 50}\n\nReceived message with payload:" + @options['format'] == 'raw' ? puts(payload) : output(data) + end + end + private def fail_nice(msg = nil, exit_code = 1) diff --git a/lib/oneview-sdk/scmb.rb b/lib/oneview-sdk/scmb.rb new file mode 100644 index 000000000..87fe2433f --- /dev/null +++ b/lib/oneview-sdk/scmb.rb @@ -0,0 +1,70 @@ +# (c) Copyright 2017 Hewlett Packard Enterprise Development LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require_relative 'client' +require 'bunny' + +module OneviewSDK + # State Schange Message Bus (SCMB) helper + module SCMB + DEFAULT_ROUTING_KEY = 'scmb.#'.freeze + + # Create a new connection to the message bus + # @param [OneviewSDK::Client] client The client object for the OneView appliance + # @param [Hash] opts Connection options (passed to Bunny.new). Defaults: + # port: 5671 + # auth_mechanism: 'EXTERNAL' + # tls: true + # verify_peer: client.ssl_enabled + # tls_cert: (retrieved automatically from OneView) + # tls_key: (retrieved automatically from OneView) + # tls_ca_certificates: System default CA (unless verify_peer is false) + # See http://rubybunny.info/articles/connecting.html for more details & options + # @return [Bunny::Session] Connection to the message bus + def self.new_connection(client, opts = {}) + con_opts = { + port: 5671, + auth_mechanism: 'EXTERNAL', + tls: true, + verify_peer: client.ssl_enabled + } + con_opts.merge!(opts) + con_opts[:host] = URI.parse(client.url).host + unless con_opts[:tls_cert] && con_opts[:tls_key] + kp = get_or_create_keypair(client) + con_opts[:tls_cert] = kp['base64SSLCertData'] + con_opts[:tls_key] = kp['base64SSLKeyData'] + end + Bunny.new(con_opts).start + end + + # Retrieve or create the default RabbitMQ keypair + # @param [OneviewSDK::Client] client The client object for the OneView appliance + # @return [Hash] Keypair details + def self.get_or_create_keypair(client) + client.response_handler(client.rest_get('/rest/certificates/client/rabbitmq/keypair/default')) + rescue OneviewSDK::NotFound # Create the keypair if it doesn't exist + client.logger.info('RabbitMQ default keypair not found. Creating it now.') + opts = { commonName: 'default', type: 'RabbitMqClientCertV2' } + client.response_handler(client.rest_post('/rest/certificates/client/rabbitmq', body: opts)) + # Retrieve the created key + client.response_handler(client.rest_get('/rest/certificates/client/rabbitmq/keypair/default')) + end + + # @param [Bunny::Session] Connection to the message bus. See ::new_connection + # @return [Bunny::Queue] Queue listening to the specified routing key + def self.new_queue(connection, routing_key = DEFAULT_ROUTING_KEY) + ch = connection.create_channel + q = ch.queue('') + q.bind('scmb', routing_key: routing_key) + end + end +end diff --git a/lib/oneview-sdk/version.rb b/lib/oneview-sdk/version.rb index 67c79991e..6b7c92409 100644 --- a/lib/oneview-sdk/version.rb +++ b/lib/oneview-sdk/version.rb @@ -11,5 +11,5 @@ # Gem version defined here module OneviewSDK - VERSION = '4.2.0'.freeze + VERSION = '4.3.0'.freeze end diff --git a/oneview-sdk.gemspec b/oneview-sdk.gemspec index 845d6f8a7..04e4c0e6d 100644 --- a/oneview-sdk.gemspec +++ b/oneview-sdk.gemspec @@ -33,6 +33,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'highline' spec.add_runtime_dependency 'pry' spec.add_runtime_dependency 'multipart-post' + spec.add_runtime_dependency 'bunny' spec.add_development_dependency 'coveralls' spec.add_development_dependency 'bundler' diff --git a/spec/unit/cli/scmb_spec.rb b/spec/unit/cli/scmb_spec.rb new file mode 100644 index 000000000..28fe75272 --- /dev/null +++ b/spec/unit/cli/scmb_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +RSpec.describe OneviewSDK::Cli do + include_context 'cli context' + + describe '#scmb' do + context 'with invalid options' do + it 'requires a valid format' do + expect($stderr).to receive(:puts).with(/Expected.+format/) + described_class.start(%w(scmb -f InvalidFormat)) + end + end + + let(:command) { OneviewSDK::Cli.start(%w(scmb)) } + let(:command_eth) { OneviewSDK::Cli.start(%w(scmb -r scmb.ethernet-networks.#)) } + let(:con) { double('connection') } + let(:chan) { double('channel') } + let(:queue) { double('queue') } + let(:payload) { { resourceUri: '/rest/fake', changeType: 'Updated' } } + + + before :each do + allow(OneviewSDK::SCMB).to receive(:new_connection).and_return(con) + allow(STDOUT).to receive(:puts).with(/Subscribing/) + end + + it 'subscribes to the queue' do + allow(OneviewSDK::SCMB).to receive(:new_queue) do |_c, route| + expect(route).to eq(OneviewSDK::SCMB::DEFAULT_ROUTING_KEY) + queue + end + allow(queue).to receive(:subscribe).and_return(true) + command + end + + it 'uses the routing key (-r) option' do + allow(OneviewSDK::SCMB).to receive(:new_queue) do |_c, route| + expect(route).to eq('scmb.ethernet-networks.#') + queue + end + allow(queue).to receive(:subscribe).and_return(true) + command_eth + end + + context 'with output' do + before :each do + allow(OneviewSDK::SCMB).to receive(:new_queue) do |_c, route| + expect(route).to eq('scmb.ethernet-networks.#') + queue + end + allow(queue).to receive(:subscribe) do |_args, &block| + block.yield(nil, nil, payload.to_json) + true + end + expect(STDOUT).to receive(:puts).with(/Subscribing/) + expect(STDOUT).to receive(:puts).with(/Received.+with.+payload:/) + end + + it 'outputs the payload in json format by default' do + expect(STDOUT).to receive(:puts).with(/#{JSON.pretty_generate(payload)}/) + command_eth + end + + it 'outputs the payload in raw format' do + expect(STDOUT).to receive(:puts).with(/#{payload.to_json}/) + OneviewSDK::Cli.start(%w(scmb -r scmb.ethernet-networks.# -f raw)) + end + end + end +end diff --git a/spec/unit/scmb_spec.rb b/spec/unit/scmb_spec.rb new file mode 100644 index 000000000..badec64eb --- /dev/null +++ b/spec/unit/scmb_spec.rb @@ -0,0 +1,86 @@ +require_relative './../spec_helper' +require 'uri' + +# Tests for the SCMB module +RSpec.describe OneviewSDK::SCMB do + include_context 'shared context' + + let(:valid_url) { 'https://ov.example.com' } + let(:keypair) do + { + 'base64SSLCertData' => 'cert1', + 'base64SSLKeyData' => 'key1' + } + end + let(:keypair_uri) { '/rest/certificates/client/rabbitmq/keypair/default' } + + describe '::new_connection' do + let(:con) { double('connection') } + + before :each do + allow(con).to receive(:start).and_return(con) + allow(described_class).to receive(:get_or_create_keypair).and_return(keypair) + end + + it 'creates a Bunny connection with the correct defaults' do + allow(Bunny).to receive(:new) do |opts| + expect(opts[:host]).to eq(URI.parse(@client_300.url).host) + expect(opts[:verify_peer]).to eq(@client_300.ssl_enabled) + expect(opts[:tls_cert]).to eq('cert1') + expect(opts[:tls_key]).to eq('key1') + con + end + expect(described_class.new_connection(@client_300)).to eq(con) + end + + it 'allows you to override the defaults' do + allow(Bunny).to receive(:new) do |opts| + expect(opts[:host]).to eq(URI.parse(@client_300.url).host) # Doesn't actually override the value + expect(opts[:verify_peer]).to eq(false) + expect(opts[:tls_cert]).to eq('c2') + expect(opts[:tls_key]).to eq('k2') + expect(opts[:key]).to eq('val') + con + end + override_opts = { host: 'fake', verify_peer: false, tls_cert: 'c2', tls_key: 'k2', key: 'val' } + described_class.new_connection(@client_300, override_opts) + end + end + + describe '::get_or_create_keypair' do + it 'gets the default rabbitmq keypair' do + expect(@client_300).to receive(:rest_get).with(keypair_uri).and_return(FakeResponse.new(keypair)) + expect(described_class.get_or_create_keypair(@client_300)). to eq(keypair) + end + + it 'creates the default rabbitmq keypair if it does not exist' do + resp1 = FakeResponse.new('', 404) + resp2 = FakeResponse.new(keypair) + expect(@client_300).to receive(:rest_get).with(keypair_uri).and_return(resp1, resp2) + expect(@client_300).to receive(:rest_post).with('/rest/certificates/client/rabbitmq', Hash) + .and_return(FakeResponse.new) + expect(@client_300.logger).to receive(:info).with(/default keypair not found\. Creating it/) + expect(described_class.get_or_create_keypair(@client_300)). to eq(keypair) + end + end + + describe '::new_queue' do + let(:con) { double('connection') } + let(:chan) { double('channel') } + let(:queue) { double('queue') } + + it 'creates an unnamed queue and binds to it' do + expect(con).to receive(:create_channel).and_return(chan) + expect(chan).to receive(:queue).and_return(queue) + expect(queue).to receive(:bind).with('scmb', routing_key: 'scmb.#').and_return(:val) + expect(described_class.new_queue(con)).to eq(:val) + end + + it 'accepts a routing key param' do + expect(con).to receive(:create_channel).and_return(chan) + expect(chan).to receive(:queue).and_return(queue) + expect(queue).to receive(:bind).with('scmb', routing_key: 'key').and_return(:val) + described_class.new_queue(con, 'key') + end + end +end From 39b7326c96a91dd551a56ce7ea294e5187e7815d Mon Sep 17 00:00:00 2001 From: Jared Smartt Date: Wed, 29 Mar 2017 13:17:06 -0600 Subject: [PATCH 2/3] SCMB connection use client logger --- lib/oneview-sdk/cli.rb | 2 +- lib/oneview-sdk/scmb.rb | 3 ++- spec/unit/scmb_spec.rb | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/oneview-sdk/cli.rb b/lib/oneview-sdk/cli.rb index 91fd113a5..f893c0896 100644 --- a/lib/oneview-sdk/cli.rb +++ b/lib/oneview-sdk/cli.rb @@ -54,7 +54,7 @@ def execute! class_option :ssl_verify, type: :boolean, - desc: 'Enable/Disable SSL verification for requests. Can also use ENV[\'ONEVIEWSDK_SSL_ENABLED\']', + desc: 'Enable/Disable SSL verification for requests. Uses ENV[\'ONEVIEWSDK_SSL_ENABLED\']', default: nil class_option :url, diff --git a/lib/oneview-sdk/scmb.rb b/lib/oneview-sdk/scmb.rb index 87fe2433f..052b72cbf 100644 --- a/lib/oneview-sdk/scmb.rb +++ b/lib/oneview-sdk/scmb.rb @@ -34,7 +34,8 @@ def self.new_connection(client, opts = {}) port: 5671, auth_mechanism: 'EXTERNAL', tls: true, - verify_peer: client.ssl_enabled + verify_peer: client.ssl_enabled, + logger: client.logger } con_opts.merge!(opts) con_opts[:host] = URI.parse(client.url).host diff --git a/spec/unit/scmb_spec.rb b/spec/unit/scmb_spec.rb index badec64eb..2631d6c06 100644 --- a/spec/unit/scmb_spec.rb +++ b/spec/unit/scmb_spec.rb @@ -26,6 +26,7 @@ allow(Bunny).to receive(:new) do |opts| expect(opts[:host]).to eq(URI.parse(@client_300.url).host) expect(opts[:verify_peer]).to eq(@client_300.ssl_enabled) + expect(opts[:logger]).to eq(@client_300.logger) expect(opts[:tls_cert]).to eq('cert1') expect(opts[:tls_key]).to eq('key1') con From b0d0268a904ea9fbb2b55df8982cd81f8b72c14e Mon Sep 17 00:00:00 2001 From: Jared Smartt Date: Tue, 18 Apr 2017 09:56:26 -0600 Subject: [PATCH 3/3] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3072eab32..ba2365c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Unreleased Changes ##### Suggested release: v4.3.0 +#### New Features: +- Added SCMB module and CLI command + #### Bug fixes & Enhancements: - [#222](https://github.com/HewlettPackard/oneview-sdk-ruby/issues/222) Error listing the OS Deployment Plans from OneView -- Added SCMB module and CLI command ## v4.2.0