fluent-plugin-pi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 27e54a4517d037bd4d94bfd82de9cad602b64762
4
+ data.tar.gz: b97da9892c3d3ff312832f8cb92b65a47f8eb147
5
+ SHA512:
6
+ metadata.gz: 63b04f2f4715f67f5135f99bdd344e936634fa94464ab9ed9d0f9f647dea234b3b57af2e583227dc11d63b5f2308af708336b5e66ddfa681e7e19d446fbf06b4
7
+ data.tar.gz: 6930855b64fca1bc4287e4ebd8d051a732b6e7dcab934647503869a7a813cd92c9802c78361c6371cc982e966145b6cfebd7d4bf8bd7bdf2b7fdfd7af4be3ea8
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at dendres@osisoft.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-pi.gemspec
4
+ gemspec
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fluent-plugin-pi (0.0.1)
5
+ adal (~> 1.0.0)
6
+ certified (~> 1.0.0)
7
+ rest-client (~> 1.6.9)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ adal (1.0.0)
13
+ jwt (~> 1.5)
14
+ nokogiri (~> 1.6)
15
+ uri_template (~> 0.7)
16
+ certified (1.0.0)
17
+ jwt (1.5.6)
18
+ mime-types (1.25.1)
19
+ mini_portile2 (2.1.0)
20
+ nokogiri (1.6.8-x86-mingw32)
21
+ mini_portile2 (~> 2.1.0)
22
+ pkg-config (~> 1.1.7)
23
+ pkg-config (1.1.7)
24
+ rake (10.4.2)
25
+ rest-client (1.6.9)
26
+ mime-types (~> 1.16)
27
+ uri_template (0.7.0)
28
+
29
+ PLATFORMS
30
+ x86-mingw32
31
+
32
+ DEPENDENCIES
33
+ bundler (~> 1.13)
34
+ fluent-plugin-pi!
35
+ rake (~> 10.0)
36
+
37
+ BUNDLED WITH
38
+ 1.13.2
@@ -0,0 +1,36 @@
1
+ # Fluent::Plugin::Pi
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/fluent/plugin/pi`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'fluent-plugin-pi'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install fluent-plugin-pi
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/fluent-plugin-pi. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "fluent-plugin-pi"
6
+ spec.version = "0.0.1"
7
+ spec.authors = ["Derek Endres"]
8
+ spec.email = ["dendres@osisoft.com"]
9
+
10
+ spec.summary = %q{Fluentd plugin to either get data from PI, send to PI or send to QI.}
11
+ spec.description = %q{Fluentd plugin to either get data from PI, send to PI or send to QI}
12
+ spec.homepage = "https://github.com/osisoftresearch"
13
+ spec.license = "OSISoft LLC"
14
+
15
+ root_path = File.dirname(__FILE__)
16
+ spec.files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rest-client", "~> 1.6.9"
22
+ spec.add_dependency "adal", "~> 1.0.0"
23
+ spec.add_dependency "certified", "~> 1.0.0"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.13"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
@@ -0,0 +1,101 @@
1
+ # Copyright (c) 2013-2016 OSIsoft, LLC. All rights reserved.
2
+ #
3
+ # THIS SOFTWARE CONTAINS CONFIDENTIAL INFORMATION AND TRADE SECRETS OF
4
+ # OSIsoft, LLC. USE, DISCLOSURE, OR REPRODUCTION IS PROHIBITED WITHOUT
5
+ # THE PRIOR EXPRESS WRITTEN PERMISSION OF OSIsoft, LLC.
6
+ #
7
+ # RESTRICTED RIGHTS LEGEND
8
+ # Use, duplication, or disclosure by the Government is subject to restrictions
9
+ # as set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and
10
+ # Computer Software clause at DFARS 252.227.7013
11
+ #
12
+ # OSIsoft, LLC
13
+ # 777 Davis Street, Suite 250, San Leandro CA 94577
14
+
15
+ require 'fluent/input'
16
+ require 'rest-client'
17
+ require 'json'
18
+ require 'time'
19
+
20
+ module Fluent
21
+ class Piin < Input
22
+ # First, register the plugin. NAME is the name of this plugin
23
+ # and identifies the plugin in the configuration file.
24
+ Fluent::Plugin.register_input('piin', self)
25
+
26
+ stop_flag = false
27
+ webid = nil
28
+ piwebapiurl = 'localhost', tagpath = nil
29
+ tag = nil
30
+ regetrate = 10
31
+
32
+ # This method is called before starting.
33
+ # 'conf' is a Hash that includes configuration parameters.
34
+ # If the configuration is invalid, raise Fluent::ConfigError.
35
+ def configure(conf)
36
+ super
37
+ @piwebapiurl = conf['piwebapiurl']
38
+ (conf.key?('tagpath')) ? @tagpath = conf['tagpath'] : @tagpath = nil
39
+ (conf.key?('webid')) ? @webid = conf['webid'] : @webid = nil
40
+ (conf.key?('username')) ? @username = conf['username'] : @username = nil
41
+ (conf.key?('pwd')) ? @pwd = conf['pwd'] : @pwd = nil
42
+ (conf.key?('anon')) ? @anon = conf['anon'] : @anon = false
43
+ (conf.key?('tag')) ? @tag = conf['tag'] : @tag = 'piin'
44
+ (conf.key?('regetrate')) ? @regetrate = conf['regetrate'] : @regetrate = 10
45
+ end
46
+
47
+ # This method is called when starting.
48
+ # Open sockets or files and create a thread here.
49
+ def start
50
+ super
51
+
52
+ if(@webid == nil)
53
+ url = "https://#{@piwebapiurl}/piwebapi/points/?path=#{@tagpath}"
54
+ begin
55
+ client = RestClient::Resource.new(url, :verify_ssl => OpenSSL::SSL::VERIFY_NONE, :user => @username, :password =>@pwd)
56
+ response = client.get
57
+ @webid = (JSON.parse(response.body))['WebId']
58
+ rescue
59
+ log.warn "Errors. Web Id not found. Not able to bring in data from the specified address"
60
+ log.warn url
61
+ @webid = nil
62
+ end
63
+ end
64
+
65
+ @stop_flag = false
66
+ @thread = Thread.new(&method(:thread_main))
67
+ end
68
+
69
+ # This method is called when shutting down.
70
+ # Shutdown the thread and close sockets or files here.
71
+ def shutdown
72
+ super
73
+ @stop_flag = true
74
+ $log.debug "Waiting for thread to finish"
75
+ @thread.join
76
+ end
77
+
78
+ def thread_main
79
+ if(@webid == nil)
80
+ return
81
+ end
82
+ url = "https://#{@piwebapiurl}/piwebapi/streams/#{@webid}/value"
83
+ client = RestClient::Resource.new(url, :verify_ssl => OpenSSL::SSL::VERIFY_NONE,:user => @username, :password =>@pwd)
84
+ until @stop_flag
85
+ sleep @regetrate
86
+ begin
87
+ response = client.get :content_type => 'application/json'
88
+ responseObj = JSON.parse(response)
89
+ time = Time.parse(responseObj['Timestamp'])
90
+ if (time == nil)
91
+ time = Engine.now
92
+ end
93
+ router.emit(@tag, time, responseObj)
94
+ rescue => e
95
+ log.warn "Having problems connecting to PI WebAPI. Will try again."
96
+ end
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,150 @@
1
+ # Copyright (c) 2013-2016 OSIsoft, LLC. All rights reserved.
2
+ #
3
+ # THIS SOFTWARE CONTAINS CONFIDENTIAL INFORMATION AND TRADE SECRETS OF
4
+ # OSIsoft, LLC. USE, DISCLOSURE, OR REPRODUCTION IS PROHIBITED WITHOUT
5
+ # THE PRIOR EXPRESS WRITTEN PERMISSION OF OSIsoft, LLC.
6
+ #
7
+ # RESTRICTED RIGHTS LEGEND
8
+ # Use, duplication, or disclosure by the Government is subject to restrictions
9
+ # as set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and
10
+ # Computer Software clause at DFARS 252.227.7013
11
+ #
12
+ # OSIsoft, LLC
13
+ # 777 Davis Street, Suite 250, San Leandro CA 94577
14
+
15
+ require 'fluent/plugin/output'
16
+ require 'json'
17
+ require 'rest-client'
18
+
19
+ module Fluent
20
+ class Pi < BufferedOutput
21
+ Fluent::Plugin.register_output('pi', self)
22
+
23
+ helpers :inject, :formatter, :compat_parameters
24
+
25
+ piwebapiserver = 'localhost', tagpath = nil
26
+ valuetag = 'value'
27
+ username = nil, pwd = nil, anon = false
28
+ webid = nil
29
+
30
+ DEFAULT_FORMAT_TYPE = 'json'
31
+
32
+ config_section :buffer do
33
+ config_set_default :chunk_keys, ['tag']
34
+ config_set_default :flush_at_shutdown, true
35
+ config_set_default :chunk_limit_size, 10 * 1024
36
+ end
37
+
38
+ config_section :format do
39
+ config_set_default :@type, DEFAULT_FORMAT_TYPE
40
+ end
41
+
42
+ def prefer_buffered_processing
43
+ false
44
+ end
45
+
46
+ def prefer_delayed_commit
47
+ @delayed
48
+ end
49
+
50
+ attr_accessor :delayed
51
+
52
+ def initialize
53
+ super
54
+ @delayed = false
55
+ end
56
+
57
+ def configure(conf)
58
+ if conf['output_type'] && !conf['format']
59
+ conf['format'] = conf['output_type']
60
+ end
61
+ compat_parameters_convert(conf, :inject, :formatter)
62
+
63
+ super
64
+
65
+ @formatter = formatter_create(conf: conf.elements('format').first, default_type: DEFAULT_FORMAT_TYPE)
66
+
67
+ #set piwebapi URI information
68
+ @piwebapiserver = conf['piwebapiserver']
69
+
70
+ (conf.key?('tagpath')) ? @tagpath = conf['tagpath'] : @tagpath = nil
71
+ (conf.key?('attpath')) ? @attpath = conf['attpath'] : @attpath = nil
72
+
73
+ if(@tagpath == nil && @attpath == nil)
74
+ $log.write("Neither a tag nor attribute is specified. Unable to log to PI.\n")
75
+ return
76
+ end
77
+
78
+ if(@tagpath != nil && @attpath != nil)
79
+ $log.write("Both a tag and attribute is specified. Using the attribute.\n")
80
+ end
81
+
82
+ (conf.key?('valuetag')) ? @valuetag = conf['valuetag'] : @valuetag = 'Value'
83
+ (conf.key?('username')) ? @username = conf['username'] : @username = nil
84
+ (conf.key?('pwd')) ? @pwd = conf['pwd'] : @pwd = nil
85
+ (conf.key?('anon')) ? @anon = conf['anon'] : @anon = false
86
+
87
+ (conf.key?('webid')) ? @webid = conf['webid'] : @webid = nil
88
+
89
+ if((@username == nil) && !@anon)
90
+ $log.write("Please specify a username, or specify that anonymous will be used.\n")
91
+ return
92
+ end
93
+ end
94
+
95
+ def start
96
+ super
97
+ # need to populate webid if not provided
98
+
99
+ if(@webid != nil)
100
+ return
101
+ end
102
+
103
+ client = nil
104
+ #PI Web API search doesn't work from basic or anonymous. Kerberos from Ruby isn't working. I think we have to use the absolute path. With Kerberos we could looking based on search and use top result
105
+ if(@attpath == nil)
106
+ url = "https://#{@piwebapiserver}/piwebapi/points/?path=#{@tagpath}"
107
+ if(@anon)
108
+ client = RestClient::Resource.new(url, :verify_ssl => OpenSSL::SSL::VERIFY_NONE )
109
+ else
110
+ client = RestClient::Resource.new(url, :verify_ssl => OpenSSL::SSL::VERIFY_NONE, :user => @username, :password =>@pwd)
111
+ end
112
+ else
113
+ url = "https://#{@piwebapiserver}/piwebapi/attributes/?path=#{@attpath}"
114
+ if(@anon)
115
+ client = RestClient::Resource.new(url, :verify_ssl => OpenSSL::SSL::VERIFY_NONE )
116
+ else
117
+ client = RestClient::Resource.new(url, :verify_ssl => OpenSSL::SSL::VERIFY_NONE, :user => @username, :password =>@pwd)
118
+ end
119
+ end
120
+
121
+ response = client.get
122
+ @webid = (JSON.parse(response.body))['WebId']
123
+ if(@webid == nil)
124
+ $log.write("WedID is lost. Unable to log to PI\n")
125
+ return
126
+ end
127
+
128
+ @writeURL = "https://#{@piwebapiserver}/piwebapi/streams/#{@webid}/recorded"
129
+ if(@anon)
130
+ @writeclient = RestClient::Resource.new(@writeURL, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
131
+ else
132
+ @writeclient = RestClient::Resource.new(@writeURL, :verify_ssl => OpenSSL::SSL::VERIFY_NONE,:user => @username, :password =>@pwd)
133
+ end
134
+
135
+ end
136
+
137
+ def write(chunk)
138
+ data= chunk.read
139
+ jsonObj = "[" + data.chop + "]" # removes trailing ',' and makes a valid json object
140
+
141
+ response = @writeclient.post jsonObj, :content_type => 'application/json'
142
+ $log.write("PI successful writes\n")
143
+ end
144
+
145
+ def format(tag, time, record)
146
+ value = record[@valuetag].to_s
147
+ "{\"Timestamp\":\"#{Time.at(time).localtime}\",\"Value\":\"#{value}\"},"
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,198 @@
1
+ # Copyright (c) 2013-2016 OSIsoft, LLC. All rights reserved.
2
+ #
3
+ # THIS SOFTWARE CONTAINS CONFIDENTIAL INFORMATION AND TRADE SECRETS OF
4
+ # OSIsoft, LLC. USE, DISCLOSURE, OR REPRODUCTION IS PROHIBITED WITHOUT
5
+ # THE PRIOR EXPRESS WRITTEN PERMISSION OF OSIsoft, LLC.
6
+ #
7
+ # RESTRICTED RIGHTS LEGEND
8
+ # Use, duplication, or disclosure by the Government is subject to restrictions
9
+ # as set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and
10
+ # Computer Software clause at DFARS 252.227.7013
11
+ #
12
+ # OSIsoft, LLC
13
+ # 777 Davis Street, Suite 250, San Leandro CA 94577
14
+
15
+ require 'fluent/output'
16
+ require 'json'
17
+ require 'rest-client'
18
+ require 'adal'
19
+ require 'certified'
20
+
21
+ module Fluent
22
+ class Qi < BufferedOutput
23
+ Fluent::Plugin.register_output('qi', self)
24
+
25
+ helpers :inject, :formatter, :compat_parameters
26
+
27
+ typeID = nil, streamID = nil, tenantId = nil, namespaceId = nil
28
+ appId = nil, appKey = nil
29
+ valuetag = 'value'
30
+ access_token = nil, full_token = nil, refresh_token = nil
31
+ authHeader = nil
32
+
33
+ secondWriteAttempt = false
34
+
35
+ DEFAULT_FORMAT_TYPE = 'json'
36
+
37
+ config_section :buffer do
38
+ config_set_default :chunk_keys, ['tag']
39
+ config_set_default :flush_at_shutdown, true
40
+ config_set_default :chunk_limit_size, 10 * 1024
41
+ end
42
+
43
+ config_section :format do
44
+ config_set_default :@type, DEFAULT_FORMAT_TYPE
45
+ end
46
+
47
+ def prefer_buffered_processing
48
+ false
49
+ end
50
+
51
+ def prefer_delayed_commit
52
+ @delayed
53
+ end
54
+
55
+ attr_accessor :delayed
56
+
57
+ def initialize
58
+ super
59
+ @delayed = false
60
+ end
61
+
62
+ def configure(conf)
63
+ if conf['output_type'] && !conf['format']
64
+ conf['format'] = conf['output_type']
65
+ end
66
+ compat_parameters_convert(conf, :inject, :formatter)
67
+
68
+ super
69
+
70
+ @formatter = formatter_create(conf: conf.elements('format').first, default_type: DEFAULT_FORMAT_TYPE)
71
+
72
+ (conf.key?('tenantId')) ? @tenantId = conf['tenantId'] : @tenantId = nil
73
+ (conf.key?('namespaceId')) ? @namespaceId = conf['namespaceId'] : @attpath = nil
74
+
75
+ (conf.key?('appId')) ? @appId = conf['appId'] : @appId = nil
76
+ (conf.key?('appKey')) ? @appKey = conf['appKey'] : @appKey = nil
77
+
78
+ (conf.key?('typeID')) ? @typeID = conf['typeID'] : @typeID = nil
79
+ (conf.key?('streamID')) ? @streamID = conf['streamID'] : @streamID = 'nil'
80
+ (conf.key?('valuetag')) ? @valuetag = conf['valuetag'] : @valuetag = 'Value'
81
+
82
+ if(@tenantId == nil || @namespaceId == nil)
83
+ #could change requirement and create a namespace as it is needed, but currently it must be already created
84
+ $log.write("Please specify a tenantID and namespaceID. Unable to send to QI.\n")
85
+ return
86
+ end
87
+
88
+ if(@appId == nil || @appKey == nil)
89
+ $log.write("Please specify a appId and appKey. Unable to send to QI.\n")
90
+ return
91
+ end
92
+ end
93
+
94
+ def start
95
+ super
96
+ gettoken(true)
97
+ @secondWriteAttempt = false
98
+ end
99
+
100
+ def write(chunk)
101
+ data = chunk.read
102
+ jsonObj = "[" + data.chop + "]" # removes trailing ',' and makes a valid json object
103
+
104
+ if(Time.at(@full_token.expires_on).utc < (Time.now.utc + 30))
105
+ gettoken(false) #the refresh token isn't working so when we are withing 30 seconds get a new token
106
+ end
107
+
108
+ sendDataURL = "https://qi-data.osisoft.com/Qi/#{@tenantId}/#{@namespaceId}/Streams/#{@streamID}/Data/UpdateValues"
109
+
110
+ clientGetStream = RestClient::Resource.new(sendDataURL, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
111
+ clientGetStream.put(jsonObj, :authorization => @authHeader, :content_type => "application/json", :accept => "text/plain") { |response, request, result, &block|
112
+ case response.code
113
+ when 200,201,302
114
+ #201 means it is created, 302 means it was created and it was just retrieved.
115
+ secondWriteAttempt=false
116
+ else
117
+ $log.write("Failed to write the value. Regetting stream and type to see if this helps.")
118
+ if(!secondWriteAttempt)
119
+ secondWriteAttempt=true
120
+ createTypeAndStream()
121
+ write(chunk)
122
+ else
123
+ $log.write("Failed to write the value. Regetting stream and type did not help.")
124
+ raise "Writing Values failed"
125
+ end
126
+ end
127
+ }
128
+ $log.write("QI successful writes\n")
129
+ end
130
+
131
+ def gettoken(getstream)
132
+ auth_ctx = ADAL::AuthenticationContext.new("login.windows.net","365cc376-d621-41b0-b32f-7a982b22a63c")
133
+ client_cred = ADAL::ClientCredential.new(@appId, @appKey)
134
+
135
+ token_response = auth_ctx.acquire_token_for_client("https://qihomeprod.onmicrosoft.com/historian",client_cred)
136
+ case token_response
137
+ when ADAL::SuccessResponse
138
+ # ADAL successfully exchanged the authorization code for an access_token.
139
+ # The token_response includes other information but we only care about the
140
+ # access token and the refresh token.
141
+ @full_token = token_response
142
+ @access_token = token_response.access_token
143
+ @refresh_token = token_response.refresh_token
144
+ @authHeader = ("bearer " + @access_token)
145
+
146
+ if(getstream)
147
+ createTypeAndStream()
148
+ end
149
+
150
+ when ADAL::ErrorResponse
151
+ # ADAL failed to exchange the authorization code for an access_token.
152
+ $log.write("Failed to get an access_token")
153
+ raise "ADAL token failed"
154
+ end
155
+ end
156
+
157
+ def createTypeAndStream
158
+ typeURL = ("https://qi-data.osisoft.com/Qi/#{@tenantId}/#{@namespaceId}/Types")
159
+
160
+ # could open this up to let configuration dictate how the object is created.
161
+ # need to figure out if this should be guided or open (either ask for information and it creates JSON, or just import the JSON and use directly).
162
+ jsonObj = "{\"Id\":\"#{@typeID}\",\"Name\":\"FluentD\",\"Description\":\"This is a type for fluentd events\",\"QiTypeCode\":0,\"Properties\":[{\"Id\":\"Timestamp\",\"Name\":null,\"Description\":null,\"QiType\":{\"Id\":\"string\",\"Name\":null,\"Description\":null,\"QiTypeCode\":18,\"Properties\":null},\"IsKey\":true},{\"Id\":\"Value\",\"Name\":null,\"Description\":null,\"QiType\":{\"Id\":\"string\",\"Name\":null,\"Description\":null,\"QiTypeCode\":18,\"Properties\":null},\"IsKey\":false}]}"
163
+
164
+ client = RestClient::Resource.new(typeURL, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
165
+
166
+ client.post(jsonObj, :authorization => @authHeader, :content_type => "application/json", :accept => "text/plain") { |response, request, result, &block|
167
+ case response.code
168
+ when 200,201,302
169
+ #this returns a 201 when the create works and a 302 when the thing already existed
170
+
171
+ getStreamURL = "https://qi-data.osisoft.com/Qi/#{@tenantId}/#{@namespaceId}/Streams"
172
+ jsonObjGetStream = "{\"Id\":\"#{@streamID}\",\"Name\":\"WaveData_SampleStream\",\"Description\":null,\"TypeId\":\"#{@typeID}\",\"BehaviorId\":null}"
173
+
174
+ clientGetStream = RestClient::Resource.new(getStreamURL, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
175
+ clientGetStream.post(jsonObjGetStream, :authorization => @authHeader, :content_type => "application/json", :accept => "text/plain") { |response, request, result, &block|
176
+ case response.code
177
+ when 201,302
178
+ #this returns a 201 when the create works and a 302 when the thing already existed
179
+
180
+ else
181
+ $log.write("Failed to get the stream created or found ")
182
+ raise "Stream creation failed"
183
+ end
184
+ }
185
+ else
186
+ $log.write("Failed to get the type created or found ")
187
+ raise "Type creation failed"
188
+ end
189
+ }
190
+ end
191
+
192
+ def format(tag, time, record)
193
+ #assumes time is a bignum in FluentD pattern not a PI Time
194
+ value = record[@valuetag].to_s
195
+ return "{\"Timestamp\":\"#{Time.at(time).localtime}\",\"Value\":\"#{value}\"},"
196
+ end
197
+ end
198
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-pi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Derek Endres
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.6.9
27
+ - !ruby/object:Gem::Dependency
28
+ name: adal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: certified
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ description: Fluentd plugin to either get data from PI, send to PI or send to QI
84
+ email:
85
+ - dendres@osisoft.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - CODE_OF_CONDUCT.md
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - README.md
94
+ - Rakefile
95
+ - fluent-plugin-pi.gemspec
96
+ - lib/fluent/plugin/in_piin.rb
97
+ - lib/fluent/plugin/out_pi.rb
98
+ - lib/fluent/plugin/out_qi.rb
99
+ - pkg/fluent-plugin-pi-0.0.1.gem
100
+ homepage: https://github.com/osisoftresearch
101
+ licenses:
102
+ - OSISoft LLC
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.6.7
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Fluentd plugin to either get data from PI, send to PI or send to QI.
124
+ test_files: []