From 36ae8910287a4b16befef6b1f063b3fbfdbd81ce Mon Sep 17 00:00:00 2001 From: Vladimir Moravec Date: Wed, 6 Jan 2016 21:02:49 +0100 Subject: [PATCH 1/3] Add VM scalling tests with cirros image; Update and fix running openstack commands; Extend openstack commands by: * keypair * security_rule * security_group * ip_floating * flavor --- features/scaling_vms.feature | 44 ++++ .../scaling_vms/scaling_steps.rb | 196 ++++++++++++++++++ features/support/env.rb | 4 +- features/support/feature_helpers.rb | 1 - features/support/openstack_helpers.rb | 64 ++++++ lib/cct/commands/openstack.rb | 88 ++++++-- lib/cct/commands/openstack/flavor.rb | 16 ++ lib/cct/commands/openstack/ip_floating.rb | 27 +++ lib/cct/commands/openstack/keypair.rb | 25 +++ lib/cct/commands/openstack/network.rb | 4 +- lib/cct/commands/openstack/role.rb | 2 +- lib/cct/commands/openstack/security_group.rb | 30 +++ lib/cct/commands/openstack/security_rule.rb | 26 +++ lib/cct/commands/openstack/server.rb | 38 ++++ lib/cct/remote_command.rb | 2 +- tasks/features/scaling_vms.rake | 9 + 16 files changed, 555 insertions(+), 21 deletions(-) create mode 100644 features/scaling_vms.feature create mode 100644 features/step_definitions/scaling_vms/scaling_steps.rb create mode 100644 features/support/openstack_helpers.rb create mode 100644 lib/cct/commands/openstack/flavor.rb create mode 100644 lib/cct/commands/openstack/ip_floating.rb create mode 100644 lib/cct/commands/openstack/keypair.rb create mode 100644 lib/cct/commands/openstack/security_group.rb create mode 100644 lib/cct/commands/openstack/security_rule.rb create mode 100644 lib/cct/commands/openstack/server.rb create mode 100644 tasks/features/scaling_vms.rake diff --git a/features/scaling_vms.feature b/features/scaling_vms.feature new file mode 100644 index 0000000..d16db00 --- /dev/null +++ b/features/scaling_vms.feature @@ -0,0 +1,44 @@ +# You need to set the environment variable $cct_vm_number before running this feature +# If your cloud doesn't have the same number of floating IPs as VMs available, set +# the variable $cct_fip_number; floating IPs will be assigned to VMs picked randomly +# from the created pool of VMs. Additionally you can set the way how the VMs are +# going to be spawned, you either can rely on openstack managing the VMs' spawning +# processt_vm_delay to delay the VMs' booting process by number of seconds. +# +# Configuration: +# =================================================================================== +# $cct_vm_number => number of VMs to spawn, default is 10 +# $cct_fip_number => number of floating IPs to be assigned to the created VMs, optional +# $cct_wait_for_vm => optional, default is 1; +# $cct_vm_reserve => optional, default is 3; increases the quotas due to VMs leftovers + +@scaling_vms +Feature: Scaling VMs + As a cloud administrator + I want to verify the cloud can scale to VMs count defined by VM_COUNT env variable + In order to make sure the cloud components work properly + + Background: + Given the environment variable "cct_vm_number" is set + And the variable "cct_vm_number" has value greater than zero + And necessary rules for "default" security group on port "22" are present + And necessary rules for "default" security group on "ipmc" protocol are present + And "default" quotas for cores and instances in project "openstack" have been updated + And respective quotas for nova in project "openstack" have been updated + And respective quotas for neutron in project "openstack" have been updated + + @cirros + Scenario: Scaling with cirros image + Given the image named "cirros-.*x86_64.*-machine" is available + And the flavor "cirros-test" is defined + And the key pair "cirros-test" has been created + And there are no VMs with the name "cirros-test-vm" present + When I request creating VMs with name "cirros-test-vm" + Then I get all the VMs listed as "ACTIVE" + And there are enough floating IPs available + And I assign floating IPs to the VMs + And I can ping running VMs + And I ssh to VMs successfully as "cirros" user + And I remove the floating IPs from all VMs + And I delete floating IPs from the pool + And I delete all the VMs used for testing diff --git a/features/step_definitions/scaling_vms/scaling_steps.rb b/features/step_definitions/scaling_vms/scaling_steps.rb new file mode 100644 index 0000000..8422ea3 --- /dev/null +++ b/features/step_definitions/scaling_vms/scaling_steps.rb @@ -0,0 +1,196 @@ +Given(/^the environment variable "([^"]*)" is set$/) do |vm_count| + ENV["cct_vm_number"] = "10" if ENV["cct_vm_number"].nil? + expect(ENV[vm_count]).not_to be_nil +end + +Given(/^the variable "([^"]*)" has value greater than zero$/) do |vm_count| + @vm_count = ENV[vm_count].to_i + expect(@vm_count).to be > 0 + # Let's expect there is some number of VMs already in place (e.g. testsetup leftovers), + # they must be considered when setting the quotas for neutron and nova later + @vm_reserve = ENV["cct_vm_reserve"] || 3 +end + +Given(/^necessary rules for "([^"]*)" security group on port "([^"]*)" are present$/) do |sec_group, port_number| + rule_found = openstack.security_group.rule.list(sec_group).find do |rule| + rule.port_range.match("#{port_number}:#{port_number}") + end + + if !rule_found + openstack.security_group.rule.create( + sec_group, + dst_port: port_number.to_i + ) + end +end + +Given(/^necessary rules for "([^"]*)" security group on "([^"]*)" protocol are present$/) do |sec_group, protocol| + rule_found = openstack.security_group.rule.list(sec_group).find do |rule| + rule.protocol == "icmp" + end + + if !rule_found + openstack.security_group.rule.create( + sec_group, + proto: "icmp" + ) + end +end + +Given(/^"([^"]*)" quotas for cores and instances in project "([^"]*)" have been updated$/) do |default, project| + quota_class_update( + "default", + instances: @vm_count + @vm_reserve, + cores: @vm_count + @vm_reserve, + floating_ips: @vm_count + @vm_reserve + ) + + quota_class_update( + project, + instances: @vm_count + @vm_reserve, + cores: @vm_count + @vm_reserve, + floating_ips: @vm_count + @vm_reserve + ) +end + +Given(/^respective quotas for nova in project "([^"]*)" have been updated$/) do |project| + quota_update( + :nova, + floating_ips: @vm_count + @vm_reserve, + instances: @vm_count + @vm_reserve, + tenant: project + ) +end + +Given(/^respective quotas for neutron in project "([^"]*)" have been updated$/) do |project| + quota_update( + :neutron, + port: @vm_count + @vm_reserve, + vip: @vm_count + @vm_reserve, + tenant: project + ) + + quota_update( + :neutron, + port: @vm_count + @vm_reserve, + vip: @vm_count + @vm_reserve, + ) +end + +Given(/^the image named "([^"]*)" is available$/) do |image_name| + image = openstack.image.list.find {|i| i.name.match(/#{image_name}$/) } + @image = openstack.image.show(image.name) +end + +Given(/^the flavor "([^"]*)" is defined$/) do |flavor| + @flavor = openstack.flavor.list.find {|f| f.name == flavor } + @flavor = openstack.flavor.create(flavor) if @flavor.nil? +end + +Given(/^the key pair "([^"]*)" has been created$/) do |keypair_name| + @key_path = "/tmp/#{keypair_name}" + control_node.exec!("rm -rf #@key_path*") + control_node.exec!( + "ssh-keygen -t dsa -f #{@key_path} -N ''" + ) + control_node.exec!( + "chmod 600 #{@key_path}*" + ) + + if openstack.keypair.list.find {|k| k.name.match(keypair_name) } + control_node.openstack.keypair.delete(keypair_name) + end + + control_node.openstack.keypair.create(keypair_name, public_key: "#{@key_path}.pub") + @keypair_name = keypair_name +end + +Given(/^there are no VMs with the name "([^"]*)" present$/) do |vm_name| + @vm_name = vm_name + @wait = ENV["cct_wait_for_vm"].nil? ? true : (ENV["cct_wait_for_vm"].to_i.zero? ? false : true) + delete_vms(name: @vm_name) +end + +When(/^I request creating VMs with name "([^"]*)"$/) do |vm_name| + + options = { + image: @image.name, + flavor: @flavor.name, + key_name: @keypair_name, + } + + if @wait + options.merge!(wait: true, max: @vm_count) + openstack.server.create(vm_name, options) + else + 1.upto(@vm_count).each do |num| + openstack.server.create("#{vm_name}-#{num}", options) + end + end +end + +Then(/^I get all the VMs listed as "([^"]*)"$/) do |status_active| + @all_vms = [] + wait_for("VMs being up and running successfully", max: "#{@vm_count*5} seconds", sleep: "2 seconds") do + @all_vms = openstack.server.list.select {|vm| vm.name.match(@vm_name)} + statuses = @all_vms.map(&:status) + active = statuses.select {|status| status == "ACTIVE"} || [] + break if active.count == @all_vms.count + end + expect(@all_vms.count).to eq(@vm_count) +end + +Then(/^there are enough floating IPs available$/) do + fip_limit = ENV["cct_fip_number"].to_i + ips = openstack.ip_floating.list.select {|ip| ip.instance_id.empty? } + + if fip_limit.nonzero? + needed = fip_limit > ips.size ? fip_limit - ips.size : fip_limit + else + needed = @all_vms.size - ips.size + end + + 1.upto(needed).each do + openstack.ip_floating.create("floating") + end + @floating_ips = openstack.ip_floating.list.select {|ip| ip.instance_id.empty? } + @all_vms = @all_vms.sample(fip_limit) if fip_limit.nonzero? +end + +When(/^I assign floating IPs to the VMs$/) do + Ip = Struct.new(:id, :ip) + @vm_ips = {} + @all_vms.each_with_index do |vm, index| + floating = @floating_ips[index] + openstack.ip_floating.add(floating.ip, vm.id) + @vm_ips[vm.id] = Ip.new(floating.id, floating.ip) + end +end + +Then(/^I can ping running VMs$/) do + @all_vms.each {|vm| ping_vm(@vm_ips[vm.id].ip) } +end + +Then(/^I ssh to VMs successfully as "([^"]*)" user$/) do |user| + @all_vms.each do |vm| + control_node.exec!("ssh #{user}@#{@vm_ips[vm.id].ip} -i #{@key_path} 'echo test'") + end +end + +Then(/^I remove the floating IPs from all VMs$/) do + @all_vms.each do |vm| + openstack.ip_floating.remove(@vm_ips[vm.id].ip, vm.id) + end +end + +Then(/^I delete floating IPs from the pool$/) do + @all_vms.each do |vm| + openstack.ip_floating.delete(@vm_ips[vm.id].id) + end +end + +Then(/^I delete all the VMs used for testing$/) do + delete_vms(name: @vm_name) +end + + diff --git a/features/support/env.rb b/features/support/env.rb index 6a8f134..b9e0af4 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -4,6 +4,7 @@ require_relative "step_helpers" require_relative "feature_helpers" require_relative "custom_matchers" +require_relative "openstack_helpers" # Guess verbosity from the cli params verbose = ARGV.grep(/(--verbose|-v)/).empty? ? false : true @@ -27,7 +28,8 @@ World( StepHelpers, - FeatureHelpers + FeatureHelpers, + OpenstackHelpers ) diff --git a/features/support/feature_helpers.rb b/features/support/feature_helpers.rb index 2c72ebc..8aeeda8 100644 --- a/features/support/feature_helpers.rb +++ b/features/support/feature_helpers.rb @@ -1,6 +1,5 @@ module FeatureHelpers attr_reader :scenario_tag, :feature_tag - def proposal barclamp, name: "default" JSON.parse(admin_node.exec!("crowbar #{barclamp} show #{name}").output) end diff --git a/features/support/openstack_helpers.rb b/features/support/openstack_helpers.rb new file mode 100644 index 0000000..820c1da --- /dev/null +++ b/features/support/openstack_helpers.rb @@ -0,0 +1,64 @@ +module OpenstackHelpers + + def ping_vm ip + control_node.exec!("ping", "-q -c 2 -w 3 #{ip}") + end + + def delete_vms name: nil, wait: true, all: false, ids: [] + options = wait ? {wait: true} : {} + + if !ids.empty? + openstack.server.delete(ids, options) + return + end + + if all + openstack.server.list.each {|s| openstack.server.delete(s.id, options) } + return + end + + vms = openstack.server.list + ids = vms.select {|vm| vm.name.match(/#{name}/)}.map(&:id) + openstack.server.delete(ids, options) unless ids.empty? + end + + def openstack + control_node.openstack + end + + def quota_update component, options={} + case component + when :nova then nova_quota_update(options) + when :neutron then neutron_quota_update(options) + end + end + + def quota_class_update classname, options + command = "nova --insecure quota-class-update " + command << "--instances #{options[:instances]} " if options[:instances] + command << "--cores #{options[:cores]} " if options[:cores] + command << "--floating #{options[:floating_ips]} " if options[:floating_ips] + command << classname + control_node.exec!(command) + end + + private + + def nova_quota_update options + command = "nova --insecure quota-update " + command << "--floating-ips #{options[:floating_ips]} " + command << "--instances #{options[:instances]} " + command << options[:tenant] + control_node.exec!(command) + end + + def neutron_quota_update options + command = "neutron --insecure quota-update " + command << "--port #{options[:port]} " + command << "--vip #{options[:vip]} " + command << "--tenant-id #{options[:tenant]}" if options[:tenant] + control_node.exec!(command) + end + +end + diff --git a/lib/cct/commands/openstack.rb b/lib/cct/commands/openstack.rb index ef7002d..dce17c7 100644 --- a/lib/cct/commands/openstack.rb +++ b/lib/cct/commands/openstack.rb @@ -22,6 +22,11 @@ class Client attr_reader :project attr_reader :network attr_reader :role + attr_reader :keypair + attr_reader :security_group + attr_reader :ip_floating + attr_reader :flavor + attr_reader :server # @param [Cct::Node] as the receiver for the openstack client def initialize node @@ -30,6 +35,11 @@ def initialize node @project = Openstack::Project.new(node) @network = Openstack::Network.new(node) @role = Openstack::Role.new(node) + @keypair = Openstack::Keypair.new(node) + @security_group = Openstack::SecurityGroup.new(node) + @ip_floating = Openstack::IpFloating.new(node) + @flavor = Openstack::Flavor.new(node) + @server = Openstack::Server.new(node) end def actions @@ -57,37 +67,50 @@ def actions class Command class << self attr_accessor :command + attr_accessor :subcommand end - attr_reader :node, :log, :params + attr_reader :node, :log, :params, :command, :subcommand, :parent - def initialize node + def initialize node, parent=nil @node = node @log = node.log @params = Params.new + #@command = self.class.command + @command = + if self.class.command.is_a?(Array) + self.class.command.join(" ") + else + self.class.command + end + @subcommand = self.class.subcommand.new(node, self) if self.class.subcommand + @parent = parent end def exec! subcommand, *params + parent_command = (parent && "#{parent.command} ") || "" node.exec!( Openstack::Client::COMMAND, - self.class.command, + parent_command << command, subcommand, - "--insecure", # yes, we ignore server certificates in SSL enabled cloud - *params) + *params, + "--insecure") # yes, we ignore server certificates in SSL enabled cloud end def create name, options={} params.clear - yield params + yield params if block_given? all_params = ["create", name, "--format=shell"].concat(params.extract!(options)) - OpenStruct.new(shell_parse(exec!(all_params).output)) + result = exec!(all_params).output + options[:dont_format_output] ? result : OpenStruct.new(shell_parse(result)) end - def add name, options={} + def add options={} params.clear - yield params - all_params = ["add", name].concat(params.extract!(options)) - OpenStruct.new(shell_parse(exec!(all_params).output)) + yield params if block_given? + all_params = ["add #{options[:args].join(" ")} "].concat(params.extract!(options)) + result = exec!(all_params.join(" ")).output + OpenStruct.new(shell_parse(result)) end def set name, options={} @@ -97,16 +120,18 @@ def set name, options={} OpenStruct.new(shell_parse(exec!(all_params).output)) end - def delete id_or_name + def delete id_or_name, options={} params.clear - exec!("delete", id_or_name) + yield params if block_given? + all_params = ["delete", id_or_name].concat(params.extract!(options)) + exec!(all_params) end - def list *options + def list options=[] params.clear extended = options.last.is_a?(Hash) ? options.pop : {} row = extended[:row] || Struct.new(:id, :name) - result = exec!("list", "--format=csv", "-c ID", "-c Name", options).output + result = exec!("list #{extended[:args]}", "--format=csv", options).output csv_parse(result).map do |csv_row| row.new(*csv_row) end @@ -123,8 +148,26 @@ def exist? id_or_name return false end + def custom *args + options = args.last.is_a?(Hash) ? args.pop : {} + command = args.concat(params.extract!(options)) + result = exec!(command) + case options[:format] + when :csv + csv_parse(result.output).flatten + when :shell + shell_parse(result.output) + else + result.output + end + end + private + def columns struct + {row: struct} + end + def csv_parse csv_data, header: false result = CSV.parse(csv_data) header ? result : result.drop(1) @@ -132,12 +175,19 @@ def csv_parse csv_data, header: false def shell_parse shell_data shell_data.gsub("\"", "").split("\n").reduce({}) do |result, shell_pair| + next result if shell_pair.empty? attribute, value = shell_pair.split("=") result[attribute] = value result end end + def method_missing name, *args, &block + super unless subcommand && name.to_s == subcommand.class.command + + subcommand + end + class Params attr_reader :mandatory, :optional, :properties, :shell @@ -196,7 +246,7 @@ def extract type, options if type == :properties shell.push("--property #{value}=#{options[key]}") if options[key] else - shell.push("#{value}=#{options[key]}") + shell.push("#{value}=\"#{options[key]}\"") end end end @@ -222,4 +272,10 @@ def filter_params type require 'cct/commands/openstack/project' require 'cct/commands/openstack/network' require 'cct/commands/openstack/role' +require 'cct/commands/openstack/keypair' +require 'cct/commands/openstack/security_rule' +require 'cct/commands/openstack/security_group' +require 'cct/commands/openstack/ip_floating' +require 'cct/commands/openstack/flavor' +require 'cct/commands/openstack/server' diff --git a/lib/cct/commands/openstack/flavor.rb b/lib/cct/commands/openstack/flavor.rb new file mode 100644 index 0000000..467c8bd --- /dev/null +++ b/lib/cct/commands/openstack/flavor.rb @@ -0,0 +1,16 @@ +module Cct + module Commands + module Openstack + class Flavor < Command + self.command = "flavor" + + def list *options + super(options << columns( + Struct.new(:id, :name, :ram, :disk, :ephemeral, :vcpus, :is_public) + )) + end + + end + end + end +end diff --git a/lib/cct/commands/openstack/ip_floating.rb b/lib/cct/commands/openstack/ip_floating.rb new file mode 100644 index 0000000..d252a5d --- /dev/null +++ b/lib/cct/commands/openstack/ip_floating.rb @@ -0,0 +1,27 @@ +module Cct + module Commands + module Openstack + class IpFloating < Command + self.command = "ip floating" + + def list *options + super( + options << columns(Struct.new(:id, :pool, :ip, :fixed_ip, :instance_id)) + ) + end + + def add address, server, options={} + super(options.merge(args: [address, server])) do |params| + params.add :optional, name: "--name" + params.add :optional, description: "--description" + end + end + + def remove address, server + custom(:remove, address, server) + end + + end + end + end +end diff --git a/lib/cct/commands/openstack/keypair.rb b/lib/cct/commands/openstack/keypair.rb new file mode 100644 index 0000000..0d3e508 --- /dev/null +++ b/lib/cct/commands/openstack/keypair.rb @@ -0,0 +1,25 @@ +module Cct + module Commands + module Openstack + class Keypair < Command + self.command = "keypair" + + def list *options + super( + options << columns(Struct.new(:name, :fingerprint)) + ) + end + + def public_key key_name + exec!("show", key_name, "--public-key").output + end + + def create name, options={} + super(name, options.merge(dont_format_output: true)) do |params| + params.add :optional, public_key: "--public-key" + end + end + end + end + end +end diff --git a/lib/cct/commands/openstack/network.rb b/lib/cct/commands/openstack/network.rb index 2b7578a..55fb17f 100644 --- a/lib/cct/commands/openstack/network.rb +++ b/lib/cct/commands/openstack/network.rb @@ -5,7 +5,9 @@ class Network < Command self.command = "network" def list *options - super(*(options << {row: Struct.new(:id, :name, :subnets)})) + super( + options << columns(Struct.new(:id, :name, :subnets)) + ) end end end diff --git a/lib/cct/commands/openstack/role.rb b/lib/cct/commands/openstack/role.rb index 38f3f94..43d5bdb 100644 --- a/lib/cct/commands/openstack/role.rb +++ b/lib/cct/commands/openstack/role.rb @@ -5,7 +5,7 @@ class Role < Command self.command = "role" def add name, options={} - super do |params| + super(options.merge(args: [name])) do |params| params.add :optional, domain: "--domain" params.add :optional, project: "--project" params.add :optional, user: "--user" diff --git a/lib/cct/commands/openstack/security_group.rb b/lib/cct/commands/openstack/security_group.rb new file mode 100644 index 0000000..58de4d0 --- /dev/null +++ b/lib/cct/commands/openstack/security_group.rb @@ -0,0 +1,30 @@ +module Cct + module Commands + module Openstack + class SecurityGroup < Command + self.command = "security group" + self.subcommand = Openstack::SecurityRule + + def list *options + super( + options << columns(Struct.new(:id, :name, :description)) + ) + end + + def create name, options={} + super do |params| + params.add :optional, description: "--description" + end + end + + def set name, options={} + super do |params| + params.add :optional, name: "--name" + params.add :optional, description: "--description" + end + end + + end + end + end +end diff --git a/lib/cct/commands/openstack/security_rule.rb b/lib/cct/commands/openstack/security_rule.rb new file mode 100644 index 0000000..b5b6ad9 --- /dev/null +++ b/lib/cct/commands/openstack/security_rule.rb @@ -0,0 +1,26 @@ +module Cct + module Commands + module Openstack + class SecurityRule < Command + self.command = "rule" + + def list group_name, *options + super( + options << {args: group_name}.merge( + columns(Struct.new(:id, :protocol, :ip_range, :port_range)) + ) + ) + end + + def create group_name, options={} + super do |params| + params.add :optional, src_ip: "--src-ip" + params.add :optional, dst_port: "--dst-port" + params.add :optional, proto: "--proto" + end + end + + end + end + end +end diff --git a/lib/cct/commands/openstack/server.rb b/lib/cct/commands/openstack/server.rb new file mode 100644 index 0000000..0cc5163 --- /dev/null +++ b/lib/cct/commands/openstack/server.rb @@ -0,0 +1,38 @@ +module Cct + module Commands + module Openstack + class Server < Command + self.command = "server" + + def list *options + cols = columns( + Struct.new(:id, :name, :status, :networks) + ) + super(options << cols) + end + + def create name, options={} + super do |params| + params.add :optional, wait: "--wait", param_type: :switch + params.add :optional, image: "--image" + params.add :optional, volume: "--volume" + params.add :optional, flavor: "--flavor" + params.add :optional, key_name: "--key-name" + params.add :optional, min: "--min" + params.add :optional, max: "--max" + params.add :optional, property: "--property" + params.add :optional, availability_zone: "--availability-zone" + params.add :optional, security_group: "--security-group" + end + end + + def delete name, options={} + super do |params| + params.add :optional, wait: "--wait", param_type: :switch + end + end + + end + end + end +end diff --git a/lib/cct/remote_command.rb b/lib/cct/remote_command.rb index b7276ad..1a12e58 100644 --- a/lib/cct/remote_command.rb +++ b/lib/cct/remote_command.rb @@ -149,7 +149,7 @@ def set_environment params export_env = options[:environment].map {|env| "export #{env[0]}=#{env[1]};" }.join.strip source_env = source_files.map {|file| "source #{file}; "}.join env = source_env + export_env - log.always("Updating environment with `#{env}`") + log.debug("Updating environment with `#{env}`") env end diff --git a/tasks/features/scaling_vms.rake b/tasks/features/scaling_vms.rake new file mode 100644 index 0000000..bb1604e --- /dev/null +++ b/tasks/features/scaling_vms.rake @@ -0,0 +1,9 @@ +namespace :feature do + feature_name "Scaling VMs" + + namespace :scale do + desc "Scale with cirros VMs" + feature_task :cirros, tags: :@cirros + end +end + From 7422850a06b5230568b0da4b08675b322cedd290 Mon Sep 17 00:00:00 2001 From: Vladimir Moravec Date: Tue, 21 Jun 2016 18:58:07 +0200 Subject: [PATCH 2/3] Handle openstack.image.list output with status column for mitaka --- lib/cct/commands/openstack/image.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/cct/commands/openstack/image.rb b/lib/cct/commands/openstack/image.rb index c020350..4eb7e1b 100644 --- a/lib/cct/commands/openstack/image.rb +++ b/lib/cct/commands/openstack/image.rb @@ -4,6 +4,12 @@ module Openstack class Image < Command self.command = ["image"] + def list *options + super(options << columns( + Struct.new(:id, :name, :status) + )) + end + def create name, options={} super do |params| # mandatory params From f186597510a087a87259a5df435a50785c7dffb8 Mon Sep 17 00:00:00 2001 From: Vladimir Moravec Date: Wed, 22 Jun 2016 00:09:43 +0200 Subject: [PATCH 3/3] Change creating columns for lists --- lib/cct/commands/openstack.rb | 11 +++++++---- lib/cct/commands/openstack/flavor.rb | 6 +++++- lib/cct/commands/openstack/image.rb | 4 +++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/cct/commands/openstack.rb b/lib/cct/commands/openstack.rb index dce17c7..373b609 100644 --- a/lib/cct/commands/openstack.rb +++ b/lib/cct/commands/openstack.rb @@ -130,8 +130,8 @@ def delete id_or_name, options={} def list options=[] params.clear extended = options.last.is_a?(Hash) ? options.pop : {} - row = extended[:row] || Struct.new(:id, :name) - result = exec!("list #{extended[:args]}", "--format=csv", options).output + row = extended[:keys] || Struct.new(:id, :name) + result = exec!("list #{extended[:args]}", "--format=csv", extended[:columns], options).output csv_parse(result).map do |csv_row| row.new(*csv_row) end @@ -164,8 +164,11 @@ def custom *args private - def columns struct - {row: struct} + def columns names + { + keys: Struct.new(*names.keys), + columns: names.values.map {|column_name| "-c \'#{column_name}\' " }.join + } end def csv_parse csv_data, header: false diff --git a/lib/cct/commands/openstack/flavor.rb b/lib/cct/commands/openstack/flavor.rb index 467c8bd..7c3b44a 100644 --- a/lib/cct/commands/openstack/flavor.rb +++ b/lib/cct/commands/openstack/flavor.rb @@ -6,7 +6,11 @@ class Flavor < Command def list *options super(options << columns( - Struct.new(:id, :name, :ram, :disk, :ephemeral, :vcpus, :is_public) + id: "ID", + name: "Name", + ram: "RAM", + disk: "Disk", + is_public: "Is Public" )) end diff --git a/lib/cct/commands/openstack/image.rb b/lib/cct/commands/openstack/image.rb index 4eb7e1b..49acb24 100644 --- a/lib/cct/commands/openstack/image.rb +++ b/lib/cct/commands/openstack/image.rb @@ -6,7 +6,9 @@ class Image < Command def list *options super(options << columns( - Struct.new(:id, :name, :status) + id: "ID", + name: "Name", + status: "Status" )) end