More detailed information about networks.
authorFrantišek Dvořák <valtri@civ.zcu.cz>
Fri, 17 Jun 2016 15:32:04 +0000 (17:32 +0200)
committerFrantišek Dvořák <valtri@civ.zcu.cz>
Fri, 17 Jun 2016 15:33:08 +0000 (17:33 +0200)
.rubocop.yml
README.md
lib/api.rb
lib/nebula.rb
models/network.rb
models/range.rb [new file with mode: 0644]
swagger.yaml

index af1f2e2..f297369 100644 (file)
@@ -4,6 +4,7 @@ AllCops:
   Exclude:
    # exclude generated code (maybe we'll rewrite it anyway one day)
    - models/network.rb
+   - models/range.rb
 
 # Assignment Branch Condition size for initialize is too high
 # Perceived complexity for deep_merge is too high
@@ -71,6 +72,11 @@ Style/NegatedIf:
 Style/RedundantReturn:
   Enabled: false
 
+# Avoid comma after the last parameter of a method call.
+# (do want)
+Style/TrailingCommaInArguments:
+  Enabled: false
+
 # Avoid comma after the last item of an array
 # (do want)
 Style/TrailingCommaInLiteral:
index 9365503..c13ef84 100644 (file)
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Launch NOW:
 
 List networks example:
 
-    curl http://now.example.com:9292/list?user=myuser
+    curl http://now.example.com:9292/network?user=myuser
 
 ## Development
 
index 05735a4..f3c4b2d 100644 (file)
@@ -39,7 +39,7 @@ module Now
       API_VERSION
     end
 
-    get '/list' do
+    get '/network' do
       cross_origin
       begin
         switch_user(params['user'])
index af4ea3c..a5c6ca0 100644 (file)
@@ -56,10 +56,12 @@ module Now
 
       networks = []
       vn_pool.each do |vn|
-        id = vn.id
-        title = vn.name
-        network = Network.new(id: id, title: title)
-        networks << network.to_hash
+        begin
+          network = parse_network(vn)
+          networks << network.to_hash
+        rescue NowError => e
+          logger.warn "[code #{e.code}] #{e.message}, skipping"
+        end
       end
 
       return networks
@@ -70,10 +72,7 @@ module Now
       vn = OpenNebula::VirtualNetwork.new(vn_generic, @ctx)
       check(vn.info)
 
-      id = vn.id
-      title = vn.name
-      logger.debug "OpenNebula get(#{network_id}) ==> #{id}, #{title}"
-      network = Network.new(id: id, title: title)
+      network = parse_network(vn)
 
       return network.to_hash
     end
@@ -112,5 +111,89 @@ module Now
       raise NowError.new(code), return_code.message
     end
 
