cvprac 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +51 -0
- data/.rspec +4 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +24 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +36 -0
- data/Gemfile +6 -0
- data/Guardfile +11 -0
- data/Jenkinsfile +239 -0
- data/LICENSE +29 -0
- data/README.md +152 -0
- data/Rakefile +55 -0
- data/cvprac.gemspec +57 -0
- data/lib/cvprac.rb +41 -0
- data/lib/cvprac/api.rb +79 -0
- data/lib/cvprac/api/configlet.rb +309 -0
- data/lib/cvprac/api/info.rb +53 -0
- data/lib/cvprac/api/inventory.rb +61 -0
- data/lib/cvprac/api/provisioning.rb +119 -0
- data/lib/cvprac/api/task.rb +108 -0
- data/lib/cvprac/client.rb +560 -0
- data/lib/cvprac/client_errors.rb +67 -0
- data/lib/cvprac/version.rb +34 -0
- metadata +433 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# BSD 3-Clause License
|
4
|
+
#
|
5
|
+
# Copyright (c) 2017, Arista Networks EOS+
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
12
|
+
# list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# * Neither the name Arista nor the names of its
|
19
|
+
# contributors may be used to endorse or promote products derived from
|
20
|
+
# this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
25
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
26
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
27
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
28
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
29
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
30
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
31
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
#
|
33
|
+
|
34
|
+
# @author Arista EOS+ Consulting Services <eosplus-dev@arista.com>
|
35
|
+
module Cvprac
|
36
|
+
# Cvprac::Api namespace
|
37
|
+
module Api
|
38
|
+
# CVP Info api methods
|
39
|
+
module Info
|
40
|
+
# rubocop:disable Style/AccessorMethodName
|
41
|
+
# @!group Info Method Summary
|
42
|
+
|
43
|
+
# Get CVP version information
|
44
|
+
#
|
45
|
+
# @return [Hash] CVP Version data. Ex: {"version"=>"2016.1.1"}
|
46
|
+
def get_cvp_info
|
47
|
+
@clnt.get('/cvpInfo/getCvpInfo.do')
|
48
|
+
# return @clnt.get('/cvpInfo/getCvpInfo.do',
|
49
|
+
# timeout: @request_timeout)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# BSD 3-Clause License
|
4
|
+
#
|
5
|
+
# Copyright (c) 2017, Arista Networks EOS+
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
12
|
+
# list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# * Neither the name Arista nor the names of its
|
19
|
+
# contributors may be used to endorse or promote products derived from
|
20
|
+
# this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
25
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
26
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
27
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
28
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
29
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
30
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
31
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
#
|
33
|
+
|
34
|
+
# @author Arista EOS+ Consulting Services <eosplus-dev@arista.com>
|
35
|
+
module Cvprac
|
36
|
+
# Cvprac::Api namespace
|
37
|
+
module Api
|
38
|
+
# CVP Inventory api methods
|
39
|
+
module Inventory
|
40
|
+
# @!group Inventory Method Summary
|
41
|
+
|
42
|
+
# Get device (NetElement) by name (fqdn)
|
43
|
+
#
|
44
|
+
# @param [String] fqdn The FQDN (name) of the desired device
|
45
|
+
#
|
46
|
+
# @return [Hash] CVP NetElement data.
|
47
|
+
def get_device_by_name(fqdn)
|
48
|
+
log(Logger::DEBUG) { "get_device_by_name: #{fqdn}" }
|
49
|
+
res = @clnt.get('/inventory/getInventory.do',
|
50
|
+
data: { queryparam: fqdn,
|
51
|
+
startIndex: 0,
|
52
|
+
endIndex: 0 })
|
53
|
+
return {} if res['netElementList'].length.zero?
|
54
|
+
res['netElementList'].each do |element|
|
55
|
+
return element if element['fqdn'] == fqdn
|
56
|
+
end
|
57
|
+
{}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# BSD 3-Clause License
|
4
|
+
#
|
5
|
+
# Copyright (c) 2017, Arista Networks EOS+
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
12
|
+
# list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# * Neither the name Arista nor the names of its
|
19
|
+
# contributors may be used to endorse or promote products derived from
|
20
|
+
# this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
25
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
26
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
27
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
28
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
29
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
30
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
31
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
#
|
33
|
+
|
34
|
+
# @author Arista EOS+ Consulting Services <eosplus-dev@arista.com>
|
35
|
+
module Cvprac
|
36
|
+
# Cvprac::Api namespace
|
37
|
+
module Api
|
38
|
+
# CVP provisioning api methods
|
39
|
+
module Provisioning
|
40
|
+
# @!group Provisioning Method Summary
|
41
|
+
|
42
|
+
# Get configlets by device ID
|
43
|
+
#
|
44
|
+
# @param [String] sys_mac The netElementId (System MAC) of the device
|
45
|
+
# @param opts [Hash] Optional arguments
|
46
|
+
# @option opts [String] :queryparam Search string
|
47
|
+
# @option opts [Fixnum] :start_index (0) Start index for pagination
|
48
|
+
# @option opts [Fixnum] :end_index (0) End index for pagination
|
49
|
+
#
|
50
|
+
# @return [Array] List of configlets applied to the device
|
51
|
+
# rubocop:disable Metrics/MethodLength
|
52
|
+
def get_configlets_by_device_id(sys_mac, **opts)
|
53
|
+
opts = { queryparam: nil,
|
54
|
+
start_index: 0,
|
55
|
+
end_index: 0 }.merge(opts)
|
56
|
+
log(Logger::DEBUG) do
|
57
|
+
"get_configlets_by_device_id: #{sys_mac} with query: #{opts.inspect}"
|
58
|
+
end
|
59
|
+
res = @clnt.get('/provisioning/getConfigletsByNetElementId.do',
|
60
|
+
data: { netElementId: sys_mac,
|
61
|
+
queryParam: opts[:queryparam],
|
62
|
+
startIndex: opts[:start_index],
|
63
|
+
endIndex: opts[:end_index] })
|
64
|
+
res['configletList']
|
65
|
+
end
|
66
|
+
# rubocop:enable Metrics/MethodLength
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Add a temp action. Requires a save_topology_v2() call to take effect.
|
71
|
+
#
|
72
|
+
# @param [Hash] data the data object to process
|
73
|
+
# Ex: data = {'data': [{specific key/value pairs}]}
|
74
|
+
#
|
75
|
+
# @return [Hash] A Topology hash
|
76
|
+
# rubocop:disable Metrics/MethodLength
|
77
|
+
def add_temp_action(data)
|
78
|
+
log(Logger::DEBUG) do
|
79
|
+
"#{__method__}: #{data.inspect}"
|
80
|
+
end
|
81
|
+
resp = @clnt.post('/provisioning/addTempAction.do',
|
82
|
+
data: { format: 'topology',
|
83
|
+
queryParam: nil,
|
84
|
+
nodeId: 'root' },
|
85
|
+
body: data)
|
86
|
+
log(Logger::DEBUG) do
|
87
|
+
"#{__method__}: response #{resp.inspect}"
|
88
|
+
end
|
89
|
+
resp
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/MethodLength
|
92
|
+
|
93
|
+
# Commits a temp action. See #add_temp_action
|
94
|
+
#
|
95
|
+
# @param [Array] data a list that contains a dict with a specific
|
96
|
+
# format for the desired action. Our primary use case is for
|
97
|
+
# confirming existing temp actions so we most often send an
|
98
|
+
# empty list to confirm an existing temp action.
|
99
|
+
# Example:
|
100
|
+
#
|
101
|
+
# @return [Hash] Contains a status and a list of task ids created,
|
102
|
+
# if any.
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# => {u'data': {u'status': u'success', u'taskIds': []}}
|
106
|
+
def save_topology_v2(data)
|
107
|
+
log(Logger::DEBUG) do
|
108
|
+
"#{__method__}: #{data.inspect}"
|
109
|
+
end
|
110
|
+
resp = @clnt.post('/provisioning/v2/saveTopology.do',
|
111
|
+
body: data)
|
112
|
+
log(Logger::DEBUG) do
|
113
|
+
"#{__method__}: response #{resp.inspect}"
|
114
|
+
end
|
115
|
+
resp
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# BSD 3-Clause License
|
4
|
+
#
|
5
|
+
# Copyright (c) 2017, Arista Networks EOS+
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
12
|
+
# list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# * Neither the name Arista nor the names of its
|
19
|
+
# contributors may be used to endorse or promote products derived from
|
20
|
+
# this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
25
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
26
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
27
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
28
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
29
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
30
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
31
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
#
|
33
|
+
|
34
|
+
# @author Arista EOS+ Consulting Services <eosplus-dev@arista.com>
|
35
|
+
module Cvprac
|
36
|
+
# CvpRac::Api namespace
|
37
|
+
module Api
|
38
|
+
# CVP Task api methods
|
39
|
+
module Task
|
40
|
+
# @!group Task Method Summary
|
41
|
+
|
42
|
+
# Get task data by ID
|
43
|
+
#
|
44
|
+
# @param [String] task_id The id of the task to execute
|
45
|
+
#
|
46
|
+
# @return [Hash] request body
|
47
|
+
def get_task_by_id(task_id)
|
48
|
+
log(Logger::DEBUG) { "#{__method__}: task_id: #{task_id}" }
|
49
|
+
begin
|
50
|
+
task = @clnt.get('/task/getTaskById.do', data: { taskId: task_id })
|
51
|
+
rescue CvpApiError => e
|
52
|
+
if e.to_s.include?('Invalid WorkOrderId') ||
|
53
|
+
e.to_s.include?('Entity does not exist')
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
task
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get task data by device name (FQDN)
|
61
|
+
#
|
62
|
+
# @param [String] device Name (FQDN) of a device
|
63
|
+
#
|
64
|
+
# @return [Hash] request body
|
65
|
+
# rubocop:disable Metrics/MethodLength
|
66
|
+
def get_pending_tasks_by_device(device)
|
67
|
+
log(Logger::DEBUG) { "#{__method__}: device: #{device}" }
|
68
|
+
begin
|
69
|
+
task = @clnt.get('/task/getTasks.do', data: { queryparam: 'Pending',
|
70
|
+
startIndex: 0,
|
71
|
+
endIndex: 0 })
|
72
|
+
rescue CvpApiError => e
|
73
|
+
if e.to_s.include?('Invalid WorkOrderId') ||
|
74
|
+
e.to_s.include?('Entity does not exist')
|
75
|
+
return nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# TODO: filter tasks by device
|
79
|
+
task['data']
|
80
|
+
end
|
81
|
+
# rubocop:enable Metrics/MethodLength
|
82
|
+
|
83
|
+
# Add note to CVP task by taskID
|
84
|
+
#
|
85
|
+
# @param [String] task_id The id of the task to execute
|
86
|
+
# @param [String] note Content of the note
|
87
|
+
#
|
88
|
+
# @return [Hash] request body
|
89
|
+
def add_note_to_task(task_id, note)
|
90
|
+
log(Logger::DEBUG) do
|
91
|
+
"add_note_to_task: task_id: #{task_id}, note: [#{note}]"
|
92
|
+
end
|
93
|
+
@clnt.post('/task/addNoteToTask.do',
|
94
|
+
data: { workOrderId: task_id, note: note })
|
95
|
+
end
|
96
|
+
|
97
|
+
# Execute CVP task by taskID
|
98
|
+
#
|
99
|
+
# @param [String] task_id The id of the task to execute
|
100
|
+
#
|
101
|
+
# @return [Hash] request body
|
102
|
+
def execute_task(task_id)
|
103
|
+
log(Logger::DEBUG) { "execute_task: task_id: #{task_id}" }
|
104
|
+
@clnt.post('/task/executeTask.do', body: { data: [task_id] })
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,560 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# BSD 3-Clause License
|
4
|
+
#
|
5
|
+
# Copyright (c) 2016, Arista Networks EOS+
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
12
|
+
# list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# * Neither the name Arista nor the names of its
|
19
|
+
# contributors may be used to endorse or promote products derived from
|
20
|
+
# this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
25
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
26
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
27
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
28
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
29
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
30
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
31
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
#
|
33
|
+
# rubocop:disable Metrics/ClassLength
|
34
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
35
|
+
|
36
|
+
require 'cgi'
|
37
|
+
require 'http-cookie'
|
38
|
+
require 'json'
|
39
|
+
require 'logger'
|
40
|
+
require 'net/http'
|
41
|
+
require 'pp'
|
42
|
+
require 'syslog/logger'
|
43
|
+
|
44
|
+
# Provide simplified RESTful methods to access Arista CloudVision Portal
|
45
|
+
#
|
46
|
+
# Establish and maintain connections with Arista CloudVision Portal servers,
|
47
|
+
# providing basic RESTful methods which handle session, cookie, and reconnects
|
48
|
+
# behind the scenes.
|
49
|
+
#
|
50
|
+
# @example Basic usage
|
51
|
+
# require 'cvprac'
|
52
|
+
# cvp = CvpClient.new
|
53
|
+
# cvp.connect(['cvp1', 'cvp2', 'cvp3'], 'cvpadmin', 'arista123')
|
54
|
+
# result = cvp.get('/user/getUsers.do',
|
55
|
+
# data: {queryparam: nil,
|
56
|
+
# startIndex: 0,
|
57
|
+
# endIndex: 0})
|
58
|
+
# pp(result)
|
59
|
+
# {"total"=>1,
|
60
|
+
# "users"=>
|
61
|
+
# [{"userId"=>"cvpadmin",
|
62
|
+
# "firstName"=>nil,
|
63
|
+
# "email"=>"nobody@example.com",
|
64
|
+
# "lastAccessed"=>1483726955950,
|
65
|
+
# "userStatus"=>"Enabled",
|
66
|
+
# "currentStatus"=>"Online",
|
67
|
+
# "contactNumber"=>nil,
|
68
|
+
# "factoryId"=>1,
|
69
|
+
# "lastName"=>nil,
|
70
|
+
# "password"=>nil,
|
71
|
+
# "id"=>28}],
|
72
|
+
# "roles"=>{"cvpadmin"=>["network-admin"]}}
|
73
|
+
#
|
74
|
+
# cvp.post('/test/endpoint.do', body: '{"some":"data"}')
|
75
|
+
#
|
76
|
+
# @author Arista EOS+ Consulting Services <eosplus-dev@arista.com>
|
77
|
+
class CvpClient
|
78
|
+
METHOD_LIST = {
|
79
|
+
get: Net::HTTP::Get,
|
80
|
+
post: Net::HTTP::Post,
|
81
|
+
put: Net::HTTP::Put,
|
82
|
+
head: Net::HTTP::Head,
|
83
|
+
delete: Net::HTTP::Delete
|
84
|
+
}.freeze
|
85
|
+
private_constant :METHOD_LIST
|
86
|
+
|
87
|
+
# Maximum number of times to retry a get or post to the same
|
88
|
+
# CVP node.
|
89
|
+
NUM_RETRY_REQUESTS = 3
|
90
|
+
|
91
|
+
# @!attribute [rw] agent
|
92
|
+
# Agent is the first part of the complete User-Agent
|
93
|
+
# @example User-Agent
|
94
|
+
# "User-agent"=>"cvp_app (x86_64-darwin14) cvprac-rb/0.1.0"
|
95
|
+
# @return [String] Application name included in HTTP User-Agent passed to
|
96
|
+
# CloudVision Portal. (Default: $PROGRAM_NAME) The full User-Agent string
|
97
|
+
# includes the application name, system-OS, and cvprac version
|
98
|
+
# information.
|
99
|
+
# @!attribute [rw] connect_timeout
|
100
|
+
# @return [Fixnum] Max number of seconds before failing an HTTP connect
|
101
|
+
# @!attribute [rw] headers
|
102
|
+
# @return [Hash] HTTP request headers
|
103
|
+
# @!attribute [rw] port
|
104
|
+
# @return [Fixnum] TCP port used for connections
|
105
|
+
# @!attribute [rw] protocol
|
106
|
+
# @return [String] 'http' or 'https'
|
107
|
+
# @!attribute [rw] ssl_verify_mode
|
108
|
+
# OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER
|
109
|
+
# @see http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL.html#module-OpenSSL-label-Peer+Verification
|
110
|
+
# @!attribute [rw] logger.level
|
111
|
+
# logger severity level: Logger::DEBUG < Logger::INFO < Logger::WARN <
|
112
|
+
# Logger::ERROR < Logger::FATAL. This allows the user to increase or
|
113
|
+
# decrease the logging level of the STDOUT log as needed throughout their
|
114
|
+
# application.
|
115
|
+
# @!attribute [rw] api
|
116
|
+
# An instance of CvpApi
|
117
|
+
attr_accessor :agent, :connect_timeout, :headers,
|
118
|
+
:port, :protocol, :ssl_verify_mode, :file_log_level, :api
|
119
|
+
|
120
|
+
# @!attribute [r] cookies
|
121
|
+
# @return [HTTP::CookieJar] HTTP cookies sent with each authenticated
|
122
|
+
# request
|
123
|
+
# @!attribute [r] headers
|
124
|
+
# @return [Hash] HTTP headers sent with each request
|
125
|
+
# @!attribute [r] nodes
|
126
|
+
# @return [Array<String>] List of configured CloudVision Portal nodes
|
127
|
+
attr_reader :cookies, :headers, :nodes
|
128
|
+
|
129
|
+
def file_log_level=(value)
|
130
|
+
@file_log_level = value
|
131
|
+
# Update existing handles if they exist
|
132
|
+
@logstdout.level = @file_log_level if @logstdout.level
|
133
|
+
@logfile.level = @file_log_level if @logfile.level
|
134
|
+
end
|
135
|
+
|
136
|
+
# Initialize a new CvpClient object
|
137
|
+
#
|
138
|
+
# @param opts [Hash] Optional arguments
|
139
|
+
# @option opts [String] :logger ('cvprac') Logging name for this service
|
140
|
+
# @option opts [Bool] :syslog (false) Log to the syslog service?
|
141
|
+
# @option opts [String] :filename (nil) A local logfile to use, if provided
|
142
|
+
# @option opts [Logger::level] :file_log_level (Logger::INFO) The default
|
143
|
+
# logging level which will be recorded in the logs. See the Logging
|
144
|
+
# rubygem for additional severity levels
|
145
|
+
def initialize(**opts)
|
146
|
+
opts = { logger: 'cvprac',
|
147
|
+
syslog: false,
|
148
|
+
filename: nil,
|
149
|
+
file_log_level: Logger::INFO }.merge(opts)
|
150
|
+
@agent = File.basename($PROGRAM_NAME)
|
151
|
+
@agent_full = "#{@agent} (#{RUBY_PLATFORM}) "\
|
152
|
+
"cvprac-rb/#{Cvprac::VERSION}"
|
153
|
+
@authdata = nil
|
154
|
+
@connect_timeout = nil
|
155
|
+
@cookies = HTTP::CookieJar.new
|
156
|
+
@error_msg = nil
|
157
|
+
@file_log_level = opts[:file_log_level]
|
158
|
+
@headers = { 'Accept' => 'application/json',
|
159
|
+
'Content-Type' => 'application/json',
|
160
|
+
'User-agent' => @agent_full }
|
161
|
+
@node_count = nil
|
162
|
+
@node_pool = nil
|
163
|
+
@nodes = nil
|
164
|
+
@port = nil
|
165
|
+
@protocol = nil
|
166
|
+
@session = nil
|
167
|
+
# OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER
|
168
|
+
@ssl_verify_mode = OpenSSL::SSL::VERIFY_NONE
|
169
|
+
@url_prefix = nil
|
170
|
+
|
171
|
+
if opts[:filename] == 'STDOUT'
|
172
|
+
@logstdout = Logger.new(STDOUT)
|
173
|
+
@logstdout.level = @file_log_level
|
174
|
+
else
|
175
|
+
unless opts[:filename].nil?
|
176
|
+
@logfile = Logger.new(opts[:filename])
|
177
|
+
@logfile.level = @file_log_level
|
178
|
+
end
|
179
|
+
end
|
180
|
+
@syslog = Syslog::Logger.new(opts[:filename]) if opts[:syslog]
|
181
|
+
|
182
|
+
# Instantiate the CvpApi class
|
183
|
+
@api = CvpApi.new(self)
|
184
|
+
|
185
|
+
log(Logger::INFO, 'CvpClient initialized')
|
186
|
+
end
|
187
|
+
|
188
|
+
# Log message to all configured loggers
|
189
|
+
#
|
190
|
+
# @overload log(severity: Logger::INFO, msg: nil)
|
191
|
+
# @param severity [Logger] Severity to log to:
|
192
|
+
# DEBUG < INFO < WARN < ERROR < FATAL
|
193
|
+
# @param msg [String] Message to log
|
194
|
+
#
|
195
|
+
# @overload log(severity: Logger::INFO)
|
196
|
+
# @param severity [Logger] Severity to log to:
|
197
|
+
# DEBUG < INFO < WARN < ERROR < FATAL
|
198
|
+
# @yield [msg] Messages can be passed as a block to delay evaluation
|
199
|
+
def log(severity = Logger::INFO, msg = nil)
|
200
|
+
msg = yield if block_given?
|
201
|
+
@logstdout.add(severity, msg) if defined? @logstdout
|
202
|
+
@logfile.add(severity, msg) if defined? @logfile
|
203
|
+
@syslog.add(severity, msg) if defined? @syslog
|
204
|
+
end
|
205
|
+
|
206
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
207
|
+
|
208
|
+
# Connect to one or more CVP nodes.
|
209
|
+
#
|
210
|
+
# @param nodes [Array] Hostnames or IPs of the CVP node or nodes
|
211
|
+
# @param username [String] CVP username
|
212
|
+
# @param password [String] CVP password
|
213
|
+
#
|
214
|
+
# @param opts [Hash] Optional arguments
|
215
|
+
# @option opts [Fixnum] :connect_timeout (10) Seconds to wait before failing
|
216
|
+
# a connect. Default: 10
|
217
|
+
# @option opts [String] :protocol ('https') 'http' or 'https' to use when
|
218
|
+
# connecting to the CVP. Default: https
|
219
|
+
# @option opts [Fixnum] :port (nil) TCP port to which we should connect is
|
220
|
+
# not standard http/https port.
|
221
|
+
# @option opts [Bool] :verify_ssl (false) Verify CVP SSL certificate?
|
222
|
+
# Requires that a valid (non-self-signed) certificate be installed on the
|
223
|
+
# CloudVision Portal node(s).
|
224
|
+
def connect(nodes, username, password, **opts)
|
225
|
+
opts = { connect_timeout: 10,
|
226
|
+
protocol: 'https',
|
227
|
+
port: nil,
|
228
|
+
verify_ssl: false }.merge(opts)
|
229
|
+
connect_timeout = opts[:connect_timeout]
|
230
|
+
protocol = opts[:protocol]
|
231
|
+
port = opts[:port]
|
232
|
+
|
233
|
+
@nodes = Array(nodes) # Ensure nodes is always an array
|
234
|
+
@node_index = 0
|
235
|
+
@node_count = nodes.length
|
236
|
+
@node_last = @node_count - 1
|
237
|
+
@node_pool = Enumerator.new do |y|
|
238
|
+
loop do
|
239
|
+
index = @node_index % @node_count
|
240
|
+
if @node_index == @node_last
|
241
|
+
@node_index = 0
|
242
|
+
else
|
243
|
+
@node_index += 1
|
244
|
+
end
|
245
|
+
y.yield @nodes[index]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
@authdata = { userId: username, password: password }
|
249
|
+
@connect_timeout = connect_timeout
|
250
|
+
@protocol = protocol
|
251
|
+
|
252
|
+
if port.nil?
|
253
|
+
if protocol == 'http'
|
254
|
+
port = 80
|
255
|
+
elsif protocol == 'https'
|
256
|
+
port = 443
|
257
|
+
else
|
258
|
+
raise ArgumentError, "No default port for protocol: #{protocol}"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
@port = port
|
262
|
+
|
263
|
+
@ssl_verify_mode = if opts[:verify_ssl]
|
264
|
+
OpenSSL::SSL::VERIFY_PEER
|
265
|
+
else
|
266
|
+
OpenSSL::SSL::VERIFY_NONE
|
267
|
+
end
|
268
|
+
|
269
|
+
create_session(nil)
|
270
|
+
raise CvpLoginError, @error_msg unless @session
|
271
|
+
end
|
272
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
273
|
+
|
274
|
+
# @!group RESTful methods
|
275
|
+
|
276
|
+
# Send an HTTP GET request with session data and return the response.
|
277
|
+
#
|
278
|
+
# @param endpoint [String] URL endpoint starting after `https://host:port/web`
|
279
|
+
#
|
280
|
+
# @param [Hash] opts Optional parameters
|
281
|
+
# @option opts [Hash] :data (nil) query parameters
|
282
|
+
#
|
283
|
+
# @return [JSON] parsed response body
|
284
|
+
def get(endpoint, **opts)
|
285
|
+
data = opts.key?(:data) ? opts[:data] : nil
|
286
|
+
make_request(:get, endpoint, data: data)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Send an HTTP POST request with session data and return the response.
|
290
|
+
#
|
291
|
+
# @param endpoint [String] URL endpoint starting after `https://host:port/web`
|
292
|
+
#
|
293
|
+
# @param [Hash] opts Optional parameters
|
294
|
+
# @option opts [JSON] :body (nil) JSON body to post
|
295
|
+
# @option opts [Hash] :data (nil) query parameters
|
296
|
+
# @return [Net::HTTP Response]
|
297
|
+
def post(endpoint, **opts)
|
298
|
+
data = opts.key?(:data) ? opts[:data] : nil
|
299
|
+
body = opts.key?(:body) ? opts[:body] : nil
|
300
|
+
make_request(:post, endpoint, data: data, body: body)
|
301
|
+
end
|
302
|
+
|
303
|
+
# @!endgroup RESTful methods
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
# Send an HTTP request with session data and return the response.
|
308
|
+
#
|
309
|
+
# @param method [Symbol] Reuqest method: :get, :post, :head, etc.
|
310
|
+
# @param endpoint [String] URI path to the endpoint after /web/
|
311
|
+
#
|
312
|
+
# @param opts [Hash] Optional arguments
|
313
|
+
# @option opts [JSON] :body JSON body to post
|
314
|
+
# @option opts [Hash] :data query parameters
|
315
|
+
# @option opts [Fixnum] :timeout (30) Seconds to timeout request.
|
316
|
+
#
|
317
|
+
# @return [JSON] parsed response body
|
318
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
319
|
+
def make_request(method, endpoint, **opts)
|
320
|
+
opts = { data: nil, body: nil, timeout: 30 }.merge(opts)
|
321
|
+
log(Logger::DEBUG) do
|
322
|
+
"entering make_request #{method} "\
|
323
|
+
"endpoint: #{endpoint}"\
|
324
|
+
" with query: #{opts[:data].inspect}" \
|
325
|
+
" with body: #{opts[:body].inspect}"
|
326
|
+
end
|
327
|
+
raise 'No valid session to a CVP node. Use #connect()' unless @session
|
328
|
+
|
329
|
+
# Ensure body is valid JSON
|
330
|
+
if opts[:body]
|
331
|
+
case opts[:body]
|
332
|
+
when String
|
333
|
+
JSON.parse(opts[:body])
|
334
|
+
when Hash, Array
|
335
|
+
opts[:body] = opts[:body].to_json
|
336
|
+
else
|
337
|
+
raise ArgumentError, "Unable to coerce body to JSON: #{opts[:body]}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
url = @url_prefix + endpoint
|
342
|
+
uri = URI(url)
|
343
|
+
uri.query = URI.encode_www_form(opts[:data]) if opts[:data]
|
344
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
345
|
+
http.read_timeout = opts[:timeout]
|
346
|
+
if @protocol == 'https'
|
347
|
+
http.use_ssl = true
|
348
|
+
http.verify_mode = @ssl_verify_mode
|
349
|
+
end
|
350
|
+
|
351
|
+
error = nil
|
352
|
+
retry_count = NUM_RETRY_REQUESTS
|
353
|
+
node_count = @node_count
|
354
|
+
while node_count > 0
|
355
|
+
unless error.nil?
|
356
|
+
log(Logger::DEBUG) { "make_request: error not nil: #{error}" }
|
357
|
+
node_count -= 1
|
358
|
+
raise error if node_count.zero?
|
359
|
+
create_session
|
360
|
+
|
361
|
+
raise error unless @session
|
362
|
+
retry_count = NUM_RETRY_REQUESTS
|
363
|
+
error = nil
|
364
|
+
end
|
365
|
+
|
366
|
+
begin
|
367
|
+
log(Logger::DEBUG) { 'make_request: ' + uri.request_uri }
|
368
|
+
request = METHOD_LIST[method].new(uri.request_uri, @headers)
|
369
|
+
request.body = opts[:body] if opts[:body]
|
370
|
+
response = http.request(request)
|
371
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
372
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
373
|
+
Net::ProtocolError => error
|
374
|
+
log(Logger::ERROR) { "Request failed: #{error}" }
|
375
|
+
raise CvpRequestError, error
|
376
|
+
rescue => error
|
377
|
+
log(Logger::ERROR) { "UnknownError: #{error}" }
|
378
|
+
raise error
|
379
|
+
end
|
380
|
+
log(Logger::DEBUG) { 'Request succeeded. Checking response...' }
|
381
|
+
|
382
|
+
begin
|
383
|
+
good_response?(response, "#{method} #{uri.request_uri}:")
|
384
|
+
rescue CvpSessionLogOutError => error
|
385
|
+
log(Logger::DEBUG) { "Session logged out: #{error}" }
|
386
|
+
retry_count -= 1
|
387
|
+
if retry_count > 0
|
388
|
+
log(Logger::DEBUG) do
|
389
|
+
'Session logged out... resetting and retrying '\
|
390
|
+
"#{error}"
|
391
|
+
end
|
392
|
+
reset_session
|
393
|
+
error = nil if @session # rubocop:disable Metrics/BlockNesting
|
394
|
+
else
|
395
|
+
msg = 'Session logged out. Failed to re-login. '\
|
396
|
+
"No more retries: #{error}"
|
397
|
+
log(Logger::ERROR) { msg }
|
398
|
+
raise CvpSessionLogOutError, msg
|
399
|
+
end
|
400
|
+
next
|
401
|
+
end
|
402
|
+
log(Logger::DEBUG) { 'make_request completed.' }
|
403
|
+
break
|
404
|
+
end
|
405
|
+
|
406
|
+
response.body ? JSON.parse(response.body) : nil
|
407
|
+
end
|
408
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
409
|
+
|
410
|
+
# Login to CVP and get a session ID and user information.
|
411
|
+
# If the all_nodes parameter is True then try creating a session
|
412
|
+
# with each CVP node. If False, then try creating a session with
|
413
|
+
# each node except the one currently connected to.
|
414
|
+
#
|
415
|
+
# @param all_nodes [Bool] Establish a session with each node or just one
|
416
|
+
def create_session(all_nodes = nil)
|
417
|
+
node_count = @node_count
|
418
|
+
node_count -= 1 if all_nodes.nil? && node_count > 1
|
419
|
+
|
420
|
+
@error_msg = '\n'
|
421
|
+
(0...node_count).each do
|
422
|
+
host = @node_pool.next
|
423
|
+
@url_prefix = "#{@protocol}://#{host}:#{@port}/web"
|
424
|
+
@http = Net::HTTP.new(host, @port)
|
425
|
+
if @protocol == 'https'
|
426
|
+
@http.use_ssl = true
|
427
|
+
@http.verify_mode = @ssl_verify_mode
|
428
|
+
end
|
429
|
+
error = reset_session
|
430
|
+
break if error.nil?
|
431
|
+
@error_msg += "#{host}: #{error}\n"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# Get a new request session and try logging into the current
|
436
|
+
# CVP node. If the login succeeded None will be returned and
|
437
|
+
# @session will be valid. If the login failed then an
|
438
|
+
# exception error will be returned and @session will
|
439
|
+
# be set to None.
|
440
|
+
#
|
441
|
+
# @return [String] nil on success or errors encountered
|
442
|
+
def reset_session
|
443
|
+
@session = nil
|
444
|
+
error = nil
|
445
|
+
|
446
|
+
begin
|
447
|
+
login
|
448
|
+
rescue CvpApiError, CvpRequestError, CvpSessionLogOutError => error
|
449
|
+
log(Logger::ERROR) { error }
|
450
|
+
# Invalidate the session due to error
|
451
|
+
@session = nil
|
452
|
+
end
|
453
|
+
error
|
454
|
+
end
|
455
|
+
|
456
|
+
# Check the response from Net::HTTP
|
457
|
+
# If the response is not good data, generate a useful log message, then
|
458
|
+
# raise an appropriate exception.
|
459
|
+
#
|
460
|
+
# @param response [Net::HTTP response object]
|
461
|
+
# @param prefix [String] Optional text to prepend to error messages
|
462
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
463
|
+
def good_response?(response, prefix = '')
|
464
|
+
log(Logger::DEBUG) { "response_code: #{response.code}" }
|
465
|
+
log(Logger::DEBUG) { 'response_headers: ' + response.to_hash.to_s }
|
466
|
+
log(Logger::DEBUG) { "response_body: #{response.body}" }
|
467
|
+
if response.respond_to?('reason')
|
468
|
+
log(Logger::DEBUG) { "response_reason: #{response.reason}" }
|
469
|
+
end
|
470
|
+
|
471
|
+
if response.code.to_i == 302
|
472
|
+
msg = "#{prefix} Notice302: session logged out"
|
473
|
+
log(Logger::DEBUG) { msg }
|
474
|
+
raise CvpSessionLogOutError, msg
|
475
|
+
elsif response.code.to_i != 200
|
476
|
+
msg = "#{prefix}: Request Error"
|
477
|
+
if response.code.to_i == 400
|
478
|
+
title = response.body.match(%r{<h1>(.*?)</h1>})[1]
|
479
|
+
msg = "#{prefix}: #{title}" if title
|
480
|
+
end
|
481
|
+
log(Logger::ERROR) { 'ErrorCode: ' + response.code + ' - ' + msg }
|
482
|
+
msg += " Reason: #{response.reason}" if response.respond_to?('reason')
|
483
|
+
raise CvpRequestError.new(response.code, msg)
|
484
|
+
end
|
485
|
+
|
486
|
+
log(Logger::DEBUG) { 'Got a response 200 with a body' }
|
487
|
+
return unless response.body.to_s.include? 'errorCode'
|
488
|
+
|
489
|
+
log(Logger::DEBUG) { 'Body has an errorCode' }
|
490
|
+
body = JSON.parse(response.body)
|
491
|
+
if body['errorCode'] == 'MNF404'
|
492
|
+
msg = 'Invalid endpoint'
|
493
|
+
raise CvpRequestError.new('HTTP Status 404', msg)
|
494
|
+
end
|
495
|
+
if body.key?('errorMessage')
|
496
|
+
err_msg = "errorCode: #{body['errorCode']}: #{body['errorMessage']}"
|
497
|
+
log(Logger::ERROR) { err_msg }
|
498
|
+
else
|
499
|
+
error_list = if body.key?('errors')
|
500
|
+
body['errors']
|
501
|
+
else
|
502
|
+
[body['errorCode']]
|
503
|
+
end
|
504
|
+
err_msg = error_list[0]
|
505
|
+
(1...error_list.length).each do |idx|
|
506
|
+
err_msg += "\n#{error_list[idx]}"
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
msg = "#{prefix}: Request Error: #{err_msg}"
|
511
|
+
log(Logger::ERROR) { msg }
|
512
|
+
raise CvpApiError, msg
|
513
|
+
end
|
514
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
515
|
+
|
516
|
+
# Make a POST request to CVP login authentication.
|
517
|
+
# An error can be raised from the post method call or the
|
518
|
+
# good_response? method call. Any errors raised would be a good
|
519
|
+
# reason not to use this host.
|
520
|
+
#
|
521
|
+
# @raise SomeError
|
522
|
+
def login
|
523
|
+
@headers.delete('APP_SESSION_ID')
|
524
|
+
url = @url_prefix + '/login/authenticate.do'
|
525
|
+
uri = URI(url)
|
526
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
527
|
+
if @protocol == 'https'
|
528
|
+
http.use_ssl = true
|
529
|
+
http.verify_mode = @ssl_verify_mode
|
530
|
+
end
|
531
|
+
|
532
|
+
request = Net::HTTP::Post.new(uri.path, @headers)
|
533
|
+
request.body = @authdata.to_json
|
534
|
+
log(Logger::DEBUG) { 'Sending login POST' }
|
535
|
+
begin
|
536
|
+
response = http.request(request)
|
537
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
538
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
539
|
+
Net::ProtocolError => error
|
540
|
+
log(Logger::ERROR) { 'Login failed: ' + error.to_s }
|
541
|
+
raise CvpLoginError, error.to_s
|
542
|
+
rescue => error
|
543
|
+
log(Logger::ERROR) { 'Login failed UnkReason: ' + error.to_s }
|
544
|
+
raise CvpLoginError, error.to_s
|
545
|
+
end
|
546
|
+
log(Logger::DEBUG) { 'Sent login POST' }
|
547
|
+
|
548
|
+
good_response?(response, 'Authenticate:')
|
549
|
+
log(Logger::DEBUG) { 'login checked response' }
|
550
|
+
|
551
|
+
response.get_fields('Set-Cookie').each do |value|
|
552
|
+
@cookies.parse(value, @url_prefix)
|
553
|
+
end
|
554
|
+
|
555
|
+
body = JSON.parse(response.body)
|
556
|
+
@session = @headers['APP_SESSION_ID'] = body['sessionId']
|
557
|
+
@headers['Cookie'] = HTTP::Cookie.cookie_value(@cookies.cookies)
|
558
|
+
log(Logger::DEBUG) { 'login SUCCESS' }
|
559
|
+
end
|
560
|
+
end
|