Skip to content

Commit cecc3d2

Browse files
committed
Added firewall ordering
1 parent 5a7a606 commit cecc3d2

10 files changed

Lines changed: 364 additions & 11 deletions

CHANGELOG.textile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
*3.0*
22
* Substantially rewrote the ObjectFilter class. ObjectFilters used to be hashes which made it easy to manipulate their content incorrectly. The new implementation has a strict interface that makes it harder to manipulate filters incorrectly.
3-
* Added a model for Virtual Server Image Templates (SoftLayer::ImageTemplate) - VirtualServerOrder now requires an instance of this class rather than allowing you to provide just the global_id of an image
3+
* Added a model for Virtual Server Image Templates (SoftLayer::ImageTemplate) - VirtualServerOrder now requires an instance of this class rather than allowing you to provide the global_id of an image
44
* Added a model for data centers (SoftLayer::Datacenter). Bare Metal, Bare Metal Package, and Virtual server orders now use an instance of Datacenter to identify where their servers will be provisioned. The routines in those classes which used to provide lists of valid data center names now return data center objects.
55
* Virtual Server Upgrades are now handled by the VirtualServerUpgradeOrder class and not the VirtualServer class. This change was made for several reasons. Firt and foremost, it allows multiple aspects of a virtual server to be upgraded at once without having to wait on separate transactions to complete between upgrades. Secondly it opens the door for additional upgrades (for example, to disk configuration) to be added in the future.
66
* Added a method to reboot servers.
77
* The routine to retreive the open tickets on an account has been moved from the Ticket class. The set of open tickets is now a dynamic property of an account object.
8+
* The Model Layer now includes models for Server (aka. Shared) and VLAN (aka. Dedicated) firewalls in the ServerFirewall, and VLANFireall classes respectively. There are corresponding classes for ordering firewalls (ServerFirewallOrder and VLANFirewallOrder). To facilitate the process of locating the 'id' for a firewall, the Account class includes the find_VLAN_with_number routine which lets you look up the segments of a firewall from the VLAN nubmer.
89

910
*2.2*
1011
* Added the ability to set a timout for network requests. The timeout is given when a client is created by passing the :timeout hash parameter when creating a client. The value of the parameter is an integer number of seconds.

examples/order_server_firewall.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#
2+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
#
22+
23+
require 'rubygems'
24+
require 'softlayer_api'
25+
require 'pp'
26+
27+
# This is the id of the server you want to protect with a firewall.
28+
# The server can be Bare Metal or Virtual. It should have a public
29+
# network interface, and it should not already have a firewall on it.
30+
server_id = 257696 # 12345
31+
32+
# In this example, we assume this is a Bare Metal Server
33+
is_virtual_server = false
34+
35+
# Work with the SoftLayer API begins with a client. By setting
36+
# the "default" client we avoid having to specify the client repeatedly
37+
# in calls that follow.
38+
SoftLayer::Client.default_client = SoftLayer::Client.new(
39+
# :username => "joecustomer" # enter your username here
40+
# :api_key => "feeddeadbeefbadf00d..." # enter your api key here
41+
)
42+
43+
# in this case we go straight to the appropriate class to find the server
44+
# an alternative might be to create the account for this client and
45+
# search the list of servers for the one with the appropriate ID.
46+
if is_virtual_server
47+
server = SoftLayer::VirtualServer.server_with_id(server_id)
48+
else
49+
server = SoftLayer::BareMetalServer.server_with_id(server_id)
50+
end
51+
52+
# Create an instance of SoftLayer::ServerFirewallOrder
53+
order = SoftLayer::ServerFirewallOrder.new(server)
54+
55+
begin
56+
# this example calls order.verify which will build the order, submit it
57+
# to the network API, and will throw an exception if the order is
58+
# invalid.
59+
order.verify()
60+
puts "Firewall order is good for #{server.fullyQualifiedDomainName}"
61+
rescue => exception
62+
puts "Firewall order failed for #{server.fullyQualifiedDomainName} because #{exception}"
63+
end

lib/softlayer/APIParameterFilter.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ def initialize(target, starting_parameters = nil)
4343
@parameters = starting_parameters || {}
4444
end
4545

46+
##
47+
# API Parameter filters will call through to a particular service
48+
# but that service is defined by their target
49+
def service_name
50+
return @target.service_name
51+
end
52+
4653
##
4754
# Adds an API filter that narrows the scope of a call to an object with
4855
# a particular ID. For example, if you want to get the ticket