+    def parse_range(vn_id, ar)
+      id = ar['AR_ID']
+      type = ar['TYPE']
+      size = ar['SIZE']
+      case type
+      when 'IP4'
+        ip = ar['IP']
+        addr_size = 32
+        if ip.nil? || ip.empty?
+          raise NowError.new(422), "Missing 'IP' in the address range #{id} of network #{vn_id}"
+        end
+      when 'IP6', 'IP4_6'
+        ip = ar['GLOBAL_PREFIX'] || ar['ULA_PREFIX']
+        addr_size = 128
+        if ip.nil? || ip.empty?
+          raise NowError.new(422), "Missing 'GLOBAL_PREFIX' in the address range #{id} of network #{vn_id}"
+        end
+      else
+        raise NowError.new(501), "Unknown type '#{type}' in the address range #{id} of network #{vn_id}"
+      end
+      if size.nil? || size.empty?
+        raise NowError.new(422), "Missing 'SIZE' in the address range #{id} of network #{vn_id}"
+      end
+      size = size.to_i
+      mask = addr_size - Math.log(size, 2).ceil
+      logger.debug "[parse_range] id=#{id}, address=#{ip}/#{mask} (size #{size})"
+
+      return Now::Range.new(address: "#{ip}/#{mask}", allocation: 'dynamic')
+    end
+
+    def parse_ranges(vn_id, vn)
+      range = nil
+      vn.each('AR_POOL/AR') do |ar|
+        if !range.nil?
+          raise NowError.new(501), "Multiple address ranges found in network #{vn_id}"
+        end
+        range = parse_range(vn_id, ar)
+      end
+      return range
+    end
+
+    def parse_cluster(vn_id, vn)
+      cluster = nil
+      vn.each('CLUSTERS/ID') do |cluster_xml|
+        id = cluster_xml.text
+        logger.debug "[parse_cluster] cluster: #{id}"
+        if !cluster.nil?
+          raise NowError.new(501), "Multiple clusters assigned to network #{vn_id}"
+        end
+        cluster = id
+      end
+      return cluster
+    end
+
+    def parse_network(vn)
+      logger.debug "[parse_network] #{vn.to_hash}"
+
+      id = vn.id
+      title = vn.name
+      desc = vn['SUMMARY']
+      if desc.nil? || desc.empty?
+        desc = nil
+      end
+      vlan = vn['VLAN_ID']
+      if vlan.nil? || vlan.empty?
+        vlan = nil
+      end
+
+      range = parse_ranges(id, vn)
+      zone = parse_cluster(id, vn)
+      network = Network.new(
+        id: id,
+        title: title,
+        description: desc,
+        user: vn['UNAME'],
+        bridge: vn['BRIDGE'],
+        vlan: vlan,
+        range: range,
+        zone: zone,
+      )
+
+      return network
+    end
+
   end
 end
index 6d7e166..ba966db 100644 (file)
@@ -22,21 +22,43 @@ limitations under the License.
 require 'date'
 
 module Now
-
+  # Network object
   class Network
+    # OpenNebula ID
     attr_accessor :id
 
+    # Network title
     attr_accessor :title
 
+    # Network summary
+    attr_accessor :description
+
+    # Owner
     attr_accessor :user
 
+    # VLAN ID
+    attr_accessor :vlan
+
+    attr_accessor :range
+
+    # Network state (active, inactive, error)
+    attr_accessor :state
+
+    # Availability zone (cluster)
+    attr_accessor :zone
+
 
     # Attribute mapping from ruby-style variable name to JSON key.
     def self.attribute_map
       {
         :'id' => :'id',
         :'title' => :'title',
-        :'user' => :'user'
+        :'description' => :'description',
+        :'user' => :'user',
+        :'vlan' => :'vlan',
+        :'range' => :'range',
+        :'state' => :'state',
+        :'zone' => :'zone'
       }
     end
 
@@ -45,7 +67,12 @@ module Now
       {
         :'id' => :'Integer',
         :'title' => :'String',
-        :'user' => :'String'
+        :'description' => :'String',
+        :'user' => :'String',
+        :'vlan' => :'Integer',
+        :'range' => :'Range',
+        :'state' => :'String',
+        :'zone' => :'String'
       }
     end
 
@@ -65,10 +92,30 @@ module Now
         self.title = attributes[:'title']
       end
 
+      if attributes.has_key?(:'description')
+        self.description = attributes[:'description']
+      end
+
       if attributes.has_key?(:'user')
         self.user = attributes[:'user']
       end
 
+      if attributes.has_key?(:'vlan')
+        self.vlan = attributes[:'vlan']
+      end
+
+      if attributes.has_key?(:'range')
+        self.range = attributes[:'range']
+      end
+
+      if attributes.has_key?(:'state')
+        self.state = attributes[:'state']
+      end
+
+      if attributes.has_key?(:'zone')
+        self.zone = attributes[:'zone']
+      end
+
     end
 
     # Show invalid properties with the reasons. Usually used together with valid?
@@ -92,7 +139,12 @@ module Now
       self.class == o.class &&
           id == o.id &&
           title == o.title &&
