softlayer_api 3.0.b1 → 3.0.b2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, "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
@@ -20,6 +20,19 @@
20
20
 
21
21
  require 'xmlrpc/client'
22
22
 
23
+ # utility routine for swapping constants without warnings.
24
+ def with_warnings(flag)
25
+ old_verbose, $VERBOSE = $VERBOSE, flag
26
+ yield
27
+ ensure
28
+ $VERBOSE = old_verbose
29
+ end
30
+
31
+ # enable parsing of "nil" values in structures returned from the API
32
+ with_warnings(nil) {
33
+ XMLRPC::Config.const_set('ENABLE_NIL_PARSER', true)
34
+ }
35
+
23
36
  # The XML-RPC spec calls for the "faultCode" in faults to be an integer
24
37
  # but the SoftLayer XML-RPC API can return strings as the "faultCode"
25
38
  #
@@ -30,25 +43,15 @@ module XMLRPC::Convert
30
43
  def self.fault(hash)
31
44
  if hash.kind_of? Hash and hash.size == 2 and
32
45
  hash.has_key? "faultCode" and hash.has_key? "faultString" and
33
- (hash["faultCode"].kind_of?(Integer) || hash["faultCode"].kind_of?(String)) and hash["faultString"].kind_of? String
46
+ (hash['faultCode'].kind_of?(Integer) || hash['faultCode'].kind_of?(String)) and hash['faultString'].kind_of? String
34
47
 
35
- XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
48
+ XMLRPC::FaultException.new(hash['faultCode'], hash['faultString'])
36
49
  else
37
50
  super
38
51
  end
39
52
  end
40
53
  end
41
54
 
42
- # The XMLRPC client uses a fixed user agent string, but we want to
43
- # supply our own, so we add a method to XMLRPC::Client that lets
44
- # us change it.
45
- class XMLRPC::Client
46
- def self.set_user_agent(new_agent)
47
- remove_const(:USER_AGENT) if const_defined?(:USER_AGENT)
48
- const_set(:USER_AGENT, new_agent)
49
- end
50
- end
51
-
52
55
  module SoftLayer
53
56
  # = SoftLayer API Service
54
57
  #
@@ -62,7 +65,7 @@ module SoftLayer
62
65
  #
63
66
  # client = SoftLayer::Client.new(:username => "Joe", :api_key=>"feeddeadbeefbadfood...")
64
67
  # account_service = client.service_named("Account") # returns the SoftLayer_Account service
65
- # account_service = client['Account'] # Exactly the same as above
68
+ # account_service = client[:Account] # Exactly the same as above
66
69
  #
67
70
  # For backward compatibility, a service can be constructed by passing
68
71
  # client initialization options, however if you do so you will need to
