diff --git a/server/lib/deltacloud/collections/instances.rb b/server/lib/deltacloud/collections/instances.rb index 6038bee9..62696dfc 100644 --- a/server/lib/deltacloud/collections/instances.rb +++ b/server/lib/deltacloud/collections/instances.rb @@ -38,6 +38,9 @@ class Instances < Base if driver.class.has_feature?(:instances, :authentication_key) @opts[:keys] = driver.keys(credentials) end + if driver.has_capability? :networks + @opts[:networks] = driver.networks(credentials) + end end get '/instances/:id/run' do @@ -57,7 +60,11 @@ class Instances < Base param :realm_id, :string, :optional param :hwp_id, :string, :optional param :keyname, :string, :optional + param :network_id, :string, :optional + param :subnet_id, :string, :optional control do + params.merge!({:network_id => params["network_id"].split("+").first}) unless params["network_id"].empty? + params.merge!({:subnet_id => params["network_id"].split("+").last}) unless params["network_id"].empty? @instance = driver.create_instance(credentials, params[:image_id], params) if @instance.kind_of? Array @elements = @instance diff --git a/server/lib/deltacloud/collections/networks.rb b/server/lib/deltacloud/collections/networks.rb new file mode 100644 index 00000000..34c7fadb --- /dev/null +++ b/server/lib/deltacloud/collections/networks.rb @@ -0,0 +1,63 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 Deltacloud::Collections + class Networks < Base + + include Deltacloud::Features + + set :capability, lambda { |m| driver.respond_to? m } + check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) } + + get '/networks/new' do + respond_to do |format| + format.html { haml :"networks/new" } + end + end + + collection :networks do + + standard_show_operation + standard_index_operation + + operation :create, :with_capability => :create_network do + param :address_block, :string, :optional + param :name, :string, :optional + control do + @network = driver.create_network(credentials, { :address_block => params[:address_block]}) + respond_to do |format| + format.xml { haml :"networks/show" } + format.html { haml :"networks/show" } + format.json { xml_to_json("networks/show")} + end + end + end + + operation :destroy, :with_capability => :destroy_network do + control do + driver.destroy_network(credentials, params[:id]) + status 204 + respond_to do |format| + format.xml + format.json + format.html { redirect(networks_url) } + end + end + end + + end + + end +end diff --git a/server/lib/deltacloud/collections/subnets.rb b/server/lib/deltacloud/collections/subnets.rb new file mode 100644 index 00000000..9d5fd74f --- /dev/null +++ b/server/lib/deltacloud/collections/subnets.rb @@ -0,0 +1,65 @@ + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 Deltacloud::Collections + class Subnets < Base + + include Deltacloud::Features + + set :capability, lambda { |m| driver.respond_to? m } + check_features :for => lambda { |c, f| driver.class.has_feature?(c, f) } + + get '/subnets/new' do + respond_to do |format| + format.html { haml :"subnets/new" } + end + end + + + collection :subnets do + + standard_show_operation + standard_index_operation + + operation :create, :with_capability => :create_subnet do + param :network_id, :string, :required + param :address_block, :string, :required + control do + @subnet = driver.create_subnet(credentials, { :network_id => params[:network_id], :address_block => params[:address_block]}) + respond_to do |format| + format.xml { haml :"subnets/show"} + format.html { haml :"subnets/show" } + format.json { xml_to_json("subnets/show")} + end + end + end + + operation :destroy, :with_capability => :destroy_subnet do + control do + driver.destroy_subnet(credentials, params[:id]) + status 204 + respond_to do |format| + format.xml + format.json + format.html { redirect(subnets_url) } + end + end + end + + end + + end +end diff --git a/server/lib/deltacloud/drivers/base_driver.rb b/server/lib/deltacloud/drivers/base_driver.rb index 85a21487..3d146116 100644 --- a/server/lib/deltacloud/drivers/base_driver.rb +++ b/server/lib/deltacloud/drivers/base_driver.rb @@ -259,6 +259,13 @@ def address(credentials, opts={}) addresses(credentials, opts).first if has_capability?(:addresses) end + def network(credentials, opts={}) + networks(credentials, opts).first if has_capability?(:networks) + end + + def subnet(credentials, opts={}) + subnets(credentials, opts).first if has_capability?(:subnets) + end MEMBER_SHOW_METHODS = [ :realm, :image, :instance, :storage_volume, :bucket, :blob, :key, :firewall ] unless defined?(MEMBER_SHOW_METHODS) diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb index 62c72cc2..88016eb5 100644 --- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb +++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb @@ -268,14 +268,8 @@ def create_instance(credentials, image_id, opts={}) if opts[:metrics] and !opts[:metrics].empty? instance_options[:monitoring_enabled] = true end - if opts[:realm_id] - az, sn = opts[:realm_id].split(":") - if sn - instance_options[:subnet_id] = sn - else - instance_options[:availability_zone] = az - end - end + instance_options[:availability_zone] = opts[:realm_id] if opts[:realm_id] + instance_options[:subnet_id] = opts[:subnet_id] if opts[:subnet_id] #FIXME should we fail if no :network_id ? don't need it but need consistency in API... instance_options[:key_name] = opts[:keyname] if opts[:keyname] instance_options[:instance_type] = opts[:hwp_id] if opts[:hwp_id] && opts[:hwp_id].length > 0 firewalls = opts.inject([]){|res, (k,v)| res << v if k =~ /firewalls\d+$/; res} @@ -835,6 +829,73 @@ def delete_firewall_rule(credentials, opts={}) end end + #Deltacloud Networks == Amazon VPC + def networks(credentials, opts={}) + ec2 = new_client(credentials) + networks = [] + safely do + subnets = subnets(credentials) #get all subnets once + ec2.describe_vpcs.each do |vpc| + vpc_subnets = subnets.inject([]){|res,cur| res<opts[:vpc_id]) + # + end + + def ports(credentials, opts={}) + # + # Need more investigations + # + end + def providers(credentials, opts={}) ec2 = new_client(credentials) @providers ||= ec2.describe_regions.map{|r| Provider.new( {:id=>r, :name=>r, @@ -966,7 +1027,7 @@ def convert_instance(instance) unless instance[:subnet_id].empty? realm_id = "#{realm_id}:#{instance[:subnet_id]}" end - Instance.new( + inst_params = { :id => instance[:aws_instance_id], :name => instance[:aws_image_id], :state => convert_state(instance[:aws_state]), @@ -981,8 +1042,11 @@ def convert_instance(instance) :private_addresses => [InstanceAddress.new(instance[:private_dns_name], :type => :hostname)], :firewalls => instance[:aws_groups], :storage_volumes => instance[:block_device_mappings].map{|vol| {vol.values.first=>vol.keys.first } }, - :create_image => can_create_image - ) + :create_image => can_create_image } + if instance[:vpc_id] + inst_params.merge!(:network_bindings => [{:network=>instance[:vpc_id], :subnet=>instance[:subnet_id], :ip_address=> instance[:aws_private_ip_address]}]) + end + Instance.new(inst_params) end def convert_key(key) @@ -1159,6 +1223,25 @@ def convert_state(ec2_state) end end + def convert_vpc(vpc, subnets=[]) + addr_blocks = subnets.inject([]){|res,cur| res << cur.address_block ; res} + Network.new({ :id => vpc[:vpc_id], + :name => vpc[:vpc_id], + :state=> vpc[:state], + :subnets => subnets.inject([]){|res,cur| res << cur.id ;res}, + :address_blocks=> (addr_blocks.empty? ? [vpc[:cidr_block]] : addr_blocks) }) + end + + def convert_subnet(subnet) + Subnet.new({ :id => subnet[:subnet_id], + :name => subnet[:subnet_id], + :network =>subnet[:vpc_id], + :address_block => subnet[:cidr_block], + :state => subnet[:state] }) + end + + + exceptions do on /root device is not supported for the instance/ do diff --git a/server/lib/deltacloud/drivers/mock/data/networks/net1.yml b/server/lib/deltacloud/drivers/mock/data/networks/net1.yml new file mode 100644 index 00000000..b39f2fdf --- /dev/null +++ b/server/lib/deltacloud/drivers/mock/data/networks/net1.yml @@ -0,0 +1,9 @@ +--- +:id: net1 +:name: network1 +:subnets: +- subnet1 +- subnet2 +:address_blocks: +- 192.168.0.0/16 +:state: UP diff --git a/server/lib/deltacloud/drivers/mock/data/networks/net2.yml b/server/lib/deltacloud/drivers/mock/data/networks/net2.yml new file mode 100644 index 00000000..535481b0 --- /dev/null +++ b/server/lib/deltacloud/drivers/mock/data/networks/net2.yml @@ -0,0 +1,9 @@ +--- +:id: net2 +:name: network2 +:subnets: +- subnet3 +- subnet4 +:address_blocks: +- 10.0.0.0/8 +:state: UP diff --git a/server/lib/deltacloud/drivers/mock/data/subnets/subnet1.yml b/server/lib/deltacloud/drivers/mock/data/subnets/subnet1.yml new file mode 100644 index 00000000..11d5db9d --- /dev/null +++ b/server/lib/deltacloud/drivers/mock/data/subnets/subnet1.yml @@ -0,0 +1,6 @@ +--- +:id: subnet1 +:name: subnet1 +:network: net1 +:address_block: 192.168.1.0/24 +:state: UP diff --git a/server/lib/deltacloud/drivers/mock/data/subnets/subnet2.yml b/server/lib/deltacloud/drivers/mock/data/subnets/subnet2.yml new file mode 100644 index 00000000..d2ef7c23 --- /dev/null +++ b/server/lib/deltacloud/drivers/mock/data/subnets/subnet2.yml @@ -0,0 +1,6 @@ +--- +:id: subnet2 +:name: subnet2 +:network: net1 +:address_block: 192.168.2.0/24 +:state: UP diff --git a/server/lib/deltacloud/drivers/mock/data/subnets/subnet3.yml b/server/lib/deltacloud/drivers/mock/data/subnets/subnet3.yml new file mode 100644 index 00000000..7109179f --- /dev/null +++ b/server/lib/deltacloud/drivers/mock/data/subnets/subnet3.yml @@ -0,0 +1,6 @@ +--- +:id: subnet3 +:name: subnet3 +:network: net2 +:address_block: 10.1.0.0/16 +:state: UP diff --git a/server/lib/deltacloud/drivers/mock/data/subnets/subnet4.yml b/server/lib/deltacloud/drivers/mock/data/subnets/subnet4.yml new file mode 100644 index 00000000..937baafc --- /dev/null +++ b/server/lib/deltacloud/drivers/mock/data/subnets/subnet4.yml @@ -0,0 +1,6 @@ +--- +:id: subnet4 +:name: subnet4 +:network: net2 +:address_block: 10.2.0.0/16 +:state: UP diff --git a/server/lib/deltacloud/drivers/mock/mock_driver.rb b/server/lib/deltacloud/drivers/mock/mock_driver.rb index 07c67e2e..0389b65e 100644 --- a/server/lib/deltacloud/drivers/mock/mock_driver.rb +++ b/server/lib/deltacloud/drivers/mock/mock_driver.rb @@ -227,6 +227,14 @@ def create_instance(credentials, image_id, opts={}) :actions => instance_actions_for((initial_state == "STARTED" ? "RUNNING" : initial_state)), :user_data => opts[:user_data] ? Base64::decode64(opts[:user_data]) : nil } + if opts[:network_id] && opts[:subnet_id] + net = network(credentials, {:id => opts[:network_id]}) + snet = subnet(credentials, {:id => opts[:subnet_id]}) + cidr = IPAddr.new(snet.address_block) + address = cidr.to_range.to_a.sample.to_s #sloppy, choose random address - hey it's mock! + net_bind = [{:network => net.id, :subnet=>snet.id, :ip_address=>address}] + instance.merge!({:network_bindings => net_bind}) + end @client.store(:instances, instance) Instance.new( instance ) @@ -561,6 +569,67 @@ def metric(credentials, opts={}) metric end + #Deltacloud Networks == Amazon VPC + def networks(credentials, opts={}) + check_credentials(credentials) + networks = @client.build_all(Network) + filter_on(networks, opts, :id) + end + + def create_network(credentials, opts={}) + check_credentials(credentials) + id = opts[:name] || "net_#{Time.now.to_i}" + net_hash = { :id => id, + :name => id, + :address_blocks => [opts[:address_block]], + :state => "UP"} + @client.store(:networks, net_hash) + Network.new(net_hash) + end + + def destroy_network(credentials, network_id) + check_credentials(credentials) + net = network(credentials, {:id => network_id}) + #also destroy subnets: + net.subnets.each do |sn| + destroy_subnet(credentials, sn) + end + @client.destroy(:networks, network_id) + end + + def subnets(credentials, opts={}) + check_credentials(credentials) + subnets = @client.build_all(Subnet) + filter_on(subnets, opts, :id) + end + + def create_subnet(credentials, opts={}) + check_credentials(credentials) + id = opts[:name] || "subnet_#{Time.now.to_i}" + snet_hash = { :id => id, + :name => id, + :address_block => opts[:address_block], + :network => opts[:network_id], + :state => "UP"} + @client.store(:subnets, snet_hash) + #also update network: + net = @client.load_collection(:networks, opts[:network_id]) + net[:subnets] ||=[] + net[:subnets] << snet_hash[:id] + @client.store(:networks, net) + Subnet.new(snet_hash) + end + + def destroy_subnet(credentials, subnet_id) + check_credentials(credentials) + snet = subnet(credentials, {:id => subnet_id}) + #also update network: + net = @client.load_collection(:networks, snet.network) + net[:subnets].delete(subnet_id) + @client.store(:networks, net) + @client.destroy(:subnets, subnet_id) + end + private def check_credentials(credentials) @@ -647,7 +716,7 @@ def update_bucket_size(id, change) status 403 end - on /BucketNotExist/ do + on /(BucketNotExist || NotFound)/ do status 404 end diff --git a/server/lib/deltacloud/drivers/openstack/openstack_driver.rb b/server/lib/deltacloud/drivers/openstack/openstack_driver.rb index 343934d1..bfcca855 100644 --- a/server/lib/deltacloud/drivers/openstack/openstack_driver.rb +++ b/server/lib/deltacloud/drivers/openstack/openstack_driver.rb @@ -162,19 +162,24 @@ def realms(credentials, opts={}) def instances(credentials, opts={}) os = new_client(credentials) - insts = attachments = [] + insts = attachments = ports = [] safely do + if quantum = have_quantum?(credentials) + ports = quantum.ports + end if opts[:id] begin server = os.get_server(opts[:id]) - insts << convert_from_server(server, os.connection.authuser, get_attachments(opts[:id], os)) + net_bind = get_net_bindings_for(server.id, ports) + insts << convert_from_server(server, os.connection.authuser, get_attachments(opts[:id], os), net_bind) rescue => e raise e unless e.message =~ /The resource could not be found/ insts = [] end else insts = os.list_servers_detail.collect do |s| - convert_from_server(s, os.connection.authuser,get_attachments(s[:id], os)) + net_bind = get_net_bindings_for(s[:id], ports) + convert_from_server(s, os.connection.authuser,get_attachments(s[:id], os), net_bind) end end end @@ -182,6 +187,30 @@ def instances(credentials, opts={}) insts end + #le port: + #=> #"f78bfc05-ead0-40a6-8325-a35eb2b535c3", "ip_address"=>"10.0.0.4"}], @device_id="fe4022fa-a77c-4adf-be45-6e069fb3a314", @device_owner="", @tenant_id="7be215d541ea4db4a23b3a84b0882408"> + def get_net_bindings_for(server_id, ports) + return [] if ports.empty? + net_bind = [] + ports.each do |port| + if port.device_id == server_id + port.fixed_ips.each do |fix_ip| + net_bind << {:network=> port.network_id, :subnet=>fix_ip["subnet_id"] , :ip_address=>fix_ip["ip_address"]} + end + end + end + net_bind + end + + def have_quantum?(credentials) + begin + quantum = new_client(credentials, "network") + rescue => e + return nil + end + quantum + end + def create_instance(credentials, image_id, opts) os = new_client( credentials, "compute") result = nil @@ -203,7 +232,13 @@ def create_instance(credentials, image_id, opts) end safely do server = os.create_server(params) - result = convert_from_server(server, os.connection.authuser, get_attachments(server.id, os)) + net_bind = [] + if opts[:network_id] && opts[:subnet_id] && (quantum=have_quantum?(credentials)) #place instance into a network + port = quantum.create_port(opts[:network_id], {"fixed_ips"=>[{"subnet_id"=>opts[:subnet_id]}], "device_id"=>server.id}) + net_bind = [{:network=>opts[:network], :subnet=>opts[:subnet_id], :ip_address=>port.fixed_ips.first["ip_address"]}] + server.refresh + end + result = convert_from_server(server, os.connection.authuser, get_attachments(server.id, os), net_bind) end result end @@ -224,6 +259,13 @@ def destroy_instance(credentials, instance_id) safely do server = os.get_server(instance_id) server.delete! + if quantum = have_quantum?(credentials) #destroy ports if any + quantum.ports.each do |port| + if port.device_id == server.id + quantum.delete_port(port.id) + end + end + end end begin server.populate @@ -473,6 +515,160 @@ def destroy_storage_snapshot(credentials, opts={}) end end + def networks(credentials, opts={}) + os = new_client(credentials, "network") + networks = [] + safely do + subnets = os.subnets + os.networks.each do |net| + addr_blocks = get_address_blocks_for(net.id, subnets) + networks << convert_network(net, addr_blocks) + end + end + networks = filter_on(networks, :id, opts) + end + + def get_address_blocks_for(network_id, subnets) + return [] if subnets.empty? + addr_blocks = [] + subnets.each do |sn| + if sn.network_id == network_id + addr_blocks << sn.cidr + end + end + addr_blocks + end + + + def convert_network(net, addr_blocks) + Network.new({ :id => net.id, + :name => net.name, + :subnets => net.subnets, + :state => (net.admin_state_up ? "UP" : "DOWN"), + :address_blocks => addr_blocks + #NOT USED :address_block, :ports + }) + end + + #require params for openstack: {:name} + def create_network(credentials, opts={}) + os = new_client(credentials, "network") + safely do + net = os.create_network(opts[:name] || "net_#{Time.now.to_i}") + convert_network(net) + end + end + + def destroy_network(credentials, id) + os = new_client(credentials, "network") + safely do + os.delete_network(id) + end + end + + #can update name or admin_state_up (true/false) + def update_network(credentials, opts={}) + os = new_client(credentials, "network") + safely do + os.update_network(opts) + end + end + + #you can list all subnets - don't need to supply network_id + #each subnet returned 'knows' its parent network_id + #no opts - could use opts for filtering = e.g. ?name=foo + def subnets(credentials, opts={}) + os = new_client(credentials, "network") + subnets = [] + safely do + os.subnets.each do |subnet| + subnets << convert_subnet(subnet) + end + end + subnets = filter_on(subnets, :id, opts) + end + + def convert_subnet(subnet) + Subnet.new({ :id => subnet.id, + :name => subnet.name, + :network => subnet.network_id, + :address_block => subnet.cidr, #or allocation_pools? start..end + # :state :type + # in quantum, ports have a 'network_id' + }) + end + + #required params: :network_id, cidr_block + #optional params: :ip_version, :gateway_ip, :allocation_pools + def create_subnet(credentials, opts={}) + os = new_client(credentials, "network") + safely do + convert_subnet(os.create_subnet(opts[:network_id], opts[:address_block])) + end + end + + #can update gateway_ip, name + def update_subnet(credentials, opts={}) + os = new_client(credentials, "network") + safely do + os.update_subnet() + end + end + + def destroy_subnet(credentials, subnet_id) + os = new_client(credentials, "network") + safely do + os.delete_subnet(subnet_id) + end + end + +# def ports(credentials, opts={}) +# os = new_client(credentials, "network") +# ports = [] +# safely do +# os.ports.each do |port| +# ports << convert_port(port) +# end +# end +# ports +# end +# +# def convert_port(port) +# Port.new({ :id => port.id, +# :attachment => port.device_id, +# :network => port.network_id, #network, not subnet +# :mac_address => port.mac_address, +# :state => (port.admin_state_up ? "UP" : "DOWN" ), # true/false +# :ip_address => port.fixed_ips # this is a structure; like [{"subnet_id": "f45087fa-a673-4c98-ba9e-e21642448997", "ip_address": "10.0.0.5"}] - COULD BE >1 address here... +# # UNUSED/no mapping for :type, :attachment (can be inferred from IP address(es)?) +# }) +# end +# +# #required params: network_id +# #optional params: name, mac_address, state, fixedIP (subnet_id AND/OR IP) +# def create_port(credentials, opts={}) +# os = new_client(credentials, "network") +# safely do +# convert_port(os.create_port(opts)) +# end +# end +# +# +# #can update name, device_id, state (what else? not clear from API docs) +# def update_port(credentials, opts={}) +# os = new_client(credentials, "network") +# safely do +# os.update_port(opts[:id], opts) +# end +# end +# +# def destroy_port(credentials, port_id) +# os = new_client(credentials, "network") +# safely do +# os.delete_port(port_id) +# end +# end +# private #for v2 authentication credentials.name == "username+tenant_name" def new_client(credentials, type="compute", ignore_provider=false) @@ -495,7 +691,7 @@ def new_client(credentials, type="compute", ignore_provider=false) connection_params.merge!({:region => region}) if region && !ignore_provider # hack needed for 'def providers' safely do raise ValidationFailure.new(Exception.new("Error: tried to initialise Openstack connection using" + - " an unknown service_type: #{type}")) unless ["volume", "compute", "object-store"].include? type + " an unknown service_type: #{type}")) unless ["volume", "compute", "object-store", "network"].include? type OpenStack::Connection.create(connection_params) end end @@ -529,7 +725,7 @@ def convert_from_image(image, owner) }) end - def convert_from_server(server, owner, attachments=[]) + def convert_from_server(server, owner, attachments=[], net_bind=[]) op = (server.class == Hash)? :fetch : :send image = server.send(op, :image) flavor = server.send(op, :flavor) @@ -538,7 +734,7 @@ def convert_from_server(server, owner, attachments=[]) rescue IndexError password = "" end - inst = Instance.new( + inst_params = { :id => server.send(op, :id).to_s, :realm_id => "default", :owner_id => owner, @@ -555,7 +751,11 @@ def convert_from_server(server, owner, attachments=[]) :keyname => server.send(op, :key_name), :launch_time => server.send(op, :created), :storage_volumes => attachments.inject([]){|res, cur| res << {cur[:volumeId] => cur[:device]} ;res} - ) + } + unless net_bind.empty? + inst_params.merge!(:network_bindings=>net_bind) + end + inst = Instance.new(inst_params) inst.actions = instance_actions_for(inst.state) inst.create_image = 'RUNNING'.eql?(inst.state) inst diff --git a/server/lib/deltacloud/models.rb b/server/lib/deltacloud/models.rb index e6020e64..7dbf3dc0 100644 --- a/server/lib/deltacloud/models.rb +++ b/server/lib/deltacloud/models.rb @@ -32,3 +32,5 @@ require_relative 'models/state_machine' require_relative 'models/storage_snapshot' require_relative 'models/storage_volume' +require_relative 'models/network' +require_relative 'models/subnet' diff --git a/server/lib/deltacloud/models/instance.rb b/server/lib/deltacloud/models/instance.rb index 6c6b0189..e88e5f06 100644 --- a/server/lib/deltacloud/models/instance.rb +++ b/server/lib/deltacloud/models/instance.rb @@ -34,6 +34,7 @@ class Instance < BaseModel attr_accessor :create_image attr_accessor :firewalls attr_accessor :storage_volumes + attr_accessor :network_bindings def to_hash(context) r = { diff --git a/server/lib/deltacloud/models/network.rb b/server/lib/deltacloud/models/network.rb new file mode 100644 index 00000000..d46ac27d --- /dev/null +++ b/server/lib/deltacloud/models/network.rb @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 Deltacloud +class Network < BaseModel + + attr_accessor :name + attr_accessor :subnets + attr_accessor :address_blocks + attr_accessor :state + + def initialize(init=nil) + super(init) + self.subnets = [] unless self.subnets + end + +end +end diff --git a/server/lib/deltacloud/models/subnet.rb b/server/lib/deltacloud/models/subnet.rb new file mode 100644 index 00000000..d91a7a9f --- /dev/null +++ b/server/lib/deltacloud/models/subnet.rb @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 Deltacloud +class Subnet < BaseModel + + attr_accessor :name + attr_accessor :network + attr_accessor :address_block + attr_accessor :state + attr_accessor :type + +end +end diff --git a/server/views/instances/new.html.haml b/server/views/instances/new.html.haml index f508e7f8..8b2c0801 100644 --- a/server/views/instances/new.html.haml +++ b/server/views/instances/new.html.haml @@ -84,6 +84,15 @@ %input{ :type => :text, :id => "path#{i}", :name => "path#{i}", :value => ""} Path #{i} %input{ :type => "file", :name => "content#{i}", :size => 50 } + - if driver.has_capability? :networks + %div{ 'data-role' => :fieldcontain } + %label{ :for => :network, :class => 'ui-input-text'} Network to Launch Instance into: + %select{:name => 'network_id', :'data-native-menu' => "true" } + %option{ :value => ''} None + - @networks.each do |net| + - net.subnets.each do |sn| + %option{ :value => "#{net.id}+#{sn}" } #{net.id}+#{sn} + - if !hardware_profiles.empty? %div{ 'data-role' => :fieldcontain } %h3 Instance profile diff --git a/server/views/instances/show.html.haml b/server/views/instances/show.html.haml index 58e912e0..afeabd42 100644 --- a/server/views/instances/show.html.haml +++ b/server/views/instances/show.html.haml @@ -56,6 +56,15 @@ -instance.storage_volumes.each do |vol| %li %a{ :href => storage_volume_url("#{vol.keys.first}"), :'data-ajax' => 'false'}=["#{vol.keys.first}", "#{vol.values.first}"].compact.reject{ |e| e.empty? }.join(' <---> ') + - if @instance.network_bindings + %li{ :'data-role' => 'list-divider'} Network Bindings + -@instance.network_bindings.each do |net_bind| + %li + %a{ :href => network_url("#{net_bind[:network]}"), :'data-ajax' => 'false'}="Network #{net_bind[:network]}" + %li + %a{ :href => subnet_url("#{net_bind[:subnet]}"), :'data-ajax' => 'false'}="Subnet #{net_bind[:subnet]}" + %li="Address #{net_bind[:ip_address]}" + %li %li{ :'data-role' => 'list-divider'} Actions %li %div{ :'data-role' => 'controlgroup', :'data-type' => "horizontal" } diff --git a/server/views/networks/index.html.haml b/server/views/networks/index.html.haml new file mode 100644 index 00000000..aa0050bd --- /dev/null +++ b/server/views/networks/index.html.haml @@ -0,0 +1,10 @@ +=header "Networks" do + %a{ :href => url_for('networks/new'), :'data-icon' => :plus, :'data-role' => :button, :class => 'ui-btn-right'} Create new network + +%div{ :'data-role' => :content, :'data-theme' => 'c'} + %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'a'} + - @elements.each do |net| + %li + %a{ :href => network_url(net.id), :'data-ajax' => 'false'} + %img{ :class => 'ui-link-thumb', :src => '/images/cloud.png'} + %h3=net.id diff --git a/server/views/networks/index.xml.haml b/server/views/networks/index.xml.haml new file mode 100644 index 00000000..59fa939c --- /dev/null +++ b/server/views/networks/index.xml.haml @@ -0,0 +1,4 @@ +!!!XML +%networks + - @elements.each do |c| + = haml :'networks/show', :locals => { :network => c, :partial => true } diff --git a/server/views/networks/new.html.haml b/server/views/networks/new.html.haml new file mode 100644 index 00000000..0087259a --- /dev/null +++ b/server/views/networks/new.html.haml @@ -0,0 +1,10 @@ +=header "Create new network" + +%div{ :'data-role' => :content, :'data-theme' => 'c', :class => 'middle-dialog'} + %form{ :action => networks_url, :method => :post} + %div{ 'data-role' => :fieldcontain } + %p + %label{ :for => :address_block} CIDR Address block (optional): + %p + %input{ :type => :text, :id => :address_block, :name => :address_block, :value => '' } + %button{ :type => :submit} Create network diff --git a/server/views/networks/show.html.haml b/server/views/networks/show.html.haml new file mode 100644 index 00000000..89a636e6 --- /dev/null +++ b/server/views/networks/show.html.haml @@ -0,0 +1,25 @@ +=header "Network" +=subheader @network.id + +%div{ :'data-role' => :content, :'data-theme' => 'c'} + %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'd'} + %li{ :'data-role' => 'list-divider'} Identifier + %li + %p{ :'data-role' => 'fieldcontain'}=@network.id + %li{ :'data-role' => 'list-divider'} Name + %li + %p{ :'data-role' => 'fieldcontain'}=@network.name + %li{ :'data-role' => 'list-divider'} State + %li + %p{ :'data-role' => 'fieldcontain'}=@network.state + %li{ :'data-role' => 'list-divider'} Address Blocks + %li + %p{ :'data-role' => 'fieldcontain'}=(@network.address_blocks ? @network.address_blocks.join(",") : nil) + %li{ :'data-role' => 'list-divider'} Subnets + -@network.subnets.each do |sn| + %li + %a{ :href => subnet_url(sn.strip), :'data-ajax'=>'false' }=sn + %li{ :'data-role' => 'list-divider'} Actions + %li + %div{ :'data-role' => 'controlgroup', :'data-type' => "horizontal" } + =link_to_action "Destroy", destroy_network_url(@network.id), :delete diff --git a/server/views/networks/show.xml.haml b/server/views/networks/show.xml.haml new file mode 100644 index 00000000..16f9e411 --- /dev/null +++ b/server/views/networks/show.xml.haml @@ -0,0 +1,15 @@ +- unless defined?(partial) + !!! XML +%network{ :href => network_url(network.id), :id => network.id } + %name=network.name + %state< + =network.state + %address_blocks + - network.address_blocks.each do |addr_block| + %address_block=addr_block + %subnets + - (network.subnets || []).each do |subnet| + %subnet{:href => subnet_url(subnet), :id=>subnet} + %actions + - if driver.respond_to?(:destroy_network) + %link{ :rel => "destroy", :method => "delete", :href => destroy_network_url(network.id)} diff --git a/server/views/subnets/index.html.haml b/server/views/subnets/index.html.haml new file mode 100644 index 00000000..5442c176 --- /dev/null +++ b/server/views/subnets/index.html.haml @@ -0,0 +1,10 @@ +=header "Subnets" do + %a{ :href => url_for('subnets/new'), :'data-icon' => :plus, :'data-role' => :button, :class => 'ui-btn-right'} Create new subnet + +%div{ :'data-role' => :content, :'data-theme' => 'c'} + %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'a'} + - @elements.each do |subnet| + %li + %a{ :href => subnet_url(subnet.id), :'data-ajax' => 'false'} + %img{ :class => 'ui-link-thumb', :src => '/images/cloud.png'} + %h3=subnet.id diff --git a/server/views/subnets/index.xml.haml b/server/views/subnets/index.xml.haml new file mode 100644 index 00000000..5079a8d0 --- /dev/null +++ b/server/views/subnets/index.xml.haml @@ -0,0 +1,4 @@ +!!!XML +%subnets + - @elements.each do |c| + = haml :'subnets/show', :locals => { :subnet => c, :partial => true } diff --git a/server/views/subnets/new.html.haml b/server/views/subnets/new.html.haml new file mode 100644 index 00000000..c542d8e0 --- /dev/null +++ b/server/views/subnets/new.html.haml @@ -0,0 +1,14 @@ +=header "Create new subnet" + +%div{ :'data-role' => :content, :'data-theme' => 'c', :class => 'middle-dialog'} + %form{ :action => subnets_url, :method => :post} + %div{ 'data-role' => :fieldcontain } + %p + %label{ :for => :network_id} Network ID: + %p + %input{ :type => :text, :id => :network_id, :name => :network_id, :value => '' } + %p + %label{ :for => :address_block} CIDR Address block: + %p + %input{ :type => :text, :id => :address_block, :name => :address_block, :value => '' } + %button{ :type => :submit} Create subnet diff --git a/server/views/subnets/show.html.haml b/server/views/subnets/show.html.haml new file mode 100644 index 00000000..dcb33c9f --- /dev/null +++ b/server/views/subnets/show.html.haml @@ -0,0 +1,24 @@ +=header "Subnet" +=subheader @subnet.id + +%div{ :'data-role' => :content, :'data-theme' => 'c'} + %ul{ :'data-role' => :listview , :'data-inset' => :true, :'data-divider-theme' => 'd'} + %li{ :'data-role' => 'list-divider'} Identifier + %li + %p{ :'data-role' => 'fieldcontain'}=@subnet.id + %li{ :'data-role' => 'list-divider'} Name + %li + %p{ :'data-role' => 'fieldcontain'}=@subnet.name + %li{ :'data-role' => 'list-divider'} Network + %li + %a{ :href => network_url(@subnet.network.strip), :'data-ajax'=>'false' }=@subnet.network + %li{ :'data-role' => 'list-divider'} State + %li + %p{ :'data-role' => 'fieldcontain'}=@subnet.state + %li{ :'data-role' => 'list-divider'} Address Block + %li + %p{ :'data-role' => 'fieldcontain'}=(@subnet.address_block ? @subnet.address_block : nil) + %li{ :'data-role' => 'list-divider'} Actions + %li + %div{ :'data-role' => 'controlgroup', :'data-type' => "horizontal" } + =link_to_action "Destroy", destroy_subnet_url(@subnet.id), :delete diff --git a/server/views/subnets/show.xml.haml b/server/views/subnets/show.xml.haml new file mode 100644 index 00000000..75363af5 --- /dev/null +++ b/server/views/subnets/show.xml.haml @@ -0,0 +1,13 @@ +- unless defined?(partial) + !!! XML +%subnet{ :href => subnet_url(subnet.id), :id => subnet.id } + %name=subnet.name + %state< + =subnet.state + %address_block + =subnet.address_block + %network + =subnet.network + %actions + - if driver.respond_to?(:destroy_subnet) + %link{ :rel => "destroy", :method => "delete", :href => destroy_subnet_url(subnet.id)}