-
-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for NVME Controllers #300
Conversation
2c9aa8e
to
e425c52
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For RuboCop it's probably best to disable it in that class.
attributes[:volumes]&.map! do |vol| | ||
return true unless vol.key?(:controller_key) | ||
end | ||
false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder why you're using map!
. Wouldn't that potentially wipe data? Isn't this what you're looking for?
attributes[:volumes]&.map! do |vol| | |
return true unless vol.key?(:controller_key) | |
end | |
false | |
attributes[:volumes]&.any? { |vol| !vol.key?(:controller_key) } || false |
Technically || false
isn't needed since nil
and false
evaluate to the same, but it ensures a proper boolean is returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's a much better approach, Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend reading https://docs.ruby-lang.org/en/master/Enumerable.html because it's extremely useful to know. Many things include Enumerable
, making your life easier.
def nvme_controller | ||
nvme_controllers.first | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From reading the above you're adding this, but deprecating it in the same PR. Wouldn't it be better to not introduce it at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we can just not deprecate it. Scsi controllers were deprecated in 60a5dc3#diff-49106de6ade06895f1a509db7c9cd040ed76bfdb472548d8b99efc0468dce548R9 , not sure why
I tried to mirror the behavior but I guess we can undo that. @chris1984 , any idea why scsi_controllers were deprecated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note it's the singular scsi_controller
that's deprecated. IMHO it makes sense if you can have more than one that there isn't a singular version. Since it was done in 2016, by this point you can probably delete it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need to add mocked data to
fog-vsphere/lib/fog/vsphere/compute.rb
Line 352 in 4a7f4e7
def self.data |
self.key ||= 2000 | ||
self.type ||= "VirtualNVMEController" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we extract those into constants:
DEFAULT_KEY = 2000
DEFAULT_TYPE = "VirtualNVMEController"
# ...
self.key ||= DEFAULT_KEY
self.type ||= DEFAULT_TYPE
@@ -11,6 +11,7 @@ class SCSIController < Fog::Model | |||
def initialize(attributes = {}) | |||
super | |||
self.key ||= 1000 | |||
self.type ||= "VirtualLsiLogicController" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to extract to constant too
@@ -7,6 +7,7 @@ class Server < Fog::Compute::Server | |||
extend Fog::Deprecation | |||
deprecate(:ipaddress, :public_ip_address) | |||
deprecate(:scsi_controller, :scsi_controllers) | |||
deprecate(:nvme_controller, :nvme_controllers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we can skip creating this method in the first place. If we don't want people to use it - we can skip creating it.
end | ||
|
||
def update_controller_key(vol) | ||
if !attributes[:scsi_controllers].empty? && !vol[:controller_key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I like early returns, this way we are clearly defining a precondition for the rest of the function
return if vol[:controller_key]
And then we can avoid the if statement altogether:
attributes[:scsi_controllers].first&.key || 1000
def initialize_volumes | ||
if attributes[:volumes] && attributes[:volumes].is_a?(Array) | ||
if attributes[:volumes] && attributes[:volumes].is_a?(Array) && !attributes[:volumes].empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think precondition here would be also more readable:
return unless attributes[:volumes].is_a?(Array) && !attributes[:volumes].empty?
We can skip the first condition entirely, since:
[4] pry(main)> nil.is_a?(Array)
=> false
attributes[:nvme_controllers].map! do |controller| | ||
controller.is_a?(Hash) ? Fog::Vsphere::Compute::NVMEController.new(controller) : controller | ||
end | ||
elsif attributes[:nvme_controller] && attributes[:nvme_controller].is_a?(Hash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we decided that nvme_controller
should be removed, this part will need to be removed also.
0ca767a
to
e8469e2
Compare
Not tested, but code-wise looks good |
Starting to test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the Layout/EmptyLineAfterGuardClause enabled here, but I like the style where you leave an empty line after a return statement.
.rubocop.yml
Outdated
Metrics/BlockLength: | ||
Enabled: false | ||
|
||
Style/MutableConstant: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please keep this cop enabled
.rubocop.yml
Outdated
Metrics/ClassLength: | ||
Enabled: false | ||
|
||
Metrics/BlockLength: | ||
Enabled: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was more thinking about disabling them in the Ruby files where needed rather than globally.
vol.server = self | ||
vol | ||
end | ||
return unless attributes[:volumes].is_a?(Array) && !attributes[:volumes].empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd leave out the empty check because calling map on an empty array is essentially a noop.
return unless attributes[:volumes].is_a?(Array) && !attributes[:volumes].empty? | |
return unless attributes[:volumes].is_a?(Array) |
return if vol[:controller_key] | ||
vol[:controller_key] = attributes[:scsi_controllers].first&.key || 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more common pattern is:
return if vol[:controller_key] | |
vol[:controller_key] = attributes[:scsi_controllers].first&.key || 1000 | |
vol[:controller_key] ||= attributes[:scsi_controllers].first&.key || 1000 |
end | ||
|
||
def initialize_nvme_controllers | ||
if attributes[:nvme_controllers] && attributes[:nvme_controllers].is_a?(Array) && !attributes[:nvme_controllers].empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is sufficient:
if attributes[:nvme_controllers] && attributes[:nvme_controllers].is_a?(Array) && !attributes[:nvme_controllers].empty? | |
if attributes[:nvme_controllers].is_a?(Array) |
attributes[:nvme_controllers]
must be true for attributes[:nvme_controllers].is_a?(Array)
to be true because only false
and nil
are the only values that evaluate to false. Neither false.is_a?(Array)
nor nil.is_a?(Array)
would pass, so you can drop it.
The empty check is also redundant, because calling map!
on an empty array would be a noop.
.rubocop.yml
Outdated
Enabled: false | ||
|
||
Style/MutableConstant: | ||
Enabled: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make sure you keep line endings at the end of files.
@@ -142,6 +142,10 @@ def device_change(attributes) | |||
devices << scsi_controllers.each_with_index.map { |controller, index| create_controller(controller, index) } | |||
end | |||
|
|||
if (nvme_controllers = (attributes[:nvme_controllers])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (nvme_controllers = (attributes[:nvme_controllers])) | |
if (nvme_controllers = attributes[:nvme_controllers]) |
@@ -255,14 +259,18 @@ def create_controller(controller = nil, index = 0) | |||
operation: options[:operation], | |||
device: controller_class.new(key: options[:key] || (1000 + index), | |||
busNumber: options[:bus_id] || index, | |||
**(options[:type] == RbVmomi::VIM::VirtualAHCIController ? {} : {sharedBus: controller_get_shared_from_options(options)})) | |||
**(check_type(options[:type]) ? {} : {sharedBus: controller_get_shared_from_options(options)})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check_type
is a really generic name. I'd be tempted to write the class as something like:
def create_controller(controller = nil, index = 0)
# ...
controller_class = ...
device_options = {}
unless [RbVmomi::VIM::VirtualAHCIController, RbVmomi::VIM::VirtualNVMEController, "VirtualNVMEController"].include?(options[:type])
device_options[:sharedBus] = controller_get_shared_from_options(options)
end
{
operation: options[:operation],
device: controller_class.new(key: options[:key] || (1000 + index),
busNumber: options[:bus_id] || index,
**device_options)
}
And then you can consider the following, but now we're really getting in the code refactoring realm.
device_options = {
key: options.fetch(:key, 1000 + index),
busNumber: options.fetch(:bus_id, index),
}
# require 'pry' | ||
# binding.pry | ||
ctrl = get_vm_ref(vm_id).config.hardware.device.find { |device| device.is_a?(RbVmomi::VIM::VirtualNVMEController) } | ||
raise(Fog::Vsphere::Compute::NotFound) unless ctrl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be explicit what wasn't found
raise(Fog::Vsphere::Compute::NotFound) unless ctrl | |
raise Fog::Vsphere::Compute::NotFound, "No NVME controller found for #{vm_id}" unless ctrl |
# require 'pry' | ||
# binding.pry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# require 'pry' | |
# binding.pry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ready for re-review
35135ee
to
26303a4
Compare
lib/fog/vsphere/compute.rb
Outdated
@@ -346,11 +349,11 @@ def is_uuid?(id) | |||
end | |||
end | |||
|
|||
class Mock | |||
class Mock # rubocop:disable Metrics/ClassLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will need to reenable the cop after the class
lib/fog/vsphere/compute.rb
Outdated
include Shared | ||
# rubocop:disable Metrics/MethodLength | ||
def self.data | ||
@data ||= Hash.new do |hash, key| | ||
@data ||= Hash.new do |hash, key| # rubocop:disable Metrics/BlockLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will need to reenable the cop after the block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the difference between an inline comment and one that is on its own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We technically don't need to re-enable it when inline but since we've been using that enable - disable format throughout, I have modified it
update_controller_key(vol) | ||
if vol.is_a?(Hash) | ||
service.volumes.new({ server: self }.merge(vol)) | ||
else | ||
vol.server = self | ||
vol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you reorder the calls to:
update_controller_key(vol) | |
if vol.is_a?(Hash) | |
service.volumes.new({ server: self }.merge(vol)) | |
else | |
vol.server = self | |
vol | |
if vol.is_a?(Hash) | |
vol = service.volumes.new({ server: self }.merge(vol)) | |
end | |
vol.server = self | |
update_controller_key(vol) | |
vol |
You will be able to remove the case
statement in update_controller_key
:
def update_controller_key(vol)
vol.controller_key ||= attributes[:scsi_controllers].first&.key || 1000
end
because vol
will always contain an object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Thanks Shim, updated :)
end | ||
end | ||
class Mock | ||
def get_vm_first_nvme_controller(vm_id); end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mock class returns nil
, while the real method will throw an exception and never return nil
.
I think it would be nice to have the behavior be equal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not doing that for the other(scsi and sata) controllers, I dont think it's necessary to do that as it's the mock class.
@@ -887,7 +886,6 @@ def relocation_volume_backing(volume) | |||
end | |||
end | |||
|
|||
# rubocop:enable Metrics/ClassLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should reenable the class length metric after the class definition.
26303a4
to
c8224bf
Compare
Starting to test again, @ekohl have all your concerns been addressed? If so will merge and get a release out pending all testing works again with the latest pushes. |
@@ -255,7 +263,7 @@ def create_controller(controller = nil, index = 0) | |||
operation: options[:operation], | |||
device: controller_class.new(key: options[:key] || (1000 + index), | |||
busNumber: options[:bus_id] || index, | |||
**(options[:type] == RbVmomi::VIM::VirtualAHCIController ? {} : {sharedBus: controller_get_shared_from_options(options)})) | |||
**device_options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indenting looks off here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's fine I had cross checked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks off if I look at the file, could it be your editor?
**device_options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated with a new indentation. Either way, was accepted by rubocop and the general editor format
9b71bef
to
5298015
Compare
Looks fine to me @ekohl any other thoughts before I do a final round of testing. |
.rubocop.yml
Outdated
@@ -42,3 +42,7 @@ Style/RescueModifier: | |||
|
|||
Metrics/MethodLength: | |||
Max: 45 | |||
|
|||
Style/MutableConstant: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be hesitant to disable this. Why is it needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used the freeze method for the string constants
5298015
to
1ea57bc
Compare
@ekohl any other comments or is this good to go, tested it and it works with the latest commit and subhash did as well. |
@chris1984 I still see open conversations when I look on the changed files tab. I'd expect those to either be fixed or closed with an explanation why it won't be fixed. |
1ea57bc
to
5ba744d
Compare
Thanks @ekohl , updated them. |
Looks fine now, just one concern: I tested this again and I am getting Failed to create a compute VMware8 (VMware) instance ron-doria.satellite.lab.eng.rdu2.redhat.com: InvalidController: The device '1' is referring to a nonexisting controller '1,000'. Since it keeps happening between my env and qe's I wonder if customers will start to hit this too. Is there a way we can get that controller id for all envs since you make a fix and it works on mine, but errors's on qe's vmware then you fix and it works on theirs but not mine. I am worried customer will hit that same error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the solution for the feature request.
NVME (Non Volatile Memory Express) controllers are a class above the currently supported (SCSI Controllers) and has multiple advantages
Currently Foreman and fog-vsphere are designed to only support SCSI controllers and it's types. The goal is to extend it to adding NVME and in future, other types. Do give this a read SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility
Front End PR : theforeman/foreman#10168
Hammer doc PR : theforeman/hammer-cli-foreman#628
More details: https://docs.google.com/document/d/1hs4JekeCQP6brbZGg8hS8kcHohselH1HTEEhThgw-AE/edit#heading=h.lrnpszeygrmo
Ready for testing :D