Basic network create (without any authorization checks).
authorFrantišek Dvořák <valtri@civ.zcu.cz>
Tue, 13 Sep 2016 16:24:24 +0000 (18:24 +0200)
committerFrantišek Dvořák <valtri@civ.zcu.cz>
Tue, 13 Sep 2016 16:24:24 +0000 (18:24 +0200)
README.md
lib/api.rb
lib/config.rb
lib/nebula.rb
spec/models/network_spec.rb
swagger.yaml
templates/network.erb [new file with mode: 0644]
templates/range.erb [new file with mode: 0644]

index c13ef84..316694d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ At NOW host (configuration `/etc/now.yaml`):
       admin_user: 'nowadmin'
       admin_password: 'the-best-strongest-password-ever'
       endpoint: http://nebula.example.com:2633/RPC2
+    bridge: br0
+    device: eth0
 
 Launch NOW:
 
@@ -25,7 +27,12 @@ Launch NOW:
 
 List networks example:
 
-    curl http://now.example.com:9292/network?user=myuser
+ *curl http://now.example.com:9292/network?user=myuser*
+
+Create the network:
+
+ *curl -i -X POST -d '{ "title": "example1", "description": "Example network", "range": { "address": "fc00::0001::/64", "allocation": "dynamic" }, "vlan": 1}' http://now.example.com:9292/network?user=myuser*
+
 
 ## Development
 
index 2f7da49..da91ffa 100644 (file)
@@ -4,7 +4,7 @@ require 'sinatra/cross_origin'
 require ::File.expand_path('../../version', __FILE__)
 
 module Now
-  # HTTP REST API between NOW and rOCCI server
+  # HTTP REST API of NOW (for usage by rOCCI server)
   class Application < Sinatra::Base
     attr_accessor :nebula
     register Sinatra::CrossOrigin
@@ -51,6 +51,30 @@ module Now
       end
     end
 
+    post '/network' do
+      cross_origin
+      request.body.rewind
+      begin
+        netinfo = JSON.parse request.body.read
+        switch_user(params['user'])
+        # Now::Network expects Now::Range object
+        if netinfo.key?('range')
+          netinfo['range'] = Now::Range.from_hash(netinfo['range'])
+        end
+        network = Now::Network.new(netinfo)
+        id = nebula.create_network(network)
+      rescue NowError => e
+        logger.error "[HTTP #{e.code}] #{e.message}"
+        halt e.code, e.message
+      rescue JSON::ParserError => e
+        logger.error "[HTTP 400] #{e.message}"
+        halt 400, e.message
+      end
+
+      body id
+      status 201
+    end
+
     get '/network/:id' do
       cross_origin
       begin
index d34e010..990d6e0 100644 (file)
@@ -31,6 +31,7 @@ module Now
           break
         end
       end
+      config['template_dir'] = ::File.expand_path('../../templates', __FILE__)
       #logger.debug "[config] Configuration: #{config}"
 
       replace config
index a49d3b7..3cbe1a7 100644 (file)
@@ -1,3 +1,4 @@
+require 'erb'
 require 'opennebula'
 require 'yaml'
 require 'ipaddress'
@@ -11,6 +12,7 @@ module Now
     # for testing
     attr_accessor :ctx
     @ctx = nil
+    @user = nil
     @server_ctx = nil
     @user_ctx = nil
 
@@ -28,6 +30,7 @@ module Now
       expiration = Time.now.to_i + EXPIRE_LENGTH
       user_token = server_auth.login_token(expiration, user)
 
+      @user = user
       @user_ctx = one_connect(@url, user_token)
       @ctx = @user_ctx
     end
@@ -38,6 +41,7 @@ module Now
       logger.debug "Authentication to #{admin_user}"
 
       direct_token = "#{admin_user}:#{admin_password}"
+      @user = admin_user
       @server_ctx = one_connect(@url, direct_token)
       @ctx = @server_ctx
     end
@@ -79,6 +83,33 @@ module Now
       return network
     end
 
+    def create_network(netinfo)
+      #logger.debug "[create_network] #{netinfo}"
+      logger.info '[create_network] Network ID ignored (set by OpenNebula)' if netinfo.id
+      logger.info "[create_network] Network owner ignored (will be '#{@user}')" if netinfo.user
+      logger.warn '[create_network] Bridge not configured (bridge)' unless config.key? 'bridge'
+      logger.warn '[create_network] Physical drvice not configured (device)' unless config.key? 'device'
+      range = netinfo.range
+
+      if range && range.address && range.address.ipv6?
+        logger.warn "[create_network] Network prefix 64 for IPv6 network required (#{range.address.to_string})" unless range.address.prefix == 64
+      end
+      vn_generic = OpenNebula::VirtualNetwork.build_xml
+      vn = OpenNebula::VirtualNetwork.new(vn_generic, @ctx)
+      b = binding
+      template = ERB.new(::File.new(::File.join(config['template_dir'], 'network.erb')).read, 0, '%').result b
+      template_ar = ERB.new(::File.new(::File.join(config['template_dir'], 'range.erb')).read, 0, '%').result b
+      template += "\n"
+      template += template_ar
+      logger.debug "[create_network] template: #{template}"
+
+      check(vn.allocate(template))
+      id = vn.id.to_s
+      logger.debug "[create_network] created network: #{id}"
+
+      return id
+    end
+
     private
 
     def error_one2http(errno)