lib/softlayer/Account.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,24 @@ def service
126126
softlayer_client[:Account].object_with_id(self.id)
127127
end
128128

129+
##
130+
# Searches the account's list of VLANs for the ones with the given
131+
# vlan number. This may return multiple results because a VLAN can
132+
# span different routers and you will get a separate segment for
133+
# each router.
134+
#
135+
# The IDs of the different segments can be helpful for ordering
136+
# firewalls.
137+
#
138+
def find_VLAN_with_number(vlan_number)
139+
filter = SoftLayer::ObjectFilter.new() { |filter|
140+
filter.accept('networkVlans.vlanNumber').when_it is vlan_number
141+
}
142+
143+
vlan_data = self.service.object_mask("mask[id,vlanNumber,primaryRouter,networkSpace]").object_filter(filter).getNetworkVlans
144+
return vlan_data
145+
end
146+
129147
##
130148
# Using the login credentials in the client, retrieve
131149
# the account associated with those credentials.

lib/softlayer/BareMetalServer.rb

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,22 @@ def cancel!(reason = :unneeded, comment = '')
5555
end
5656

5757
##
58-
# Returns the SoftLayer Service used to work with this Server
58+
# Returns the typical Service used to work with this Server
5959
# For Bare Metal Servers that is +SoftLayer_Hardware+ though in some special cases
60-
# you may have to use +SoftLayer_Hardware_Server+ as a type or service.
60+
# you may have to use +SoftLayer_Hardware_Server+ as a type or service. That
61+
# service object is available thorugh the hardware_server_service method
6162
def service
6263
return softlayer_client[:Hardware].object_with_id(self.id)
6364
end
6465

66+
##
67+
# Returns the SoftLayer_Hardware_Server service for this bare metal server
68+
# This service is used less often than SoftLayer_Hardware, but may be required
69+
# for some operations
70+
def hardware_server_service
71+
self.softlayer_client[:Hardware_Server].object_with_id(self.id)
72+
end
73+
6574
##
6675
# Returns the default object mask used when fetching servers from the API when an
6776
# explicit object mask is not provided.
@@ -106,6 +115,57 @@ def self.cancellation_reasons
106115
}
107116
end
108117

118+
##
119+
# Returns the max port speed of the public network interfaces of the server taking into account
120+
# bound interface pairs (redundant network cards).
121+
def firewall_port_speed
122+
network_components = self.service.object_mask("mask[id,maxSpeed,networkComponentGroup.networkComponents]").getFrontendNetworkComponents()
123+
124+
# Split the interfaces into grouped and ungrouped interfaces. The max speed of a group will be the sum
125+
# of the individual speeds in that group. The max speed of ungrouped interfaces is simply the max speed
126+
# of that interface.
127+
grouped_interfaces, ungrouped_interfaces = network_components.partition{ |interface| interface.has_key?("networkComponentGroup") }
128+
129+
if !grouped_interfaces.empty?
130+
group_speeds = grouped_interfaces.collect do |interface|
131+
interface['networkComponentGroup']['networkComponents'].inject(0) {|total_speed, component| total_speed += component['maxSpeed']}
132+
end
133+
134+
max_group_speed = group_speeds.max
135+
else
136+
max_group_speed = 0
137+
end
138+
139+
if !ungrouped_interfaces.empty?
140+
max_ungrouped_speed = ungrouped_interfaces.collect { |interface| interface['maxSpeed']}.max
141+
else
142+
max_ungrouped_speed = 0
143+
end
144+
145+
return [max_group_speed, max_ungrouped_speed].max
146+
end
147+
148+
##
149+
# Change the current port speed of the server
150+
#
151+
# +new_speed+ is expressed Mbps and should be 0, 10, 100, or 1000.
152+
# Ports have a maximum speed that will limit the actual speed set
153+
# on the port.
154+
#
155+
# Set +public+ to +false+ in order to change the speed of the
156+
# primary private network interface.
157+
#
158+
def change_port_speed(new_speed, public = true)
159+
if public
160+
self.hardware_server_service.setPublicNetworkInterfaceSpeed(new_speed)
161+
else
162+
self.hardware_server_service.setPrivateNetworkInterfaceSpeed(new_speed)
163+
end
164+
165+
self.refresh_details()
166+
self
167+
end
168+
109169
##
110170
# Retrive the bare metal server with the given server ID from the
111171
# SoftLayer API

