Class: ACIrb::RestClient

Inherits:
Object
  • Object
show all
Defined in:
lib/restclient.rb

Overview

REST client end point implementation

Defined Under Namespace

Classes: ApicAuthenticationError, ApicErrorResponse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ RestClient

Public: Initializes and establishes an authenticated session with APIC

REST endpoint

options - Hash options used to specify connectivity

attributes (default: {}):

:url - string URL of APIC, e.g., https://apic (required)
:user - string containing User ID for authentication (required)
:password - string containing Password for
            authentication (required)
:debug - boolean true or false for including verbose REST output
         (default: false)
:format - string 'xml' or 'json' specifying the format to use
          for messaging to APIC. (default: xml)
:verify - boolean true or false for verifying the SSL
          certificate. (default: false)

Examples:

rest = ACIrb::RestClient.new(url: 'https://apic', user: 'admin',
                             password: 'password', format: 'json',
                             debug: false)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/restclient.rb', line 42

def initialize(options = {})
  uri = URI.parse(options[:url])
  @baseurl = '%s://%s:%s' % [uri.scheme, uri.host, uri.port]
  @format = options[:format] ? options[:format] : 'xml'

  @user = options[:user]
  @password = options[:password]

  @verify = options[:verify]

  @client = HTTPClient.new

  @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE \
    unless options[:verify] && uri.scheme == 'https'

  @debug = options[:debug]

  @auth_cookie = ''

  authenticate if @user && @password
end

Instance Attribute Details

Returns the value of attribute auth_cookie.



13
14
15
# File 'lib/restclient.rb', line 13

def auth_cookie
  @auth_cookie
end

#baseurlObject

Returns the value of attribute baseurl.



12
13
14
# File 'lib/restclient.rb', line 12

def baseurl
  @baseurl
end

#debugObject

Returns the value of attribute debug.



12
13
14
# File 'lib/restclient.rb', line 12

def debug
  @debug
end

#formatObject

Returns the value of attribute format.



12
13
14
# File 'lib/restclient.rb', line 12

def format
  @format
end

#passwordObject

Returns the value of attribute password.



12
13
14
# File 'lib/restclient.rb', line 12

def password
  @password
end

#refresh_timeObject (readonly)

Returns the value of attribute refresh_time.



13
14
15
# File 'lib/restclient.rb', line 13

def refresh_time
  @refresh_time
end

#userObject

Returns the value of attribute user.



12
13
14
# File 'lib/restclient.rb', line 12

def user
  @user
end

#verifyObject

Returns the value of attribute verify.



12
13
14
# File 'lib/restclient.rb', line 12

def verify
  @verify
end

Instance Method Details

#authenticateObject

Public: Authenticates the REST session with APIC Sends a aaaLogin message to APIC and updates the following instance variables:

@auth_cookie - session cookie
@refresh_time - session refresh timeout in seconds

Returns nothing.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/restclient.rb', line 71

def authenticate
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.aaaUser(name: @user, pwd: @password)
  end
  post_url = URI.encode(@baseurl.to_s + '/api/mo/aaaLogin.xml')
  puts 'POST REQUEST', post_url if @debug
  puts 'POST BODY', builder.to_xml if @debug
  response = @client.post(post_url, body: builder.to_xml)
  puts 'POST RESPONSE: ', response.body if @debug
  doc = Nokogiri::XML(response.body)
  fail ApicAuthenticationError, 'Authentication error(%s): %s' % [doc.at_css('error')['code'], doc.at_css('error')['text']] \
    if doc.at_css('error')
  fail ApicErrorResponse, 'Unexpected HTTP Error response code(%s): %s' % [response.code, response.body] if response.code != 200
  @auth_cookie = doc.at_css('aaaLogin')['token']
  @refresh_time = doc.at_css('aaaLogin')['refreshTimeoutSeconds']
end

#get(options) ⇒ Object

Internal: Queries the APIC REST API for data

options - Hash options for defining get parameters (default: {})

:url - relative URL for request (required)