@@ -112,10 +143,10 @@ module Now
     end
 
     def parse_range(vn_id, vn, ar)
-      id = ar['AR_ID'] || '(undef)'
-      type = ar['TYPE']
-      ip = ar['NETWORK_ADDRESS'] || vn['NETWORK_ADDRESS']
-      mask = ar['NETWORK_MASK'] || vn['NETWORK_MASK']
+      id = ar && ar['AR_ID'] || '(undef)'
+      type = ar && ar['TYPE']
+      ip = ar && ar['NETWORK_ADDRESS'] || vn['NETWORK_ADDRESS']
+      mask = ar && ar['NETWORK_MASK'] || vn['NETWORK_MASK']
 
       case type
       when 'IP4'
index bb29130..a5db2f4 100644 (file)
@@ -2,9 +2,6 @@ require 'spec_helper'
 
 describe Now::Network do
   context '#type check' do
-    it 'no id raises NowError' do
-      expect { Now::Network.new }.to raise_error(Now::NowError)
-    end
     it 'string range raises NowError' do
       expect { Now::Network.new(id: 0, range: 'eee') }.to raise_error(Now::NowError)
     end
index 47735a4..659a4a8 100644 (file)
@@ -38,6 +38,27 @@ paths:
           description: "KO"
           schema:
             type: "string"
+    post:
+      summary: "Create a new network"
+      parameters:
+        - in: "query"
+          name: "user"
+          description: "OpenNebula user identity"
+          required: false
+          type: "string"
+        - in: "body"
+          name: "network"
+          description: "Network details"
+          required: true
+          schema:
+            $ref: "#/definitions/Network"
+      responses:
+        201:
+          description: "Network created"
+        default:
+          description: "Error creating network"
+          schema:
+            type: "string"
   /network/{id}:
     get:
       summary: "Information about network"
@@ -78,8 +99,6 @@ definitions:
   Network:
     description: "Network object"
     type: "object"
-    required:
-    - "id"
     properties:
       id:
         description: "OpenNebula ID"
@@ -99,7 +118,6 @@ definitions:
         type: "integer"
         format: "int64"
       range:
-        description: "IP address range"
         $ref: "#/definitions/Range"
       zone:
         description: "Availability zone (cluster)"
diff --git a/templates/network.erb b/templates/network.erb
new file mode 100644 (file)
index 0000000..02af9ce
--- /dev/null
@@ -0,0 +1,24 @@
+% if netinfo.title
+NAME = "<%= netinfo.title %>"
+% end
+
+% if netinfo.description
+DESCRIPTION = "<%= netinfo.description %>"
+% end
+
+% if netinfo.zone
+CLUSTERS = <%= netinfo.zone %>
+% end
+
+% if config.key? 'bridge'
+BRIDGE = <%= config['bridge'] %>
+% end
+
+% if config.key? 'device'
+PHYDEV = <%= config['device'] %>
+% end
+
+% if netinfo.vlan
+VLAN_ID = <%= netinfo.vlan %>
+VN_MAD = vxlan
+% end
diff --git a/templates/range.erb b/templates/range.erb
new file mode 100644 (file)
index 0000000..0ae91ef
--- /dev/null
@@ -0,0 +1,30 @@
+% range = netinfo.range
+%if range
+%
+%  if range.address.ipv4?
+NETWORK_ADDRESS = <%= range.address.network.to_s %>
+NETWORK_MASK = <%= range.address.netmask %>
+
+AR = [
+    TYPE = IP4,
+    IP = <%= range.address.first.to_s %>,
+    SIZE = <%= range.address.size - 2 %>
+]
+%  end
+%
+%  if range.address.ipv6?
+NETWORK_ADDRESS = <%= range.address.network.to_s %>
+NETWORK_MASK = <%= range.address.prefix %>
+
+AR = [
+    TYPE = IP6,
+%  if IPAddress('fc00::/7').include? range.address
+    ULA_PREFIX = <%= range.address.network.to_s %>,
+%  else
+    GLOBAL_PREFIX = <%= range.address.network.to_s %>,
+%  end
+%   # limit address range (required for OpenNebula)
+    SIZE = <%= range.address.size >= 2**31 ? 2**31 : range.address.size - 2 %>
+]
+%  end
+%end