lib/softlayer/Server.rb

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ def set_domain!(new_domain)
170170
self.refresh_details()
171171
end
172172

173+
##
174+
# Returns the max port speed of the public network interfaces of the server taking into account
175+
# bound interface pairs (redundant network cards).
176+
def firewall_port_speed
177+
network_components = self.service.object_mask("mask[id,maxSpeed]").getFrontendNetworkComponents()
178+
max_speeds = network_components.collect { |component| component['maxSpeed'] }
179+
180+
max_speeds.empty? ? 0 : max_speeds.max
181+
end
182+
173183
##
174184
# Change the current port speed of the server
175185
#
@@ -180,15 +190,9 @@ def set_domain!(new_domain)
180190
# Set +public+ to +false+ in order to change the speed of the
181191
# primary private network interface.
182192
#
193+
# This is an abstract method implemented by subclasses
183194
def change_port_speed(new_speed, public = true)
184-
if public
185-
self.service.setPublicNetworkInterfaceSpeed(new_speed)
186-
else
187-
self.service.setPrivateNetworkInterfaceSpeed(new_speed)
188-
end
189-
190-
self.refresh_details()
191-
self
195+
raise "The abstract change_port_speed method of SoftLayer::Server was called. Subclasses should implement the method for their particular service"
192196
end
193197

194198
##
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#--
2+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3+
#
4+
# For licensing information see the LICENSE.md file in the project root.
5+
#++
6+
7+
module SoftLayer
8+
#
9+
# This class allows you to order a Firewall for a server
10+
#
11+
class ServerFirewallOrder
12+
# The server that you are ordering the firewall for.
13+
attr_reader :server
14+
15+
##
16+
# Create a new order for the given server
17+
def initialize (server)
18+
@server = server
19+
20+
raise ArgumentError.new("Server does not have an active Public interface") if server.firewall_port_speed == 0
21+
end
22+
23+
##
24+
# Calls the SoftLayer API to verify that the template provided by this order is valid
25+
# This routine will return the order template generated by the API or will throw an exception
26+
#
27+
# This routine will not actually create a Bare Metal Instance and will not affect billing.
28+
#
29+
# If you provide a block, it will receive the order template as a parameter and
30+
# the block may make changes to the template before it is submitted.
31+
def verify()
32+
order_template = firewall_order_template
33+
order_template = yield order_template if block_given?
34+
35+
server.softlayer_client[:Product_Order].verifyOrder(order_template)
36+
end
37+
38+
##
39+
# Calls the SoftLayer API to place an order for a new server based on the template in this
40+
# order. If this succeeds then you will be billed for the new server.
41+
#
42+
# If you provide a block, it will receive the order template as a parameter and
43+
# the block may make changes to the template before it is submitted.
44+
def place_order!()
45+
order_template = firewall_order_template
46+
order_template = yield order_template if block_given?
47+
48+
server.softlayer_client[:Product_Order].placeOrder(order_template)
49+
end
50+
51+
protected
52+
53+
##
54+
# Returns a hash of the creation options formatted to be sent *to*
55+
# the SoftLayer API for either verification or completion
56+
def firewall_order_template
57+
client = server.softlayer_client
58+
additional_products_package = SoftLayer::ProductPackage.additional_products_package(client)
59+
60+
template = {
61+
'complexType' => 'SoftLayer_Container_Product_Order_Network_Protection_Firewall',
62+
'quantity' => 1,
63+
'packageId' => additional_products_package.id
64+
}
65+
66+
if @server.service.service_name == "SoftLayer_Virtual_Guest"
67+
template['virtualGuests'] = [{'id' => @server.id}]
68+
else
69+
template['hardware'] = [{'id' => @server.id}]
70+
end
71+
72+
expected_description = "#{@server.firewall_port_speed}Mbps Hardware Firewall"
73+
firewall_items = additional_products_package.items_with_description(expected_description)
74+
75+
raise "Could not find a price item matching the description '#{expected_description}'" if firewall_items.empty?
76+
77+
firewall_item = firewall_items[0]
78+
79+
template['prices'] = [{ 'id' => firewall_item.price_id }] if firewall_item.respond_to?(:price_id)
80+
81+
template
82+
end
83+
end # class ServerFirewallOrder
84+
end # module SoftLayer

0 commit comments

Comments
 (0)