-          user == o.user
+          description == o.description &&
+          user == o.user &&
+          vlan == o.vlan &&
+          range == o.range &&
+          state == o.state &&
+          zone == o.zone
     end
 
     # @see the `==` method
@@ -104,7 +156,7 @@ module Now
     # Calculates hash code according to all attributes.
     # @return [Fixnum] Hash code
     def hash
-      [id, title, user].hash
+      [id, title, description, user, vlan, range, state, zone].hash
     end
 
     # Builds the object from hash
diff --git a/models/range.rb b/models/range.rb
new file mode 100644 (file)
index 0000000..0b4f556
--- /dev/null
@@ -0,0 +1,208 @@
+=begin
+Network Orchestrator API
+
+OpenAPI spec version: 0.0.0
+
+Partially generated by: https://github.com/swagger-api/swagger-codegen.git
+
+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.
+
+=end
+
+require 'date'
+
+module Now
+  # Address range
+  class Range
+    # Address range (CIDR notation)
+    attr_accessor :address
+
+    # Address allocation type (static, dynamic)
+    attr_accessor :allocation
+
+
+    # Attribute mapping from ruby-style variable name to JSON key.
+    def self.attribute_map
+      {
+        :'address' => :'address',
+        :'allocation' => :'allocation'
+      }
+    end
+
+    # Attribute type mapping.
+    def self.swagger_types
+      {
+        :'address' => :'String',
+        :'allocation' => :'String'
+      }
+    end
+
+    # Initializes the object
+    # @param [Hash] attributes Model attributes in the form of hash
+    def initialize(attributes = {})
+      return unless attributes.is_a?(Hash)
+
+      # convert string to symbol for hash key
+      attributes = attributes.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
+
+      if attributes.has_key?(:'address')
+        self.address = attributes[:'address']
+      end
+
+      if attributes.has_key?(:'allocation')
+        self.allocation = attributes[:'allocation']
+      end
+
+    end
+
+    # Show invalid properties with the reasons. Usually used together with valid?
+    # @return Array for valid properies with the reasons
+    def list_invalid_properties
+      invalid_properties = Array.new
+      return invalid_properties
+    end
+
+    # Check to see if the all the properties in the model are valid
+    # @return true if the model is valid
+    def valid?
+      return true
+    end
+
+    # Checks equality by comparing each attribute.
+    # @param [Object] Object to be compared
+    def ==(o)
+      return true if self.equal?(o)
+      self.class == o.class &&
+          address == o.address &&
+          allocation == o.allocation
+    end
+
+    # @see the `==` method
+    # @param [Object] Object to be compared
+    def eql?(o)
+      self == o
+    end
+
+    # Calculates hash code according to all attributes.
+    # @return [Fixnum] Hash code
+    def hash
+      [address, allocation].hash
+    end
+
+    # Builds the object from hash
+    # @param [Hash] attributes Model attributes in the form of hash
+    # @return [Object] Returns the model itself
+    def build_from_hash(attributes)
+      return nil unless attributes.is_a?(Hash)
+      self.class.swagger_types.each_pair do |key, type|
+        if type =~ /^Array<(.*)>/i
+          # check to ensure the input is an array given that the the attribute
+          # is documented as an array but the input is not
+          if attributes[self.class.attribute_map[key]].is_a?(Array)
+            self.send("#{key}=", attributes[self.class.attribute_map[key]].map{ |v| _deserialize($1, v) } )
+          end
+        elsif !attributes[self.class.attribute_map[key]].nil?
+          self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]]))
+        end # or else data not found in attributes(hash), not an issue as the data can be optional
+      end
+
+      self
+    end
+
+    # Deserializes the data based on type
+    # @param string type Data type
+    # @param string value Value to be deserialized
+    # @return [Object] Deserialized data
+    def _deserialize(type, value)
+      case type.to_sym
+      when :DateTime
+        DateTime.parse(value)
+      when :Date
+        Date.parse(value)
+      when :String
+        value.to_s
+      when :Integer
+        value.to_i
+      when :Float
+        value.to_f
+      when :BOOLEAN
+        if value.to_s =~ /^(true|t|yes|y|1)$/i
+          true
+        else
+          false
+        end
+      when :Object
+        # generic object (usually a Hash), return directly
+        value
+      when /\AArray<(?<inner_type>.+)>\z/
+        inner_type = Regexp.last_match[:inner_type]
+        value.map { |v| _deserialize(inner_type, v) }
+      when /\AHash<(?<k_type>.+), (?<v_type>.+)>\z/
+        k_type = Regexp.last_match[:k_type]
+        v_type = Regexp.last_match[:v_type]
+        {}.tap do |hash|
+          value.each do |k, v|
+            hash[_deserialize(k_type, k)] = _deserialize(v_type, v)
+          end
+        end
+      else # model
+        temp_model = Now.const_get(type).new
+        temp_model.build_from_hash(value)
+      end
+    end
+
+    # Returns the string representation of the object
+    # @return [String] String presentation of the object
+    def to_s
+      to_hash.to_s
+    end
+
+    # to_body is an alias to to_hash (backward compatibility)
+    # @return [Hash] Returns the object in the form of hash
+    def to_body
+      to_hash
+    end
+
+    # Returns the object in the form of hash
+    # @return [Hash] Returns the object in the form of hash
+    def to_hash
+      hash = {}
+      self.class.attribute_map.each_pair do |attr, param|
+        value = self.send(attr)
+        next if value.nil?
+        hash[param] = _to_hash(value)
+      end
+      hash
+    end
+
+    # Outputs non-array value in the form of hash
+    # For object, use to_hash. Otherwise, just return the value
+    # @param [Object] value Any valid value
+    # @return [Hash] Returns the value in the form of hash
+    def _to_hash(value)
+      if value.is_a?(Array)
+        value.compact.map{ |v| _to_hash(v) }
+      elsif value.is_a?(Hash)
+        {}.tap do |hash|
+          value.each { |k, v| hash[k] = _to_hash(v) }
+        end
+      elsif value.respond_to? :to_hash
+        value.to_hash
+      else
+        value
+      end
+    end
+
+  end
+
+end
index f76e1f4..aeebeaf 100644 (file)
@@ -6,7 +6,8 @@ info:
 paths:
   /:
     get:
