hrr_rb_netconf 0.1.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 +28 -0
- data/.rspec +3 -0
- data/.travis.yml +32 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE +201 -0
- data/README.md +220 -0
- data/Rakefile +6 -0
- data/demo/server.rb +54 -0
- data/demo/server_over_ssh.rb +97 -0
- data/demo/server_with_session-oriented-database.rb +134 -0
- data/demo/server_with_sessionless-database.rb +81 -0
- data/hrr_rb_netconf.gemspec +28 -0
- data/lib/hrr_rb_netconf.rb +10 -0
- data/lib/hrr_rb_netconf/logger.rb +56 -0
- data/lib/hrr_rb_netconf/server.rb +109 -0
- data/lib/hrr_rb_netconf/server/capabilities.rb +75 -0
- data/lib/hrr_rb_netconf/server/capability.rb +206 -0
- data/lib/hrr_rb_netconf/server/capability/base_1_0.rb +183 -0
- data/lib/hrr_rb_netconf/server/capability/base_1_1.rb +247 -0
- data/lib/hrr_rb_netconf/server/capability/candidate_1_0.rb +34 -0
- data/lib/hrr_rb_netconf/server/capability/confirmed_commit_1_0.rb +24 -0
- data/lib/hrr_rb_netconf/server/capability/confirmed_commit_1_1.rb +24 -0
- data/lib/hrr_rb_netconf/server/capability/rollback_on_error_1_0.rb +16 -0
- data/lib/hrr_rb_netconf/server/capability/startup_1_0.rb +22 -0
- data/lib/hrr_rb_netconf/server/capability/url_1_0.rb +23 -0
- data/lib/hrr_rb_netconf/server/capability/validate_1_0.rb +25 -0
- data/lib/hrr_rb_netconf/server/capability/validate_1_1.rb +25 -0
- data/lib/hrr_rb_netconf/server/capability/writable_running_1_0.rb +17 -0
- data/lib/hrr_rb_netconf/server/capability/xpath_1_0.rb +14 -0
- data/lib/hrr_rb_netconf/server/datastore.rb +30 -0
- data/lib/hrr_rb_netconf/server/datastore/oper_handler.rb +29 -0
- data/lib/hrr_rb_netconf/server/datastore/session.rb +52 -0
- data/lib/hrr_rb_netconf/server/error.rb +52 -0
- data/lib/hrr_rb_netconf/server/error/access_denied.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/bad_attribute.rb +20 -0
- data/lib/hrr_rb_netconf/server/error/bad_element.rb +20 -0
- data/lib/hrr_rb_netconf/server/error/data_exists.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/data_missing.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/in_use.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/invalid_value.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/lock_denied.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/malformed_message.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/missing_attribute.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/missing_element.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/operation_failed.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/operation_not_supported.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/partial_operation.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/resource_denied.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/rollback_failed.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/rpc_errorable.rb +138 -0
- data/lib/hrr_rb_netconf/server/error/too_big.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/unknown_attribute.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/unknown_element.rb +19 -0
- data/lib/hrr_rb_netconf/server/error/unknown_namespace.rb +19 -0
- data/lib/hrr_rb_netconf/server/errors.rb +28 -0
- data/lib/hrr_rb_netconf/server/filter.rb +48 -0
- data/lib/hrr_rb_netconf/server/filter/subtree.rb +135 -0
- data/lib/hrr_rb_netconf/server/filter/xpath.rb +59 -0
- data/lib/hrr_rb_netconf/server/model.rb +123 -0
- data/lib/hrr_rb_netconf/server/model/node.rb +19 -0
- data/lib/hrr_rb_netconf/server/operation.rb +92 -0
- data/lib/hrr_rb_netconf/server/session.rb +177 -0
- data/lib/hrr_rb_netconf/version.rb +6 -0
- metadata +149 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# vim: et ts=2 sw=2
|
3
|
+
|
4
|
+
require 'thread'
|
5
|
+
require 'hrr_rb_netconf/logger'
|
6
|
+
require 'hrr_rb_netconf/server/error'
|
7
|
+
require 'hrr_rb_netconf/server/errors'
|
8
|
+
require 'hrr_rb_netconf/server/datastore'
|
9
|
+
require 'hrr_rb_netconf/server/capabilities'
|
10
|
+
require 'hrr_rb_netconf/server/session'
|
11
|
+
|
12
|
+
module HrrRbNetconf
|
13
|
+
class Server
|
14
|
+
SESSION_ID_UNALLOCATED = "UNALLOCATED"
|
15
|
+
SESSION_ID_MIN = 1
|
16
|
+
SESSION_ID_MAX = 2**32 - 1
|
17
|
+
SESSION_ID_MODULO = SESSION_ID_MAX - SESSION_ID_MIN + 1
|
18
|
+
|
19
|
+
def initialize datastore, capabilities: nil, strict_capabilities: false
|
20
|
+
@logger = Logger.new self.class.name
|
21
|
+
@datastore = datastore
|
22
|
+
@capabilities = capabilities || Capabilities.new
|
23
|
+
@strict_capabilities = strict_capabilities
|
24
|
+
@mutex = Mutex.new
|
25
|
+
@sessions = Hash.new
|
26
|
+
@locks = Hash.new
|
27
|
+
@lock_mutex = Mutex.new
|
28
|
+
@last_session_id = SESSION_ID_MIN - 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def allocate_session_id
|
32
|
+
session_id = (SESSION_ID_MODULO).times.lazy.map{ |idx| (@last_session_id + 1 + idx - SESSION_ID_MIN) % SESSION_ID_MODULO + SESSION_ID_MIN }.reject{ |sid| @sessions.has_key? sid }.first
|
33
|
+
unless session_id
|
34
|
+
@logger.error { "Failed allocating Session ID" }
|
35
|
+
raise "Failed allocating Session ID"
|
36
|
+
end
|
37
|
+
@last_session_id = session_id
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete_session session_id
|
41
|
+
if @sessions.has_key? session_id
|
42
|
+
@sessions.delete session_id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def start_session io
|
47
|
+
@logger.info { "Starting session" }
|
48
|
+
session_id = SESSION_ID_UNALLOCATED
|
49
|
+
begin
|
50
|
+
@mutex.synchronize do
|
51
|
+
session_id = allocate_session_id
|
52
|
+
@logger.info { "Session ID: #{session_id}" }
|
53
|
+
@sessions[session_id] = Session.new self, @capabilities, @datastore, session_id, io, @strict_capabilities
|
54
|
+
end
|
55
|
+
t = Thread.new {
|
56
|
+
@sessions[session_id].start
|
57
|
+
}
|
58
|
+
@logger.info { "Session started: Session ID: #{session_id}" }
|
59
|
+
t.join
|
60
|
+
rescue => e
|
61
|
+
@logger.error { "Session terminated: Session ID: #{session_id}" }
|
62
|
+
raise
|
63
|
+
else
|
64
|
+
@logger.info { "Session closed: Session ID: #{session_id}" }
|
65
|
+
ensure
|
66
|
+
@lock_mutex.synchronize do
|
67
|
+
@locks.delete_if{ |tgt, sid| sid == session_id }
|
68
|
+
end
|
69
|
+
@mutex.synchronize do
|
70
|
+
delete_session session_id
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def close_session session_id
|
76
|
+
@logger.info { "Close session: Session ID: #{session_id}" }
|
77
|
+
@sessions[session_id].close
|
78
|
+
end
|
79
|
+
|
80
|
+
def lock target, session_id
|
81
|
+
@logger.info { "Lock: Target: #{target}, Session ID: #{session_id}" }
|
82
|
+
@lock_mutex.synchronize do
|
83
|
+
if @locks.has_key? target
|
84
|
+
@logger.info { "Lock failed, lock is already held by session-id: #{@locks[target]}" }
|
85
|
+
raise Error['lock-denied'].new('protocol', 'error', info: {'session-id' => @locks[target].to_s}, message: 'Lock failed, lock is already held')
|
86
|
+
else
|
87
|
+
@locks[target] = session_id
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def unlock target, session_id
|
93
|
+
@logger.info { "Unlock: Target: #{target}, Session ID: #{session_id}" }
|
94
|
+
@lock_mutex.synchronize do
|
95
|
+
if @locks.has_key? target
|
96
|
+
if @locks[target] == session_id
|
97
|
+
@locks.delete target
|
98
|
+
else
|
99
|
+
@logger.info { "Unlock failed, lock is held by session-id: #{@locks[target]}" }
|
100
|
+
raise Error['operation-failed'].new('protocol', 'error')
|
101
|
+
end
|
102
|
+
else
|
103
|
+
@logger.info { "Unlock failed, lock is not held" }
|
104
|
+
raise Error['operation-failed'].new('protocol', 'error')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# vim: et ts=2 sw=2
|
3
|
+
|
4
|
+
require 'tsort'
|
5
|
+
require 'hrr_rb_netconf/server/capability'
|
6
|
+
|
7
|
+
module HrrRbNetconf
|
8
|
+
class Server
|
9
|
+
class Capabilities
|
10
|
+
def initialize features=nil, capabilities_h=nil
|
11
|
+
@features = features
|
12
|
+
unless capabilities_h
|
13
|
+
@caps = Capability.list.inject({}){ |a, b| a.merge({b => Capability[b].new}) }
|
14
|
+
else
|
15
|
+
@caps = capabilities_h
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def negotiate remote_capabilities
|
20
|
+
filtered_by_features = @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }
|
21
|
+
capabilities_h = filtered_by_features.values.group_by{ |c| c.keyword }.map{ |k, cs|
|
22
|
+
cs.map{ |c|
|
23
|
+
remote_capabilities.lazy.map{ |rc| c.negotiate rc }.select{ |nc| nc }.first
|
24
|
+
}.compact.max
|
25
|
+
}.compact.inject({}){ |a, c|
|
26
|
+
a.merge({c.uri => c})
|
27
|
+
}
|
28
|
+
features = if @features.nil? then nil else @features.dup end
|
29
|
+
Capabilities.new features, capabilities_h
|
30
|
+
end
|
31
|
+
|
32
|
+
def register_capability name, &blk
|
33
|
+
cap = Capability.new(name)
|
34
|
+
blk.call cap
|
35
|
+
@caps[name] = cap
|
36
|
+
end
|
37
|
+
|
38
|
+
def unregister_capability name
|
39
|
+
@caps.delete name
|
40
|
+
end
|
41
|
+
|
42
|
+
def list_all
|
43
|
+
@caps.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def list_supported
|
47
|
+
@caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }.map{ |k, v| v.id }
|
48
|
+
end
|
49
|
+
|
50
|
+
def list_loadable
|
51
|
+
filtered_by_features = @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }
|
52
|
+
@filtered_by_dependencies = filtered_by_features.select{ |k, v| v.dependencies.all?{ |d| filtered_by_features.has_key? d } }
|
53
|
+
tsort.map{ |k| @filtered_by_dependencies[k].id }
|
54
|
+
end
|
55
|
+
|
56
|
+
def each_loadable
|
57
|
+
filtered_by_features = @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }
|
58
|
+
@filtered_by_dependencies = filtered_by_features.select{ |k, v| v.dependencies.all?{ |d| filtered_by_features.has_key? d } }
|
59
|
+
tsort.each do|k|
|
60
|
+
yield @filtered_by_dependencies[k]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
include TSort
|
65
|
+
|
66
|
+
def tsort_each_node &blk
|
67
|
+
@filtered_by_dependencies.each_key(&blk)
|
68
|
+
end
|
69
|
+
|
70
|
+
def tsort_each_child node, &blk
|
71
|
+
@filtered_by_dependencies[node].dependencies.each(&blk)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# vim: et ts=2 sw=2
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module HrrRbNetconf
|
7
|
+
class Server
|
8
|
+
class Capability
|
9
|
+
@subclass_list = Array.new
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def inherited klass
|
13
|
+
@subclass_list.push klass if @subclass_list
|
14
|
+
end
|
15
|
+
|
16
|
+
def [] key
|
17
|
+
__subclass_list__(__method__).find{ |klass| klass::ID == key }
|
18
|
+
end
|
19
|
+
|
20
|
+
def list
|
21
|
+
__subclass_list__(__method__).map{ |klass| klass::ID }
|
22
|
+
end
|
23
|
+
|
24
|
+
def __subclass_list__ method_name
|
25
|
+
send(:method_missing, method_name) unless @subclass_list
|
26
|
+
@subclass_list
|
27
|
+
end
|
28
|
+
|
29
|
+
private :__subclass_list__
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def oper_procs
|
34
|
+
@oper_procs || {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def oper_proc oper_name, &blk
|
38
|
+
@oper_procs ||= Hash.new
|
39
|
+
@oper_procs[oper_name] = blk
|
40
|
+
end
|
41
|
+
|
42
|
+
def models
|
43
|
+
@models || []
|
44
|
+
end
|
45
|
+
|
46
|
+
def model oper_name, path, stmt=nil, attrs={}
|
47
|
+
@models ||= Array.new
|
48
|
+
@models.push [oper_name, path, stmt, attrs]
|
49
|
+
end
|
50
|
+
|
51
|
+
private :oper_proc, :model
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_accessor :if_features, :dependencies, :queries
|
55
|
+
|
56
|
+
def initialize id=nil
|
57
|
+
@id = id || self.class::ID
|
58
|
+
@queries = (self.class::QUERIES rescue {})
|
59
|
+
@if_features = (self.class::IF_FEATURES rescue [])
|
60
|
+
@dependencies = (self.class::DEPENDENCIES rescue [])
|
61
|
+
@oper_procs = self.class.oper_procs.dup
|
62
|
+
@models = (self.class.models rescue [])
|
63
|
+
@uri_proc = Proc.new { |id| id.split('?').first }
|
64
|
+
@keyword_proc = Proc.new { |id| id.split('?').first.match(/^((?:.+(?:\/|:))+.+)(?:\/|:)(.+)$/)[1] }
|
65
|
+
@version_proc = Proc.new { |id| id.split('?').first.match(/^((?:.+(?:\/|:))+.+)(?:\/|:)(.+)$/)[2] }
|
66
|
+
@decode_queries_proc = Proc.new { |qs_s| URI.decode_www_form(qs_s).inject({}){|a,(k,v)| a.merge({k=>(v.split(','))})} }
|
67
|
+
@encode_queries_proc = Proc.new { |qs_h| URI.encode_www_form(qs_h.map{|k,v| [k, v.join(',')]}) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def oper_procs
|
71
|
+
@oper_procs
|
72
|
+
end
|
73
|
+
|
74
|
+
def oper_proc oper_name, &blk
|
75
|
+
if blk
|
76
|
+
@oper_procs[oper_name] = blk
|
77
|
+
end
|
78
|
+
@oper_procs[oper_name]
|
79
|
+
end
|
80
|
+
|
81
|
+
def models
|
82
|
+
@models
|
83
|
+
end
|
84
|
+
|
85
|
+
def model oper_name, path, stmt=nil, attrs={}
|
86
|
+
@models.push [oper_name, path, stmt, attrs]
|
87
|
+
end
|
88
|
+
|
89
|
+
def id
|
90
|
+
if @queries.empty?
|
91
|
+
@id
|
92
|
+
else
|
93
|
+
[@id, @encode_queries_proc.call(@queries)].join('?')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def uri
|
98
|
+
@uri_proc.call id
|
99
|
+
end
|
100
|
+
|
101
|
+
def keyword
|
102
|
+
@keyword_proc.call id
|
103
|
+
end
|
104
|
+
|
105
|
+
def version
|
106
|
+
@version_proc.call id
|
107
|
+
end
|
108
|
+
|
109
|
+
def uri_proc &blk
|
110
|
+
if blk
|
111
|
+
@uri_proc = blk
|
112
|
+
else
|
113
|
+
raise ArgumentError, "block not given"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def keyword_proc &blk
|
118
|
+
if blk
|
119
|
+
@keyword_proc = blk
|
120
|
+
else
|
121
|
+
raise ArgumentError, "block not given"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def version_proc &blk
|
126
|
+
if blk
|
127
|
+
@version_proc = blk
|
128
|
+
else
|
129
|
+
raise ArgumentError, "block not given"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def decode_queries_proc &blk
|
134
|
+
if blk
|
135
|
+
@decode_queries_proc = blk
|
136
|
+
else
|
137
|
+
raise ArgumentError, "block not given"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def encode_queries_proc &blk
|
142
|
+
if blk
|
143
|
+
@encode_queries_proc = blk
|
144
|
+
else
|
145
|
+
raise ArgumentError, "block not given"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def negotiate other_id
|
150
|
+
other_keyword = @keyword_proc.call(other_id)
|
151
|
+
unless keyword == other_keyword
|
152
|
+
nil
|
153
|
+
else
|
154
|
+
other_version = @version_proc.call(other_id)
|
155
|
+
case version <=> other_version
|
156
|
+
when 0
|
157
|
+
c = self.dup
|
158
|
+
c.queries = negotiate_queries(other_id)
|
159
|
+
c
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def negotiate_queries other_id
|
167
|
+
other_queries = @decode_queries_proc.call((other_id.split('?') + [''])[1])
|
168
|
+
queries.inject({}){ |a, (k, v)|
|
169
|
+
if other_queries.has_key?(k)
|
170
|
+
values = v & other_queries[k]
|
171
|
+
if values.empty?
|
172
|
+
a
|
173
|
+
else
|
174
|
+
a.merge({k => values})
|
175
|
+
end
|
176
|
+
else
|
177
|
+
a
|
178
|
+
end
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
include Comparable
|
183
|
+
|
184
|
+
def <=> other
|
185
|
+
unless keyword == other.keyword
|
186
|
+
nil
|
187
|
+
else
|
188
|
+
version <=> other.version
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
require 'hrr_rb_netconf/server/capability/base_1_0'
|
196
|
+
require 'hrr_rb_netconf/server/capability/base_1_1'
|
197
|
+
require 'hrr_rb_netconf/server/capability/writable_running_1_0'
|
198
|
+
require 'hrr_rb_netconf/server/capability/candidate_1_0'
|
199
|
+
require 'hrr_rb_netconf/server/capability/confirmed_commit_1_0'
|
200
|
+
require 'hrr_rb_netconf/server/capability/confirmed_commit_1_1'
|
201
|
+
require 'hrr_rb_netconf/server/capability/rollback_on_error_1_0'
|
202
|
+
require 'hrr_rb_netconf/server/capability/startup_1_0'
|
203
|
+
require 'hrr_rb_netconf/server/capability/validate_1_0'
|
204
|
+
require 'hrr_rb_netconf/server/capability/validate_1_1'
|
205
|
+
require 'hrr_rb_netconf/server/capability/url_1_0'
|
206
|
+
require 'hrr_rb_netconf/server/capability/xpath_1_0'
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# vim: et ts=2 sw=2
|
3
|
+
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'hrr_rb_netconf/logger'
|
6
|
+
|
7
|
+
module HrrRbNetconf
|
8
|
+
class Server
|
9
|
+
class Capability
|
10
|
+
class Base_1_0 < Capability
|
11
|
+
ID = 'urn:ietf:params:netconf:base:1.0'
|
12
|
+
DEPENDENCIES = []
|
13
|
+
IF_FEATURES = []
|
14
|
+
|
15
|
+
oper_proc('get'){ |session, datastore, input_e|
|
16
|
+
datastore.run 'get', input_e
|
17
|
+
}
|
18
|
+
|
19
|
+
oper_proc('get-config'){ |session, datastore, input_e|
|
20
|
+
datastore.run 'get-config', input_e
|
21
|
+
}
|
22
|
+
|
23
|
+
oper_proc('edit-config'){ |session, datastore, input_e|
|
24
|
+
datastore.run 'edit-config', input_e
|
25
|
+
'<ok />'
|
26
|
+
}
|
27
|
+
|
28
|
+
oper_proc('copy-config'){ |session, datastore, input_e|
|
29
|
+
datastore.run 'copy-config', input_e
|
30
|
+
'<ok />'
|
31
|
+
}
|
32
|
+
|
33
|
+
oper_proc('delete-config'){ |session, datastore, input_e|
|
34
|
+
datastore.run 'delete-config', input_e
|
35
|
+
'<ok />'
|
36
|
+
}
|
37
|
+
|
38
|
+
oper_proc('lock'){ |session, datastore, input_e|
|
39
|
+
target = input_e.elements['target'].elements[1].name
|
40
|
+
session.lock target
|
41
|
+
begin
|
42
|
+
datastore.run 'lock', input_e
|
43
|
+
'<ok />'
|
44
|
+
rescue
|
45
|
+
session.unlock target
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
oper_proc('unlock'){ |session, datastore, input_e|
|
51
|
+
datastore.run 'unlock', input_e
|
52
|
+
target = input_e.elements['target'].elements[1].name
|
53
|
+
session.unlock target
|
54
|
+
'<ok />'
|
55
|
+
}
|
56
|
+
|
57
|
+
oper_proc('close-session'){ |session, datastore, input_e|
|
58
|
+
datastore.run 'close-session', input_e
|
59
|
+
session.close
|
60
|
+
'<ok />'
|
61
|
+
}
|
62
|
+
|
63
|
+
oper_proc('kill-session'){ |session, datastore, input_e|
|
64
|
+
session.close_other Integer(input_e.elements['session-id'].text)
|
65
|
+
'<ok />'
|
66
|
+
}
|
67
|
+
|
68
|
+
model 'get', ['filter'], 'leaf', 'type' => 'anyxml'
|
69
|
+
model 'get-config', ['source'], 'container'
|
70
|
+
model 'get-config', ['source', 'config-source'], 'choice', 'mandatory' => true
|
71
|
+
model 'get-config', ['source', 'config-source', 'running'], 'leaf', 'type' => 'empty'
|
72
|
+
model 'get-config', ['filter'], 'leaf', 'type' => 'anyxml'
|
73
|
+
model 'edit-config', ['target'], 'container'
|
74
|
+
model 'edit-config', ['target', 'config-target'], 'choice', 'mandatory' => true
|
75
|
+
model 'edit-config', ['default-operation'], 'leaf', 'type' => 'enumeration', 'enum' => ['merge', 'replace', 'none'], 'default' => 'merge'
|
76
|
+
model 'edit-config', ['error-option'], 'leaf', 'type' => 'enumeration', 'enum' => ['stop-on-error', 'continue-on-error', 'rollback-on-error'], 'default' => 'stop-on-error'
|
77
|
+
model 'edit-config', ['edit-content'], 'choice', 'mandatory' => true
|
78
|
+
model 'edit-config', ['edit-content', 'config'], 'leaf', 'type' => 'anyxml'
|
79
|
+
model 'copy-config', ['target'], 'container'
|
80
|
+
model 'copy-config', ['target', 'config-target'], 'choice', 'mandatory' => true
|
81
|
+
model 'copy-config', ['source'], 'container'
|
82
|
+
model 'copy-config', ['source', 'config-source'], 'choice', 'mandatory' => true
|
83
|
+
model 'copy-config', ['source', 'config-source', 'running'], 'leaf', 'type' => 'empty'
|
84
|
+
model 'copy-config', ['source', 'config-source', 'config'], 'leaf', 'type' => 'anyxml'
|
85
|
+
model 'delete-config', ['target'], 'container'
|
86
|
+
model 'delete-config', ['target', 'config-target'], 'choice', 'mandatory' => true
|
87
|
+
model 'lock', ['target'], 'container'
|
88
|
+
model 'lock', ['target', 'config-target'], 'choice', 'mandatory' => true
|
89
|
+
model 'lock', ['target', 'config-target', 'running'], 'leaf', 'type' => 'empty'
|
90
|
+
model 'unlock', ['target'], 'container'
|
91
|
+
model 'unlock', ['target', 'config-target'], 'choice', 'mandatory' => true
|
92
|
+
model 'unlock', ['target', 'config-target', 'running'], 'leaf', 'type' => 'empty'
|
93
|
+
model 'close-session', []
|
94
|
+
model 'kill-session', ['session-id'], 'leaf', 'type' => 'integer', 'range' => [1, 2**32-1]
|
95
|
+
|
96
|
+
class Sender
|
97
|
+
def initialize io_w
|
98
|
+
@logger = Logger.new self.class.name
|
99
|
+
@io_w = io_w
|
100
|
+
@formatter = REXML::Formatters::Pretty.new(2)
|
101
|
+
@formatter.compact = true
|
102
|
+
end
|
103
|
+
|
104
|
+
def send_message msg
|
105
|
+
buf = String.new
|
106
|
+
case msg
|
107
|
+
when String
|
108
|
+
begin
|
109
|
+
@formatter.write(REXML::Document.new(msg, {:ignore_whitespace_nodes => :all}).root, buf)
|
110
|
+
rescue => e
|
111
|
+
@logger.error { "Invalid sending message: #{msg.inspect}: #{e.message}" }
|
112
|
+
raise "Invalid sending message: #{msg.inspect}: #{e.message}"
|
113
|
+
end
|
114
|
+
when REXML::Document
|
115
|
+
@formatter.write(msg.root, buf)
|
116
|
+
when REXML::Element
|
117
|
+
@formatter.write(msg, buf)
|
118
|
+
else
|
119
|
+
@logger.error { "Unexpected sending message: #{msg.inspect}" }
|
120
|
+
raise ArgumentError, "Unexpected sending message: #{msg.inspect}"
|
121
|
+
end
|
122
|
+
@logger.debug { "Sending message: #{buf.inspect}" }
|
123
|
+
begin
|
124
|
+
@io_w.write "#{buf}\n]]>]]>\n"
|
125
|
+
rescue => e
|
126
|
+
@logger.info { "Sender IO closed: #{e.class}: #{e.message}" }
|
127
|
+
raise IOError, "Sender IO closed: #{e.class}: #{e.message}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Receiver
|
133
|
+
def initialize io_r
|
134
|
+
@logger = Logger.new self.class.name
|
135
|
+
@io_r = io_r
|
136
|
+
end
|
137
|
+
|
138
|
+
def receive_message
|
139
|
+
buf = String.new
|
140
|
+
loop do
|
141
|
+
begin
|
142
|
+
tmp = @io_r.read(1)
|
143
|
+
rescue => e
|
144
|
+
@logger.info { "Receiver IO closed: #{e.class}: #{e.message}" }
|
145
|
+
return nil
|
146
|
+
end
|
147
|
+
if tmp.nil?
|
148
|
+
@logger.info { "Receiver IO closed" }
|
149
|
+
return nil
|
150
|
+
end
|
151
|
+
buf += tmp
|
152
|
+
if buf[-6..-1] == ']]>]]>'
|
153
|
+
break
|
154
|
+
end
|
155
|
+
end
|
156
|
+
@logger.debug { "Received message: #{buf[0..-7].inspect}" }
|
157
|
+
begin
|
158
|
+
received_msg = REXML::Document.new(buf[0..-7], {:ignore_whitespace_nodes => :all}).root
|
159
|
+
validate_received_msg received_msg
|
160
|
+
received_msg
|
161
|
+
rescue => e
|
162
|
+
info = "Invalid received message: #{e.message.split("\n").first}: #{buf[0..-7].inspect}"
|
163
|
+
@logger.info { info }
|
164
|
+
raise info
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def validate_received_msg received_msg
|
169
|
+
unless received_msg
|
170
|
+
raise "No valid root tag interpreted"
|
171
|
+
end
|
172
|
+
unless "rpc" == received_msg.name
|
173
|
+
raise "Invalid message: expected #{"rpc".inspect}, but got #{received_msg.name.inspect}"
|
174
|
+
end
|
175
|
+
unless "urn:ietf:params:xml:ns:netconf:base:1.0" == received_msg.namespace
|
176
|
+
raise "Invalid namespace: expected #{"urn:ietf:params:xml:ns:netconf:base:1.0".inspect}, but got #{received_msg.namespace.inspect}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|