fluent-plugin-pi 0.0.1

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.
@@ -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: []