Class: Dcmgr::NodeModules::ServiceOpenFlow

Inherits:
Isono::NodeModules::Base
  • Object
show all
Includes:
Helpers::NicHelper, Logger
Defined in:
lib/dcmgr/node_modules/service_openflow.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helpers::NicHelper

#clean_mac, #find_nic, #is_natted?, #nic_state, #valid_nic?

Methods included from Logger

create, default_logdev, included

Instance Attribute Details

#networksObject (readonly)

Returns the value of attribute networks.



16
17
18
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 16

def networks
  @networks
end

Instance Method Details

#add_eth(port) ⇒ Object



111
112
113
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 111

def add_eth port
  worker_thread.pass { add_eth_now port }
end

#add_eth_now(port) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 115

def add_eth_now port
  logger.info "adding eth #{port.port_info.name}."

  return if port.object_id != openflow_controller.ports[port.port_info.number].object_id
  return if not port.is_active

  logger.info "port: #{port.port_info.number}"
  logger.info "mac: #{port.port_info.hw_addr.to_s}"
  logger.info "config: #{port.port_info.config}"
  logger.info "state: #{port.port_info.state}"
  logger.info "curr: #{port.port_info.curr}"
  logger.info "advertised: #{port.port_info.advertised}"
  logger.info "supported: #{port.port_info.supported}"
  logger.info "peer: #{port.port_info.peer}"

  port_number = port.port_info.number

  networks_map = get_physical_networks
  raise "A single (and only a single) physical network must be registered. (With ipv4_gw set)" unless networks_map.one?

  network = get_network_from_map networks_map[0]

  port.lock.synchronize {
    return if not port.is_active

    network.add_port port_number, true
    openflow_controller.install_eth port
    openflow_controller.update_network network

    port.network = network
    openflow_controller.ovs_ofctl.add_flows_from_list port.queued_flows
    port.queued_flows.clear
  }
end

#add_instance(port) ⇒ Object



150
151
152
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 150

def add_instance port
  worker_thread.pass { add_instance_now port }
end

#add_instance_now(port) ⇒ Object

Raises:

  • (ArgumentError)


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 154

def add_instance_now port
  logger.info "adding instance #{port.port_info.name}."

  return if port.object_id != openflow_controller.ports[port.port_info.number].object_id
  return if port.has_instance or not port.is_active

  inst_map = rpc.request('hva-collector', 'get_instance_of_nic', port.port_info.name)
  raise ArgumentError, "Unknown Nic: #{port.port_info.name}" if inst_map.nil?
  vif_map = inst_map[:vif].detect { |vif| vif[:vif_id] == port.port_info.name }
  raise ArgumentError, "Unknown Nic: #{port.port_info.name}" if vif_map.nil?
  ip_map = vif_map[:ipv4]
  raise ArgumentError, "Unknown Nic: #{port.port_info.name}" if ip_map.nil?

  # logger.info "inst_map: #{inst_map.inspect}"
  logger.debug "vif_map: #{vif_map.inspect}"

  ip_lease = ip_map[:address]
  mac_lease = clean_mac(vif_map[:mac_addr])

  port.ip = ip_lease
  port.mac = mac_lease
  port.has_instance = true

  logger.info "port: #{port.port_info.number}"
  logger.info "mac: #{port.port_info.hw_addr.to_s} <=> #{mac_lease}"
  logger.info "ip: #{ip_lease}"
  logger.info "config: #{port.port_info.config}"
  logger.info "state: #{port.port_info.state}"
  logger.info "curr: #{port.port_info.curr}"
  logger.info "advertised: #{port.port_info.advertised}"
  logger.info "supported: #{port.port_info.supported}"
  logger.info "peer: #{port.port_info.peer}"

  port_number = port.port_info.number
  network = get_network_from_map ip_map[:network]

  if not network.virtual
    openflow_controller.install_route         port, mac_lease, ip_lease
    openflow_controller.install_arp_antispoof port, mac_lease, ip_lease

    openflow_controller.install_static_d_transport 17, port, mac_lease, ip_lease, ip_map[:network][:dns_server], 53 if not ip_map[:network][:dns_server].nil?

    if not ip_map[:network][:dns_server].nil?
      openflow_controller.install_static_d_transport 17, port, mac_lease, ip_lease, ip_map[:network][:dhcp_server], 67
      openflow_controller.install_static_d_transport 17, port, mac_lease, ip_lease, ip_map[:network][:dhcp_server], 68
    else
      openflow_controller.install_static_d_transport 17, port, mac_lease, ip_lease, "0.0.0.0/0", 67
      openflow_controller.install_static_d_transport 17, port, mac_lease, ip_lease, "0.0.0.0/0", 68
    end

    add_security_group port, inst_map[:uuid], vif_map

    # Testing guest -> *
    openflow_controller.install_local_icmp          port, mac_lease, ip_lease
    openflow_controller.install_local_transport 6,  port, mac_lease, ip_lease
    openflow_controller.install_local_transport 17, port, mac_lease, ip_lease

  else
    openflow_controller.install_virtual_route network, port, mac_lease, ip_lease
  end

  port.lock.synchronize {
    return if not port.is_active

    network.add_port port_number, true
    openflow_controller.update_network network

    port.network = network
    openflow_controller.ovs_ofctl.add_flows_from_list port.queued_flows
    port.queued_flows.clear
  }