Returns results of parse_response, which will be the parsed results of the XML or JSON payload represented as ACIrb::MO objects



140
141
142
143
144
145
146
147
148
# File 'lib/restclient.rb', line 140

def get(options)
  get_url = URI.encode(@baseurl.to_s + options[:url].to_s)

  puts 'GET REQUEST', get_url if @debug
  response = @client.get(get_url)
  puts 'GET RESPONSE: ', response.body if @debug

  parse_response(response)
end

#lookupByClass(cls, options = {}) ⇒ Object

Public: Helper function that performs a simple lookup on a Class

cls - string containing the class name to query (required) options - Hash options for defining query options (default: {})

:subtree - specifies the subtree query options, which can be
           children, full or self

Examples

# return all L1 physical interfaces on the fabric with complete subtree
mo = rest.lookupByClass('l1PhysIf', subtree: 'full')

Returns an array of ACIrb::MO objects for the query



317
318
319
320
321
322
# File 'lib/restclient.rb', line 317

def lookupByClass(cls, options = {})
  subtree = options[:subtree]
  cls_query = ACIrb::ClassQuery.new(cls)
  cls_query.subtree = subtree
  query(cls_query)
end

#lookupByDn(dn, options = {}) ⇒ Object

Public: Helper function that performs a simple lookup on a Dn

dn - string containing distinguished name for the object to query

(required)

options - Hash options for defining query options (default: {})

:subtree - specifies the subtree query options, which can be
           children, full or self

Examples

mo = rest.lookupByDn('uni/tn-common', subtree: 'full')

Returns a single ACIrb::MO object or nil if no response for the query is received



293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/restclient.rb', line 293

def lookupByDn(dn, options = {})
  subtree = options[:subtree]
  dn_query = ACIrb::DnQuery.new(dn)
  dn_query.subtree = subtree

  mos = query(dn_query)
  if mos.length == 1
    return mos[0]
  else
    return nil
  end
end

#parse_error(doc) ⇒ Object

Internal: Parses for error responses in APIC response payload

doc - Nokigiri XML document or Hash array containing well formed

APIC response payload (required)


154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/restclient.rb', line 154

def parse_error(doc)
  if format == 'xml'
    fail ApicErrorResponse, 'Error response from APIC (%s): "%s"' % \
      [doc.at_css('error')['code'], doc.at_css('error')['text']] \
      if doc.at_css('error')
  elsif format == 'json'
    fail ApicErrorResponse, 'Error response from APIC (%s): "%s"' % \
      [doc['imdata'][0]['error']['attributes']['code'].to_s, \
       doc['imdata'][0]['error']['attributes']['text'].to_s] \
       if doc['imdata'].length > 0 && doc['imdata'][0].include?('error')
  end
end

#parse_response(response) ⇒ Object

Internal: Parses APIC response payload into ACIrb::MO objects

response - string containing the XML or JSON payload that will be

parsed according to the format defined at instance creation
(required)


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
# File 'lib/restclient.rb', line 172

def parse_response(response)
  if format == 'xml'
    xml_data = response.body
    doc = Nokogiri::XML(xml_data)

    parse_error(doc)

    mos = []
    doc.root.elements.each do |xml_obj|
      mo = ACIrb::Loader.load_xml(xml_obj)
      mos.push(mo)
    end

    return mos

  elsif format == 'json'
    json_data = response.body
    doc = JSON.parse(json_data)

    parse_error(doc)

    mos = []
    doc['imdata'].each do |json_obj|
      mo = ACIrb::Loader.load_json(json_obj)
      mos.push(mo)
    end

    return mos
  end
end

#post(options) ⇒ Object

Internal: Posts data to the APIC REST interface

options - Hash options for defining post parameters (default: {})

:url - relative URL for request (required)
:data - post payload to be included in the request (required)

Returns results of parse_response, which will be the parsed results of the XML or JSON payload represented as ACIrb::MO objects



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/restclient.rb', line 115