@@ -315,6 +318,7 @@ using either client.service_named('<service_name_here>') or client['<service_nam
315
318
  end
316
319
 
317
320
  @xmlrpc_client.http.set_debug_output($stderr)
321
+ @xmlrpc_client.http.instance_variable_set(:@verify_mode, OpenSSL::SSL::VERIFY_NONE)
318
322
  end # $DEBUG
319
323
  end
320
324
 
@@ -25,14 +25,14 @@ module SoftLayer
25
25
  ##
26
26
  # Returns true if the ticket has "unread" updates
27
27
  def has_updates?
28
- self["newUpdatesFlag"]
28
+ self['newUpdatesFlag']
29
29
  end
30
30
 
31
31
  ##
32
32
  # Returns true if the ticket is a server admin ticket
33
33
  def server_admin_ticket?
34
34
  # note that serverAdministrationFlag comes from the server as an Integer (0, or 1)
35
- self["serverAdministrationFlag"] != 0
35
+ self['serverAdministrationFlag'] != 0
36
36
  end
37
37
 
38
38
  ##
@@ -46,7 +46,7 @@ module SoftLayer
46
46
  # Override of service from ModelBase. Returns the SoftLayer_Ticket service
47
47
  # set up to talk to the ticket with my ID.
48
48
  def service
49
- return softlayer_client["Ticket"].object_with_id(self.id)
49
+ return softlayer_client[:Ticket].object_with_id(self.id)
50
50
  end
51
51
 
52
52
  ##
@@ -96,7 +96,7 @@ module SoftLayer
96
96
  softlayer_client = client || Client.default_client
97
97
  raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
98
98
 
99
- @ticket_subjects = softlayer_client['Ticket_Subject'].getAllObjects();
99
+ @ticket_subjects = softlayer_client[:Ticket_Subject].getAllObjects();
100
100
  end
101
101
 
102
102
  @ticket_subjects
@@ -122,7 +122,7 @@ module SoftLayer
122
122
  object_mask = default_object_mask.to_sl_object_mask
123
123
  end
124
124
 
125
- ticket_data = softlayer_client["Ticket"].object_with_id(ticket_id).object_mask(object_mask).getObject()
125
+ ticket_data = softlayer_client[:Ticket].object_with_id(ticket_id).object_mask(object_mask).getObject()
126
126
 
127
127
  return new(softlayer_client, ticket_data)
128
128
  end
@@ -153,8 +153,8 @@ module SoftLayer
153
153
  assigned_user_id = options[:assigned_user_id]
154
154
 
155
155
  if(nil == assigned_user_id)
156
- current_user = softlayer_client["Account"].object_mask("id").getCurrentUser()
157
- assigned_user_id = current_user["id"]
156
+ current_user = softlayer_client[:Account].object_mask("id").getCurrentUser()
157
+ assigned_user_id = current_user['id']
158
158
  end
159
159
 
160
160
  new_ticket = {
@@ -164,7 +164,7 @@ module SoftLayer
164
164
  'title' => title
165
165
  }
166
166
 
167
- ticket_data = softlayer_client["Ticket"].createStandardTicket(new_ticket, body)
167
+ ticket_data = softlayer_client[:Ticket].createStandardTicket(new_ticket, body)
168
168
  return new(softlayer_client, ticket_data)
169
169
  end
170
170
  end
@@ -0,0 +1,280 @@
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
+ # The VLANFirewall class represents the firewall that protects
9
+ # all the servers on a VLAN in the SoftLayer Environment. It is
10
+ # also known as a "Dedicated Firewall" in some documentation.
11
+ #
12
+ # Instances of this class are a bit odd because they actually represent a
13
+ # VLAN (the VLAN protected by the firewall to be specific), and not the
14
+ # physical hardware implementing the firewall itself. (although the device
15
+ # is accessible as the "networkVlanFirewall" property)
16
+ #
17
+ # As a result, instances of this class correspond to certain instances
18
+ # in the SoftLayer_Network_Vlan service.
19
+ #
20
+ class VLANFirewall < SoftLayer::ModelBase
21
+ include ::SoftLayer::DynamicAttribute
22
+
23
+ ##
24
+ #:attr_reader:
25
+ #
26
+ # The number of the VLAN protected by this firewall.
27
+ #
28
+ sl_attr :VLAN_number, 'vlanNumber'
29
+
30
+ ##
31
+ # :attr_reader:
32
+ #
33
+ # The set of rules applied by this firewall to incoming traffic.
34
+ # The object will retrieve the rules from the network API every
35
+ # time you ask it for the rules.
36
+ #
37
+ # The code will sort the rules by their "orderValue" which is the
38
+ # order that the firewall applies the rules, however please see
39
+ # the important note in change_rules! concerning the "orderValue"
40
+ # property of the rules.
41
+ sl_dynamic_attr :rules do |firewall_rules|
42
+ firewall_rules.should_update? do
43
+ # firewall rules update every time you ask for them.
44
+ return true
45
+ end
46
+
47
+ firewall_rules.to_update do
48
+ acl_id = rules_ACL_id()
49
+ rules_data = self.softlayer_client[:Network_Firewall_AccessControlList].object_with_id(acl_id).object_mask(self.class.default_rules_mask).getRules
50
+ rules_data.sort { |lhs, rhs| lhs['orderValue'] <=> rhs['orderValue'] }
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Returns the name of the primary router the firewall is attached to.
56
+ # This is often a "customer router" in one of the datacenters.
57
+ def primaryRouter
58
+ return self['primaryRouter']['hostname']
59
+ end
60
+
61
+ ##
62
+ # The fully qualified domain name of the physical device the
63
+ # firewall is implemented by.
64
+ def fullyQualifiedDomainName
65
+ if self.has_sl_property?('networkVlanFirewall')
66
+ return self['networkVlanFirewall']['fullyQualifiedDomainName']
67
+ else
68
+ return @softlayer_hash
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Returns true if this is a "high availability" firewall, that is a firewall
74
+ # that exists as one member of a redundant pair.
75
+ def high_availability?
76
+ # note that highAvailabilityFirewallFlag is a boolean in the softlayer hash
77
+ return self.has_sl_property?('highAvailabilityFirewallFlag') && self['highAvailabilityFirewallFlag']
78
+ end
79
+
80
+ ##
81
+ # Cancel the firewall
82
+ #
83
+ # This method cancels the firewall and releases its
84
+ # resources. The cancellation is processed immediately!
85
+ # Call this method with careful deliberation!
86
+ #
87
+ # Notes is a string that describes the reason for the
88
+ # cancellation. If empty or nil, a default string will
89
+ # be added.
90
+ #
91
+ def cancel!(notes = nil)
92
+ user = self.softlayer_client[:Account].object_mask("mask[id,account.id]").getCurrentUser
93
+ notes = "Cancelled by a call to #{__method__} in the softlayer_api gem" if notes == nil || notes == ""
94
+
95
+ cancellation_request = {
96
+ 'accountId' => user['account']['id'],
97
+ 'userId' => user['id'],
98
+ 'items' => [ {
99
+ 'billingItemId' => self['networkVlanFirewall']['billingItem']['id'],
100
+ 'immediateCancellationFlag' => true
101
+ } ],
102
+ 'notes' => notes
103
+ }
104
+
105
+ self.softlayer_client[:Billing_Item_Cancellation_Request].createObject(cancellation_request)
106
+ end
107
+
108
+ ##
109
+ # Change the set of rules for the firewall.
110
+ # The rules_data parameter should be an array of hashes where
111
+ # each hash gives the conditions of the rule. The keys of the
112
+ # hashes should be entries from the array returned by
113
+ # SoftLayer::ServerFirewall.default_rules_mask_keys
114
+ #
115
+ # *NOTE!* When changing the rules on the firewall, you must
116
+ # pass in a complete set of rules each time. The rules you
117
+ # submit will replace the entire ruleset on the destination
118
+ # firewall.
119
+ #
120
+ # *NOTE!* The rules themselves have an "orderValue" property.
121
+ # It is this property, and *not* the order that the rules are
122
+ # found in the rules_data array, which will determine in which
123
+ # order the firewall applies its rules to incomming traffic.
124
+ #
125
+ # *NOTE!* Changes to the rules are not applied immediately
126
+ # on the server side. Instead, they are enqueued by the
127
+ # firewall update service and updated periodically. A typical
128
+ # update will take about one minute to apply, but times may vary
129
+ # depending on the system load and other circumstances.
130
+ def change_rules!(rules_data)
131
+ change_object = {
132
+ "firewallContextAccessControlListId" => rules_ACL_id(),
133
+ "rules" => rules_data
134
+ }
135
+
136
+ self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
137
+ end
138
+
139
+ ##
140
+ # This method asks the firewall to ignore its rule set and pass all traffic
141
+ # through the firewall. Compare the behavior of this routine with
142
+ # change_routing_bypass!
143
+ #
144
+ # It is important to note that changing the bypass to :bypass_firewall_rules
145
+ # removes ALL the protection offered by the firewall. This routine should be
146
+ # used with extreme discretion.
147
+ #
148
+ # Note that this routine queues a rule change and rule changes may take
149
+ # time to process. The change will probably not take effect immediately.
150
+ #
151
+ # The two symbols accepted as arguments by this routine are:
152
+ # :apply_firewall_rules - The rules of the firewall are applied to traffic. This is the default operating mode of the firewall
153
+ # :bypass_firewall_rules - The rules of the firewall are ignored. In this configuration the firewall provides no protection.
154
+ #
155
+ def change_rules_bypass!(bypass_symbol)
156
+ change_object = {
157
+ "firewallContextAccessControlListId" => rules_ACL_id(),
158
+ "rules" => self.rules
159
+ }
160
+
161
+ case bypass_symbol
162
+ when :apply_firewall_rules
163
+ change_object['bypassFlag'] = false
164
+ self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
165
+ when :bypass_firewall_rules
166
+ change_object['bypassFlag'] = true
167
+ self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
168
+ else
169
+ raise ArgumentError, "An invalid parameter was sent to #{__method__}. It accepts :apply_firewall_rules and :bypass_firewall_rules"
170
+ end
171
+ end
172
+
173
+ ##
174
+ # This method allows you to route traffic around the firewall
175
+ # and directly to the servers it protects. Compare the behavior of this routine with
176
+ # change_rules_bypass!
177
+ #
178
+ # It is important to note that changing the routing to :route_around_firewall
179
+ # removes ALL the protection offered by the firewall. This routine should be
180
+ # used with extreme discretion.
181
+ #
182
+ # Note that this routine constructs a transaction. The Routing change
183
+ # may not happen immediately.
184
+ #
185
+ # The two symbols accepted as arguments by the routine are:
186
+ # :route_through_firewall - Network traffic is sent through the firewall to the servers in the VLAN segment it protects. This is the usual operating mode of the firewall.
187
+ # :route_around_firewall - Network traffic will be sent directly to the servers in the VLAN segment protected by this firewall. This means that the firewall will *NOT* be protecting those servers.
188
+ #
189
+ def change_routing_bypass!(routing_symbol)
190
+ vlan_firewall_id = self['networkVlanFirewall']['id']
191
+
192
+ raise "Could not identify the device for a VLAN firewall" if !vlan_firewall_id
193
+
194
+ case routing_symbol
195
+ when :route_through_firewall
196
+ self.softlayer_client[:Network_Vlan_Firewall].object_with_id(vlan_firewall_id).updateRouteBypass(false)
197
+ when :route_around_firewall
198
+ self.softlayer_client[:Network_Vlan_Firewall].object_with_id(vlan_firewall_id).updateRouteBypass(true)
199
+ else
200
+ raise ArgumentError, "An invalid parameter was sent to #{__method__}. It accepts :route_through_firewall and :route_around_firewall"
201
+ end
202
+ end
203
+
204
+ ##
205
+ # Collect a list of the firewalls on the account.
206
+ #
207
+ # This list is obtained by asking the account for all the VLANs
208
+ # it has that also have a networkVlanFirewall component.
209
+ def self.find_firewalls(client = nil)
210
+ softlayer_client = client || Client.default_client
211
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
212
+
213
+ # only VLAN firewallas have a networkVlanFirewall component
214
+ vlan_firewall_filter = SoftLayer::ObjectFilter.new() { |filter|
215
+ filter.accept("networkVlans.networkVlanFirewall").when_it is_not_null
216
+ }
217
+
218
+ vlan_firewalls = softlayer_client[:Account].object_mask(vlan_firewall_mask).object_filter(vlan_firewall_filter).getNetworkVlans
219
+ vlan_firewalls.collect { |firewall_data| SoftLayer::VLANFirewall.new(softlayer_client, firewall_data)}
220
+ end
221
+
222
+
223
+ #--
224
+ # Methods for the SoftLayer model
225
+ #++
226
+
227
+ def service
228
+ # Objects of this class are a bit odd because they actually represent VLANs (the VLAN protected by the firewall)
229
+ # and not the physical hardware implementing the firewall itself. (although the device is accessible as the
230
+ # "networkVlanFirewall" property)
231
+ self.softlayer_client[:Network_Vlan].object_with_id(self.id)
232
+ end
233
+
234
+ def softlayer_properties(object_mask = nil)
235
+ service = self.service
236
+ service = service.object_mask(object_mask) if object_mask
237
+ service.object_mask(self.class.vlan_firewall_mask).getObject
238
+ end
239
+
240
+ #--
241
+ #++
242
+ private
243
+
244
+ # Searches the set of access control lists for the firewall device in order to locate the one that
245
+ # sits on the "outside" side of the network and handles 'in'coming traffic.
246
+ def rules_ACL_id
247
+ outside_interface_data = self['firewallInterfaces'].find { |interface_data| interface_data['name'] == 'outside' }
248
+ incoming_ACL = outside_interface_data['firewallContextAccessControlLists'].find { |firewallACL_data| firewallACL_data['direction'] == 'in' } if outside_interface_data
249
+
250
+ if incoming_ACL
251
+ return incoming_ACL['id']
252
+ else
253
+ return nil
254
+ end
255
+ end
256
+
257
+ def self.vlan_firewall_mask
258
+ return "mask[primaryRouter,highAvailabilityFirewallFlag," +
259
+ "firewallInterfaces.firewallContextAccessControlLists," +
260
+ "networkVlanFirewall[id,datacenter,primaryIpAddress,firewallType,fullyQualifiedDomainName,billingItem.id]]"
261
+ end
262
+
263
+ def self.default_rules_mask
264
+ return { "mask" => default_rules_mask_keys }.to_sl_object_mask
265
+ end
266
+
267
+ def self.default_rules_mask_keys
268
+ ['orderValue',
269
+ 'action',
270
+ 'destinationIpAddress',
271
+ 'destinationIpSubnetMask',
272
+ 'protocol',
273
+ 'destinationPortRangeStart',
274
+ 'destinationPortRangeEnd',
275
+ 'sourceIpAddress',
276
+ 'sourceIpSubnetMask',
277
+ 'version']
278
+ end
279
+ end # class Firewall
280
+ end # module SoftLayer