end

#add_openflow_by_instance_id(inst_id) ⇒ Object



91
92
93
94
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 91

def add_openflow_by_instance_id(inst_id)
  port = get_port_from_instance_id inst_id
  add_instance_now port[1] if not port.nil?
end

#add_security_group(port, inst_id, vif_map) ⇒ Object

Cut-n-paste from ServiceNetfilter



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 381

def add_security_group port, inst_id, vif_map
  ng_maps = rpc.request('hva-collector', 'get_security_groups_of_instance', inst_id)
  rules = ng_maps.map { |ng_map|
    ng_map[:rules].map { |rule| rule[:permission] }
  }.flatten

  # security group
  build_rule(rules).each do |rule|
    case rule[:ip_protocol]
    when 'tcp', 'udp'
      if rule[:ip_fport] == rule[:ip_tport]
        openflow_controller.install_static_transport 6, port, port.mac, port.ip, rule[:ip_fport], rule[:ip_source]
      elsif rule[:ip_fport] <= 1 and rule[:ip_tport] >= 65535
        openflow_controller.install_static_transport 6, port, port.mac, port.ip, 0, rule[:ip_source]
      else
        logger.info "add_security_group: No support for port ranges yet: ip_source:#{rule[:ip_source]} ports:#{rule[:ip_fport]}-#{rule[:ip_tport]}"
      end
    when 'icmp'
      # icmp
      #   This extension can be used if `--protocol icmp' is specified. It provides the following option:
      #   [!] --icmp-type {type[/code]|typename}
      #     This allows specification of the ICMP type, which can be a numeric ICMP type, type/code pair, or one of the ICMP type names shown by the command
      #      iptables -p icmp -h
      openflow_controller.install_static_icmp rule[:icmp_type], rule[:icmp_code], port, port.mac, port.ip, rule[:ip_source]
    end
  end
end

#add_tunnel(port) ⇒ Object



257
258
259
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 257

def add_tunnel port
  worker_thread.pass { add_tunnel_now port }
end

#add_tunnel_now(port) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 261

def add_tunnel_now port
  logger.info "Got tunnel port: name:#{port.port_info.name}."

  return if port.object_id != openflow_controller.ports[port.port_info.number].object_id
  return if not port.is_active

  logger.info "port: #{port.port_info.number}"
  logger.info "mac: #{port.port_info.hw_addr.to_s}"
  logger.info "config: #{port.port_info.config}"
  logger.info "state: #{port.port_info.state}"
  logger.info "curr: #{port.port_info.curr}"
  logger.info "advertised: #{port.port_info.advertised}"
  logger.info "supported: #{port.port_info.supported}"
  logger.info "peer: #{port.port_info.peer}"

  # Note that vnet_id may be different from the actual GRE
  # tunnel id used.
  vnet_id = port.port_info.name[/^gre-[a-z]*-([0-9]*)$/, 1].to_i
  raise "GRE tunnel interface name must match 'gre-[a-z]*-[0-9]*'." if vnet_id.nil? or vnet_id == 0

  network = get_network_from_id vnet_id

  port.lock.synchronize {
    return if not port.is_active

    network.add_port port.port_info.number, false
    openflow_controller.update_network network

    port.network = network
    openflow_controller.install_gre_tunnel network.id, port

    openflow_controller.ovs_ofctl.add_flows_from_list port.queued_flows
    port.queued_flows.clear
  }
end

#build_rule(rules = []) ⇒ Object



409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 409