def post(options)
  post_url = URI.encode(@baseurl.to_s + options[:url].to_s)

  data = options[:data]
  if @format == 'xml'
    data = data.to_xml
  elsif @format == 'json'
    data = data.to_json
  end

  puts 'POST REQUEST', post_url if @debug
  puts 'POST BODY', data if @debug
  response = @client.post(post_url, body: data)
  puts 'POST RESPONSE: ', response.body if @debug

  parse_response(response)
end

#query(query_obj) ⇒ Object

Public: Sends a query to APIC and returns the matching MO objects

query_obj - ACIrb::Query object, typically either ACIrb::DnQuery or

ACIrb::ClassQuery which contains the query that will be issued
(required)

Examples

dn_query = ACIrb::DnQuery.new('uni/tn-common')
dn_query.subtree = 'full'
mos = rest.query(dn_query)

Returns array of ACIrb::MO objects for the query



215
216
217
218
# File 'lib/restclient.rb', line 215

def query(query_obj)
  query_uri = query_obj.uri(@format)
  get(url: query_uri)
end

#refresh_sessionObject

Public: Refreshes an existing RestClient object session Sends a aaaRefresh message to APIC and updates the following instance variables:

@auth_cookie - session cookie
@refresh_time - session refresh timeout in seconds

Returns nothing.



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/restclient.rb', line 95

def refresh_session
  get_url = URI.encode(@baseurl.to_s + '/api/mo/aaaRefresh.xml')
  puts 'GET REQUEST', get_url if @debug
  response = @client.get(get_url)
  puts 'GET RESPONSE: ', response.body if @debug
  doc = Nokogiri::XML(response.body)
  fail ApicAuthenticationError, 'Authentication error(%s): %s' % [doc.at_css('error')['code'], doc.at_css('error')['text']] \
    if doc.at_css('error')
  @auth_cookie = doc.at_css('aaaLogin')['token']
  @refresh_time = doc.at_css('aaaLogin')['refreshTimeoutSeconds']
end

#refresh_subscription(subscription_id) ⇒ Object

Public: Refreshes an existing subscription query

subscription_id - string containing the subscription ID for a previously

subscribed to query

Examples

class_query = ACIrb::ClassQuery.new('fvCEp')
class_query.page_size = '1'
class_query.page = '0'
subscription_id = rest.subscribe(class_query)
sleep(50)
rest.refresh_subscription(subcription_id)

Returns nothing.



276
277
278
279
# File 'lib/restclient.rb', line 276

def refresh_subscription(subscription_id)
  query_uri = '/api/subscriptionRefresh.%s?id=%s' % [@format, subscription_id]
  get(url: query_uri)
end

#subscribe(query_obj) ⇒ Object

Public: Sends an event subscription query to APIC

query_obj - ACIrb::Query object, typically either ACIrb::DnQuery or

ACIrb::ClassQuery which contains the query that will be
issued. This query will have the .subscribe property set
to "yes" as part of the subscription process (required)

Examples

# subscribe to all changes on fvCEp end points on fabric
# but restrict the results of the query to only include 1
# as to reduce the initial subscription time
class_query = ACIrb::ClassQuery.new('fvCEp')
class_query.page_size = '1'
class_query.page = '0'
subscription_id = rest.subscribe(class_query)

Returns the subscription ID for the newly registered subscription



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/restclient.rb', line 237

def subscribe(query_obj)
  query_obj.subscribe = 'yes'
  query_uri = query_obj.uri(@format)

  get_url = URI.encode(@baseurl.to_s + query_uri.to_s)

  puts 'GET REQUEST', get_url if @debug
  response = @client.get(get_url)
  puts 'GET RESPONSE: ', response.body if @debug

  if format == 'xml'
    xml_data = response.body
    doc = Nokogiri::XML(xml_data)
    parse_error(doc)
    subscriptionId = doc.at_css('imdata')['subscriptionId']
  elsif format == 'json'
    json_data = response.body
    doc = JSON.parse(json_data)
    parse_error(doc)
    subscriptionId = doc['subscriptionId']
  end

  subscriptionId
end