From fba4deec6e1c67a7025b4a2f0ab1fc8943868152 Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Mon, 22 Dec 2014 14:12:16 +0100 Subject: [PATCH] feature #3175: Moved Nic and VM to new modules --- src/vnm_mad/remotes/lib/address.rb | 3 +- src/vnm_mad/remotes/lib/command.rb | 3 +- src/vnm_mad/remotes/lib/nic.rb | 154 +++++++++++++++ src/vnm_mad/remotes/lib/opennebula_network.rb | 179 ++++++++++++++++++ src/vnm_mad/remotes/lib/security_groups.rb | 6 +- .../remotes/lib/security_groups_iptables.rb | 14 +- src/vnm_mad/remotes/lib/vm.rb | 110 +++++++++++ 7 files changed, 455 insertions(+), 14 deletions(-) create mode 100644 src/vnm_mad/remotes/lib/nic.rb create mode 100644 src/vnm_mad/remotes/lib/opennebula_network.rb create mode 100644 src/vnm_mad/remotes/lib/vm.rb diff --git a/src/vnm_mad/remotes/lib/address.rb b/src/vnm_mad/remotes/lib/address.rb index dcb8f714f8..f32838b7d4 100644 --- a/src/vnm_mad/remotes/lib/address.rb +++ b/src/vnm_mad/remotes/lib/address.rb @@ -16,8 +16,7 @@ module VNMMAD -# The address module provides basic functions to manage IP addresses -module Address +module VNMNetwork # This methods translates an address range to a set of IPv4 networks # in CIDR notation diff --git a/src/vnm_mad/remotes/lib/command.rb b/src/vnm_mad/remotes/lib/command.rb index 273c9354eb..3242283d29 100644 --- a/src/vnm_mad/remotes/lib/command.rb +++ b/src/vnm_mad/remotes/lib/command.rb @@ -16,8 +16,7 @@ module VNMMAD -# This module includes functions to execute and manage Network commands. -module Command +module VNMNetwork # Command configuration for common network commands. This CAN be adjust # to local installations. Any modification requires to sync the hosts with diff --git a/src/vnm_mad/remotes/lib/nic.rb b/src/vnm_mad/remotes/lib/nic.rb new file mode 100644 index 0000000000..9c85841e7d --- /dev/null +++ b/src/vnm_mad/remotes/lib/nic.rb @@ -0,0 +1,154 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs # +# # +# 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. # +#--------------------------------------------------------------------------- # + +module VNMMAD + +module VNMNetwork + + # This class represents the NICS of a VM, it provides a factory method + # to create VMs of the given hyprtvisor + class Nics < Array + def initialize(hypervisor) + case hypervisor + when "kvm" + @nicClass = NicKVM + when "xen" + @nicClass = NicXen + when "vmware" + @nicClass = NicVMware + end + end + + def new_nic + @nicClass.new + end + end + + ############################################################################ + # Hypervisor specific implementation of network interfaces. Each class + # implements the following interface: + # - get_info to populste the VM.vm_info Hash + # - get_tap to set the [:tap] attribute with the associated NIC + ############################################################################ + + # A NIC using KVM. This class implements functions to get the physical + # interface that the NIC is using, based on the MAC address + class NicKVM < Hash + def initialize + super(nil) + end + + # Get the VM information with virsh dumpxml + def get_info(vm) + if vm.deploy_id + deploy_id = vm.deploy_id + else + deploy_id = vm['DEPLOY_ID'] + end + + if deploy_id and vm.vm_info[:dumpxml].nil? + vm.vm_info[:dumpxml] = `#{VNMNetwork::COMMANDS[:virsh]} dumpxml #{deploy_id} 2>/dev/null` + + vm.vm_info.each_key do |k| + vm.vm_info[k] = nil if vm.vm_info[k].to_s.strip.empty? + end + end + end + + # Look for the tap in + # devices/interface[@type='bridge']/mac[@address='']/../target" + def get_tap(vm) + dumpxml = vm.vm_info[:dumpxml] + + if dumpxml + dumpxml_root = REXML::Document.new(dumpxml).root + + xpath = "devices/interface[@type='bridge']/" \ + "mac[@address='#{self[:mac]}']/../target" + + tap = dumpxml_root.elements[xpath] + + self[:tap] = tap.attributes['dev'] if tap + end + + self + end + end + + + # A NIC using Xen. This class implements functions to get the physical interface + # that the NIC is using + class NicXen < Hash + def initialize + super(nil) + end + + def get_info(vm) + if vm.deploy_id + deploy_id = vm.deploy_id + else + deploy_id = vm['DEPLOY_ID'] + end + + if deploy_id and (vm.vm_info[:domid].nil? or vm.vm_info[:networks].nil?) + vm.vm_info[:domid] =`#{VNMNetwork::COMMANDS[:xm]} domid #{deploy_id}`.strip + vm.vm_info[:networks] =`#{VNMNetwork::COMMANDS[:xm]} network-list #{deploy_id}` + + vm.vm_info.each_key do |k| + vm.vm_info[k] = nil if vm.vm_info[k].to_s.strip.empty? + end + end + end + + def get_tap(vm) + domid = vm.vm_info[:domid] + + if domid + networks = vm.vm_info[:networks].split("\n")[1..-1] + networks.each do |net| + n = net.split + + iface_id = n[0] + iface_mac = n[2] + + if iface_mac == self[:mac] + self[:tap] = "vif#{domid}.#{iface_id}" + break + end + end + end + self + end + end + + # A NIC using VMware. This class implements functions to get the physical interface + # that the NIC is using + class NicVMware < Hash + def initialize + super(nil) + end + + def get_info(vm) + end + + def get_tap(vm) + self + end + end + +end + +end \ No newline at end of file diff --git a/src/vnm_mad/remotes/lib/opennebula_network.rb b/src/vnm_mad/remotes/lib/opennebula_network.rb new file mode 100644 index 0000000000..350d9adfe8 --- /dev/null +++ b/src/vnm_mad/remotes/lib/opennebula_network.rb @@ -0,0 +1,179 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs # +# # +# 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. # +#--------------------------------------------------------------------------- # + +$: << File.dirname(__FILE__) +$: << File.join(File.dirname(__FILE__), '..') + +require 'rexml/document' +require 'base64' +require 'yaml' + +require 'one_firewall' +require 'one_sg' +require 'lib/vm' +require 'lib/nic' +require 'lib/address' +require 'lib/security_groups' +require 'lib/security_groups_iptables' + +require 'scripts_common' + +include OpenNebula + +begin + CONF = YAML.load_file( + File.join(File.dirname(__FILE__), "OpenNebulaNetwork.conf") + ) +rescue + CONF = { + :start_vlan => 2 + } +end + +# Set PATH +ENV['PATH'] = "#{ENV['PATH']}:/bin:/sbin:/usr/bin" + + +################################################################################ +# The VNMMAD module provides the basic abstraction to implement custom +# virtual network drivers. The VNMAD module includes: +# - VNMNetwork with base classes and main functionality +# - SGIPTables a module with a SG implementation based in iptables/ipset +################################################################################ +module VNMMAD + + ############################################################################ + # Base driver class to implement a Network driver. It relays on two filter + # drivers OpenNebulaFirewall and OpenNebulaSG. + ############################################################################ + class OpenNebulaNetwork + attr_reader :hypervisor, :vm + + # Creates new OpenNebulaNetwork using: + # @param vm_tpl [String] XML String from oned + # @param xpath_filter [String] to get relevant NICs for the driver + # @param deploy_id [String] + # @param hypervisor [String] + def initialize(vm_tpl, xpath_filter, deploy_id = nil, hypervisor = nil) + @locking = false + + if !hypervisor + @hypervisor = detect_hypervisor + else + @hypervisor = hypervisor + end + + @vm = VM.new(REXML::Document.new(vm_tpl).root, xpath_filter, + deploy_id, @hypervisor) + end + + # Creates a new OpenNebulaNetwork using: + # @param vm_64 [String] Base64 encoded XML String from oned + # @param deploy_id [String] + # @param hypervisor [String] + def self.from_base64(vm_64, deploy_id = nil, hypervisor = nil) + vm_xml = Base64::decode64(vm_64) + self.new(vm_xml, deploy_id, hypervisor) + end + + # Locking function to serialized driver operations if needed. Similar + # to flock. File is created as /tmp/onevnm--lock + def lock + if @locking + driver_name = self.class.name.downcase + @locking_file = File.open("/tmp/onevnm-#{driver_name}-lock","w") + @locking_file.flock(File::LOCK_EX) + end + end + + # Unlock driver execution mutex + def unlock + if @locking + @locking_file.close + end + end + + # Executes the given block on each NIC + def process(&block) + @vm.each_nic(block) + end + + # Return a string for the hypervisor + # @return [String] "kvm", "xen" or nil + def detect_hypervisor + lsmod = `#{VNMNetwork::COMMANDS[:lsmod]}` + xen_file = "/proc/xen/capabilities" + + if File.exists?(xen_file) + "xen" + elsif lsmod.match(/kvm/) + "kvm" + else + nil + end + end + + # Get hypervisor bridges + # @return [Hash] with the bridge names + def get_interfaces + bridges = Hash.new + brctl_exit =`#{VNMNetwork::COMMANDS[:brctl]} show` + + cur_bridge = "" + + brctl_exit.split("\n")[1..-1].each do |l| + l = l.split + + if l.length > 1 + cur_bridge = l[0] + + bridges[cur_bridge] = Array.new + bridges[cur_bridge] << l[3] if l[3] + else + bridges[cur_bridge] << l[0] + end + end + + bridges + end + + # Returns true if the template contains the deprecated firewall attributes: + # - ICMP + # - WHITE_PORTS_TCP + # - WHITE_PORTS_UDP + # - BLACK_PORTS_TCP + # - BLACK_PORTS_UDP + # + # @return Boolean + def self.has_fw_attrs?(vm_xml) + vm_root = REXML::Document.new(vm_xml).root + !vm_root.elements[OpenNebulaFirewall::XPATH_FILTER].nil? + end + + # Returns a filter object based on the contents of the template + # + # @return OpenNebulaFirewall or OpenNebulaSG object + def self.filter_driver(vm_64, deploy_id = nil, hypervisor = nil) + vm_xml = Base64::decode64(vm_64) + + if self.has_fw_attrs?(vm_xml) + OpenNebulaFirewall.new(vm_xml, deploy_id, hypervisor) + else + OpenNebulaSG.new(vm_xml, deploy_id, hypervisor) + end + end + end +end diff --git a/src/vnm_mad/remotes/lib/security_groups.rb b/src/vnm_mad/remotes/lib/security_groups.rb index ca138ff0d6..34a82f0cd5 100644 --- a/src/vnm_mad/remotes/lib/security_groups.rb +++ b/src/vnm_mad/remotes/lib/security_groups.rb @@ -17,7 +17,7 @@ module VNMMAD # This module includes provides the abstractions to implement SecurityGroups -module SGBase +module VNMNetwork ############################################################################ # Rule supports these (final and relevant) attributes: @@ -101,7 +101,7 @@ module SGBase def net return [] if @ip.nil? || @size.nil? - Address::to_nets(@ip, @size) + VNMNetwork::to_nets(@ip, @size) end # Expand the ICMP type with associated codes if any @@ -190,7 +190,7 @@ module SGBase @rules = [] @vars = {} - @commands = Commands.new + @commands = VNMNetwork::Commands.new rules.each do |rule| @rules << new_rule(rule) diff --git a/src/vnm_mad/remotes/lib/security_groups_iptables.rb b/src/vnm_mad/remotes/lib/security_groups_iptables.rb index 4c57ad96d1..86ec08d0b2 100644 --- a/src/vnm_mad/remotes/lib/security_groups_iptables.rb +++ b/src/vnm_mad/remotes/lib/security_groups_iptables.rb @@ -22,7 +22,7 @@ module SGIPTables ############################################################################ # A Rule implemented with the iptables/ipset Linux kernel facilities ############################################################################ - class RuleIPTables < Rule + class RuleIPTables < VNMNetwork::Rule ######################################################################## # Implementation of each rule type ######################################################################## @@ -136,7 +136,7 @@ module SGIPTables # This class represents a SecurityGroup implemented with iptables/ipset # Kernel facilities. ############################################################################ - class SecurityGroupIPTables < SecurityGroup + class SecurityGroupIPTables < VNMNetwork::SecurityGroup def initialize(vm, nic, sg_id, rules) super @@ -157,7 +157,7 @@ module SGIPTables # - :iptables_s # - :ipset_list def self.info - commands = Commands.new + commands = VNMNetwork::Commands.new commands << :iptables "-S" iptables_s = commands.run! @@ -188,7 +188,7 @@ module SGIPTables return if info[:iptables_s].split("\n").include? "-N #{GLOBAL_CHAIN}" - commands = Commands.new + commands = VNMNetwork::Commands.new commands.iptables "-N #{GLOBAL_CHAIN}" commands.iptables "-A FORWARD -m physdev --physdev-is-bridged -j #{GLOBAL_CHAIN}" @@ -244,7 +244,7 @@ module SGIPTables # IP spoofing # iptables -A one-3-0-o ! --source 10.0.0.1 -j DROP def self.nic_pre(vm, nic) - commands = Commands.new + commands = VNMNetwork::Commands.new vars = SGIPTables.vars(vm, nic) @@ -285,7 +285,7 @@ module SGIPTables chain_in = vars[:chain_in] chain_out = vars[:chain_out] - commands = Commands.new + commands = VNMNetwork::Commands.new commands << :iptables "-A #{chain_in} -j DROP" commands << :iptables "-A #{chain_out} -j DROP" @@ -304,7 +304,7 @@ module SGIPTables iptables_s = info[:iptables_s] ipset_list = info[:ipset_list] - commands = Commands.new + commands = VNMNetwork::Commands.new iptables_forwards.lines.reverse_each do |line| fields = line.split diff --git a/src/vnm_mad/remotes/lib/vm.rb b/src/vnm_mad/remotes/lib/vm.rb new file mode 100644 index 0000000000..8dfa9f5f18 --- /dev/null +++ b/src/vnm_mad/remotes/lib/vm.rb @@ -0,0 +1,110 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs # +# # +# 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. # +#--------------------------------------------------------------------------- # + +module VNMMAD + +module VNMNetwork + + ############################################################################ + # This class represents the VM abstraction. It provides basic methods + # to interact with its network interfaces. + ############################################################################ + class VM + attr_accessor :nics, :vm_info, :deploy_id, :vm_root + + + # Creates a new VM object, and bootstrap the NICs array + # @param vm_root [REXML] XML document representing the VM + # @param xpath_filer [String] to get the VM NICs + # @param deploy_id [String] refers to the VM in the hypervisor + # @param hypervisor [String] + def initialize(vm_root, xpath_filter, deploy_id, hypervisor) + @vm_root = vm_root + @xpath_filter = xpath_filter + @deploy_id = deploy_id + @hypervisor = hypervisor + @vm_info = Hash.new + + @deploy_id = nil if deploy_id == "-" + + nics = VNMNetwork::Nics.new(@hypervisor) + + @vm_root.elements.each(@xpath_filter) do |nic_element| + nic = nics.new_nic + + nic_build_hash(nic_element,nic) + + nic.get_info(self) + nic.get_tap(self) + + nics << nic + end + + @nics = nics + end + + # Iterator on each NIC of the VM + def each_nic(block) + if @nics != nil + @nics.each do |the_nic| + block.call(the_nic) + end + end + end + + # Access an XML Element of the VM + # @param element [String] element name + # @return [String] valule of the element or nil if not found + def [](element) + if @vm_root + val = @vm_root.elements[element] + return val.text if !val.nil? && val.text + end + + nil + end + + private: + + # Method to build the associated Hash from a NIC + # @param nic_element [REXML] for the NIC + # @param nic [Nic] class representation + def nic_build_hash(nic_element,nic) + nic_element.elements.each('*') do |nic_attribute| + key = nic_attribute.name.downcase.to_sym + + if nic_attribute.has_elements? + data = {} + nic_build_hash(nic_attribute,data) + else + data = nic_attribute.text + end + + if nic[key] + if nic[key].instance_of?(Array) + nic[key] << data + else + nic[key] = [nic[key], data] + end + else + nic[key] = data + end + end + end + end +end + +end \ No newline at end of file