def build_rule(rules = [])
  rule_maps = []

  rules.each do |rule|
    rule = rule.strip.gsub(/[\s\t]+/, '')
    from_group = false
    ipv4s = []

    # ex.
    # "tcp:22,22,ip4:0.0.0.0"
    # "udp:53,53,ip4:0.0.0.0"
    # "icmp:-1,-1,ip4:0.0.0.0"

    # 1st phase
    # ip_tport    : tcp,udp? 1 - 16bit, icmp: -1
    # id_port has been separeted in first phase.
    from_pair, ip_tport, source_pair = rule.split(',')

    next if from_pair.nil?
    next if ip_tport.nil?
    next if source_pair.nil?

    # 2nd phase
    # ip_protocol : [ tcp | udp | icmp ]
    # ip_fport    : tcp,udp? 1 - 16bit, icmp: -1
    ip_protocol, ip_fport = from_pair.split(':')

    # protocol    : [ ip4 | ip6 | #{account_id} ]
    # ip_source   : ip4? xxx.xxx.xxx.xxx./[0-32], ip6? (not yet supprted), #{netfilter_group_id}
    protocol, ip_source = source_pair.split(':')

    begin
      s = StringScanner.new(protocol)
      until s.eos?
        case
        when s.scan(/ip6/)
          # TODO#FUTURE: support IPv6 address format
          next
        when s.scan(/ip4/)
          # IPAddress doesn't support prefix '0'.
          ip_addr, prefix = ip_source.split('/', 2)
          if prefix.to_i == 0
            ip_source = ip_addr
          end
        when s.scan(/a-\w{8}/)
          from_group = true
          inst_maps = rpc.request('hva-collector', 'get_instances_of_account_netfilter_group', protocol, ip_source)
          inst_maps.each { |inst_map|
            ipv4s << inst_map[:ips]
          }
        else
          raise "unexpected protocol '#{s.peep(20)}'"
        end
      end
    rescue Exception => e
      p e
      next
    end

    begin
      if from_group == false
        #p "from_group:(#{from_group}) ip_source -> #{ip_source}"
        ip = IPAddress(ip_source)
        ip_source = case ip.u32
                    when 0
                      "#{ip.address}/0"
                    else
                      "#{ip.address}/#{ip.prefix}"
                    end
      else
        ipv4s = ipv4s.flatten.uniq
      end
    rescue Exception => e
      p e
      next
    end

    case ip_protocol
    when 'tcp', 'udp'
      ip_fport = ip_fport.to_i
      ip_tport = ip_tport.to_i

      # validate port range
      [ ip_fport, ip_tport ].each do |port|
        next unless port >= 1 && port <= 65535
      end

      if ip_fport <= ip_tport
        if from_group == false
          rule_maps << {
            :ip_protocol => ip_protocol,
            :ip_fport    => ip_fport,
            :ip_tport    => ip_tport,
            :protocol    => protocol,
            :ip_source   => ip_source,
          }
        else
          ipv4s.each { |ip|
            rule_maps << {
              :ip_protocol => ip_protocol,
              :ip_fport    => ip_fport,
              :ip_tport    => ip_tport,
              :protocol    => 'ip4',
              :ip_source   => ip,
            }
          }
        end
      end
    when 'icmp'
      # via http://docs.amazonwebservices.com/AWSEC2/latest/CommandLineReference/
      #
      # For the ICMP protocol, the ICMP type and code must be specified.
      # This must be specified in the format type:code where both are integers.
      # Type, code, or both can be specified as -1, which is a wildcard.

      icmp_type = ip_fport.to_i
      icmp_code = ip_tport.to_i

      # icmp_type
      case icmp_type
      when -1
      when 0, 3, 5, 8, 11, 12, 13, 14, 15, 16, 17, 18
      else
        next
      end

      # icmp_code
      case icmp_code
      when -1
      when 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
        # when icmp_type equals -1 icmp_code must equal -1.
        next if icmp_type == -1
      else
        next
      end

      if from_group == false
        rule_maps << {
          :ip_protocol => ip_protocol,
          :icmp_type   => ip_tport.to_i, # ip_tport.to_i, # -1 or 0,       3,    5,       8,        11, 12, 13, 14, 15, 16, 17, 18
          :icmp_code   => ip_fport.to_i, # ip_fport.to_i, # -1 or 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
          :protocol    => protocol,
          :ip_source   => ip_source,
        }
      else
        ipv4s.each { |ip|
          rule_maps << {
            :ip_protocol => ip_protocol,
            :icmp_type   => ip_tport.to_i, # ip_tport.to_i, # -1 or 0,       3,    5,       8,        11, 12, 13, 14, 15, 16, 17, 18
            :icmp_code   => ip_fport.to_i, # ip_fport.to_i, # -1 or 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
            :protocol    => 'ip4',
            :ip_source   => ip,
          }
        }
      end
    end
  end

  rule_maps
end

#create_network(network_map) ⇒ Object



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 343