-      description: "Simple check"
+      summary: "Simple service check"
+      description: "Simple service check. API version is returned."
       parameters: []
       responses:
         200:
@@ -17,8 +18,9 @@ paths:
           description: "KO"
           schema:
             type: "string"
-  /list:
+  /network:
     get:
+      summary: "Networks list"
       parameters:
         - in: "query"
           name: "user"
@@ -38,6 +40,7 @@ paths:
             type: "string"
   /network/{id}:
     get:
+      summary: "Information about network"
       parameters:
         - in: "path"
           name: "id"
@@ -60,15 +63,41 @@ paths:
           schema:
             type: "string"
 definitions:
+  Range:
+    description: "Address range"
+    type: "object"
+    properties:
+      address:
+        type: "string"
+        description: "Address range (CIDR notation)"
+      allocation:
+        description: "Address allocation type (static, dynamic)"
+        type: "string"
   Network:
+    description: "Network object"
     type: "object"
     required:
     - "id"
     properties:
       id:
+        description: "OpenNebula ID"
         type: "integer"
         format: "int64"
       title:
+        description: "Network title"
+        type: "string"
+      description:
+        description: "Network summary"
         type: "string"
       user:
+        description: "Owner"
+        type: "string"
+      vlan:
+        description: "VLAN ID"
+        type: "integer"
+        format: "int64"
+      range:
+        $ref: "#/definitions/Range"
+      zone:
+        description: "Availability zone (cluster)"
         type: "string"