cvprac 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|