def create_network network_map
  throw "Network map is invalid: #{network_map.inspect}." if network_map.nil? or network_map[:id] <= 0

  network_id = network_map[:id]
  throw "Network already created" if networks.has_key? network_id and not networks[network_id].nil?

  if not network_map[:ipv4_gw].nil?
    logger.info "Creating physical network: id:#{network_id} link_interface:#{network_map[:link_interface]}."

    # Do more here...
    network = networks[network_id] = OpenFlowNetwork.new(network_id)
    network.add_port OpenFlowController::OFPP_LOCAL, true

    openflow_controller.install_physical_network network

  else
    logger.info "Creating virtual network: id:#{network_id} link_interface:#{network_map[:link_interface]}."
    raise "No valid IPv4 network defined." if network_map[:ipv4_network].nil? or not network_map[:ipv4_network] =~ /\.0$/

    dhcp_ip = IPAddr.new(network_map[:ipv4_network]) | IPAddr.new("0.0.0.1")

    network = networks[network_id] = OpenFlowNetwork.new(network_id)
    network.dhcp_hw = openflow_controller.local_hw
    network.dhcp_ip = dhcp_ip
    network.ipv4_network = IPAddr.new(network_map[:ipv4_network])
    network.prefix = network_map[:prefix]
    network.virtual = true

    openflow_controller.install_virtual_network network
  end

  network
end

#delete_instance(port) ⇒ Object

Always call in the worker thread.



228
229
230
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 228

def delete_instance port
  worker_thread.pass { delete_instance_now port }
end

#delete_instance_now(port) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 232

def delete_instance_now port
  logger.info "deleting instance #{port.port_info.name}."

  return if not port.has_instance

  ip_lease = port.ip
  mac_lease = port.mac

  port.ip = nil
  port.mac = nil
  port.has_instance = false

  logger.info "port: #{port.port_info.number}"
  logger.info "mac: #{port.port_info.hw_addr.to_s} <=> #{mac_lease}"
  logger.info "ip: #{ip_lease}"
  logger.info "config: #{port.port_info.config}"
  logger.info "state: #{port.port_info.state}"
  logger.info "curr: #{port.port_info.curr}"
  logger.info "advertised: #{port.port_info.advertised}"
  logger.info "supported: #{port.port_info.supported}"
  logger.info "peer: #{port.port_info.peer}"

  networks.each { |network| network.remove_port port.port_info.number }
end

#delete_openflow_by_instance_id(inst_id) ⇒ Object



96
97
98
99
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 96

def delete_openflow_by_instance_id(inst_id)
  port = get_port_from_instance_id inst_id
  delete_instance_now port[1] if not port.nil?
end

#delete_tunnel(port) ⇒ Object



297
298
299
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 297

def delete_tunnel port
  worker_thread.pass { delete_tunnel_now port }
end

#delete_tunnel_now(port) ⇒ Object



301
302
303
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 301

def delete_tunnel_now port
  return if not port.has_instance
end

#get_network_from_id(network_id) ⇒ Object



325
326
327
328
329
330
331
332
333
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 325

def get_network_from_id network_id
  return networks[network_id] if networks.has_key? network_id
    
  network_map = rpc.request('hva-collector', 'get_network', network_id)
  raise "Failed to retrieve network #{network_id}." if network_map.nil?

  logger.debug "get network from: id:#{network_id} map:#{network_map.inspect}."
  create_network network_map
end

#get_network_from_map(network_map) ⇒ Object



335
336
337
338
339
340
341
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 335

def get_network_from_map network_map
  if networks.has_key? network_map[:id]
    networks[network_map[:id]]
  else
    create_network network_map
  end
end

#get_physical_networksObject

networks_map.each { |network|

}

end



318
319
320
321
322
323
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 318

def get_physical_networks
  networks_map = rpc.request('hva-collector', 'get_networks')
  raise "Failed to retrieve networks." if networks_map.nil?

  networks_map.select { |network| not network[:ipv4_gw].nil? }
end

#get_port_from_instance_id(inst_id) ⇒ Object

Raises:

  • (ArgumentError)


582
583
584
585
586
587
588
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 582

def get_port_from_instance_id inst_id
  raise ArgumentError, "Unknown Instance ID: #{inst_id}" if inst_id.nil?
  inst_map = rpc.request('hva-collector', 'get_instance', inst_id)
  raise ArgumentError, "Unknown Instance ID: #{inst_id}" if inst_map.nil?

  port = openflow_controller.ports.detect { |f| f[1].port_info.name == inst_map[:instance_nics].first[:uuid] }
end

#openflow_controllerObject



570
571
572
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 570

def openflow_controller
  @openflow_controller ||= OpenFlowController.new(self)
end

#refresh_openflow_by_joined_openflow_group_id(openflow_group_id) ⇒ Object



101
102
103
104
105
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 101

def refresh_openflow_by_joined_openflow_group_id(openflow_group_id)
  raise "UnknownOpenflowGroupID" if openflow_group_id.nil?

  logger.info "Refresh Openflow..."
end

#rpcObject



574
575
576
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 574

def rpc
  @rpc ||= Isono::NodeModules::RpcChannel.new(@node)
end

#worker_threadObject



578
579
580
# File 'lib/dcmgr/node_modules/service_openflow.rb', line 578

def worker_thread
  @worker_thread ||= Isono::ThreadPool.new(1, 'Openflow')
end