master
go 242 lines 7.34 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build cgo && ibm_mq
4
5 package pcf
6
7 import (
8 "fmt"
9 "os"
10
11 "github.com/ibm-messaging/mq-golang/v5/ibmmq"
12 "github.com/ibm-messaging/mq-golang/v5/mqmetric"
13 )
14
15 // Connect connects to the queue manager using IBM library.
16 func (c *Client) Connect() error {
17 c.protocol.Debugf("protocol starting connection attempt to queue manager '%s' on %s:%d via channel '%s' (user: '%s')",
18 c.config.QueueManager, c.config.Host, c.config.Port, c.config.Channel, c.config.User)
19
20 backoff := c.protocol.GetBackoff()
21 attemptCount := 0
22 err := backoff.Retry(func() error {
23 attemptCount++
24 c.protocol.Debugf("connection attempt #%d to %s:%d (queue manager: '%s', channel: '%s')",
25 attemptCount, c.config.Host, c.config.Port, c.config.QueueManager, c.config.Channel)
26 return c.doConnect()
27 })
28 if err != nil {
29 c.protocol.Errorf("connection failed to queue manager '%s' at %s:%d after %d attempts: %v",
30 c.config.QueueManager, c.config.Host, c.config.Port, attemptCount, err)
31 c.protocol.Debugf("Connect FAILED")
32 return err
33 }
34 c.protocol.MarkConnected()
35 c.protocol.Debugf("successfully connected to queue manager '%s' at %s:%d (channel: '%s', user: '%s') after %d attempts",
36 c.config.QueueManager, c.config.Host, c.config.Port, c.config.Channel, c.config.User, attemptCount)
37 return nil
38 }
39
40 func (c *Client) doConnect() error {
41 if c.connected {
42 c.protocol.Debugf("already connected to queue manager '%s' at %s:%d, skipping connection attempt",
43 c.config.QueueManager, c.config.Host, c.config.Port)
44 return nil
45 }
46
47 // CRITICAL: IBM MQ PCF fails with LC_ALL=C locale
48 // The MQ client library has issues with string handling in the C locale,
49 // which causes PCF responses to be garbled and unparseable
50 if currentLocale := os.Getenv("LC_ALL"); currentLocale == "C" {
51 c.protocol.Warningf("LC_ALL environment variable is set to 'C', changing to 'en_US.UTF-8' to avoid MQ client library issues")
52 if err := os.Setenv("LC_ALL", "en_US.UTF-8"); err != nil {
53 c.protocol.Warningf("failed to change LC_ALL environment variable: %v", err)
54 }
55 }
56
57 // Connection #1: IBM PCF connection for administrative commands
58 if err := c.connectPCF(); err != nil {
59 return fmt.Errorf("PCF connection failed: %w", err)
60 }
61
62 // Connection #2: Initialize IBM resource monitoring (separate connection)
63 if err := c.initResourceMonitoring(); err != nil {
64 c.protocol.Warningf("Resource monitoring initialization failed: %v", err)
65 // Continue - resource monitoring is optional, PCF commands still work
66 c.metricsReady = false
67 } else {
68 c.metricsReady = true
69 }
70
71 // Refresh static data on successful connection
72 if err := c.refreshStaticData(); err != nil {
73 c.protocol.Warningf("failed to refresh static data: %v", err)
74 // Continue - not critical for basic operation
75 }
76
77 c.connected = true
78 return nil
79 }
80
81 func (c *Client) connectPCF() error {
82 c.protocol.Debugf("initializing IBM MQ connection to %s:%d", c.config.Host, c.config.Port)
83
84 // Create connection options
85 cno := ibmmq.NewMQCNO()
86 cno.Options = ibmmq.MQCNO_CLIENT_BINDING
87
88 // Create channel definition
89 cd := ibmmq.NewMQCD()
90 cd.ChannelName = c.config.Channel
91 cd.ConnectionName = fmt.Sprintf("%s(%d)", c.config.Host, c.config.Port)
92
93 // Set up authentication if credentials are provided
94 if c.config.User != "" && c.config.Password != "" {
95 csp := ibmmq.NewMQCSP()
96 csp.AuthenticationType = ibmmq.MQCSP_AUTH_USER_ID_AND_PWD
97 csp.UserId = c.config.User
98 csp.Password = c.config.Password
99 cno.SecurityParms = csp
100 c.protocol.Debugf("authentication configured for user '%s'", c.config.User)
101 }
102
103 cno.ClientConn = cd
104
105 // Connect to queue manager
106 qmgr, err := ibmmq.Connx(c.config.QueueManager, cno)
107 if err != nil {
108 return fmt.Errorf("failed to connect to queue manager '%s': %w", c.config.QueueManager, err)
109 }
110 c.qmgr = qmgr
111
112 c.protocol.Debugf("successfully connected to queue manager '%s'", c.config.QueueManager)
113
114 // Open command queue
115 if err := c.openCommandQueue(); err != nil {
116 c.qmgr.Disc()
117 return fmt.Errorf("failed to open command queue: %w", err)
118 }
119
120 // Create reply queue
121 if err := c.createReplyQueue(); err != nil {
122 c.cmdQueue.Close(0)
123 c.qmgr.Disc()
124 return fmt.Errorf("failed to create reply queue: %w", err)
125 }
126
127 return nil
128 }
129
130 func (c *Client) openCommandQueue() error {
131 mqod := ibmmq.NewMQOD()
132 mqod.ObjectType = ibmmq.MQOT_Q
133 mqod.ObjectName = "SYSTEM.ADMIN.COMMAND.QUEUE"
134
135 openOptions := ibmmq.MQOO_OUTPUT | ibmmq.MQOO_FAIL_IF_QUIESCING
136
137 obj, err := c.qmgr.Open(mqod, openOptions)
138 if err != nil {
139 return fmt.Errorf("failed to open SYSTEM.ADMIN.COMMAND.QUEUE: %w", err)
140 }
141 c.cmdQueue = obj
142
143 c.protocol.Debugf("successfully opened SYSTEM.ADMIN.COMMAND.QUEUE")
144 return nil
145 }
146
147 func (c *Client) createReplyQueue() error {
148 // Create dynamic reply queue
149 mqod := ibmmq.NewMQOD()
150 mqod.ObjectType = ibmmq.MQOT_Q
151 mqod.ObjectName = "SYSTEM.DEFAULT.MODEL.QUEUE"
152 mqod.DynamicQName = "NETDATA.REPLY.*"
153
154 openOptions := ibmmq.MQOO_INPUT_EXCLUSIVE | ibmmq.MQOO_FAIL_IF_QUIESCING
155
156 obj, err := c.qmgr.Open(mqod, openOptions)
157 if err != nil {
158 return fmt.Errorf("failed to create reply queue: %w", err)
159 }
160 c.replyQueue = obj
161 c.replyQueueName = obj.Name
162
163 c.protocol.Debugf("successfully created reply queue: '%s'", c.replyQueueName)
164 return nil
165 }
166
167 func (c *Client) initResourceMonitoring() error {
168 // DISABLE: Resource monitoring needs to be properly implemented
169 // The mqmetric library expects pre-existing queues, not model queues
170 // This requires a different initialization approach than what was migrated
171
172 c.protocol.Debugf("resource monitoring disabled - requires proper mqmetric integration")
173 return fmt.Errorf("resource monitoring not yet implemented in migrated PCF client")
174 }
175
176 func (c *Client) refreshStaticData() error {
177 // Get queue manager information to populate cached static data
178 info, err := c.getQueueManagerInfo()
179 if err != nil {
180 return fmt.Errorf("failed to get queue manager info: %w", err)
181 }
182
183 c.cachedVersion = info.Version
184 c.cachedEdition = info.Edition
185 c.cachedCommandLevel = info.CommandLevel
186 c.cachedPlatform = info.Platform
187
188 c.protocol.Debugf("QMGR refreshed static data: version=%s, edition=%s, command_level=%d, platform=%d",
189 c.cachedVersion, c.cachedEdition, c.cachedCommandLevel, c.cachedPlatform)
190
191 return nil
192 }
193
194 func (c *Client) getQueueManagerInfo() (*QueueManagerInfo, error) {
195 // Use the migrated PCF commands to get queue manager information
196 err := c.refreshStaticDataFromPCF()
197 if err != nil {
198 return nil, err
199 }
200
201 return &QueueManagerInfo{
202 Version: c.cachedVersion,
203 Edition: c.cachedEdition,
204 CommandLevel: c.cachedCommandLevel,
205 Platform: c.cachedPlatform,
206 }, nil
207 }
208
209 // Disconnect closes both connections and cleans up resources
210 func (c *Client) Disconnect() {
211 if !c.connected {
212 c.protocol.Debugf("not connected, skipping disconnect")
213 return
214 }
215
216 c.protocol.Debugf("disconnecting from queue manager '%s'", c.config.QueueManager)
217
218 // Close PCF connection (Connection #1)
219 if c.replyQueue != (ibmmq.MQObject{}) {
220 c.replyQueue.Close(0)
221 c.protocol.Debugf("closed reply queue")
222 }
223
224 if c.cmdQueue != (ibmmq.MQObject{}) {
225 c.cmdQueue.Close(0)
226 c.protocol.Debugf("closed command queue")
227 }
228
229 if c.connected {
230 c.qmgr.Disc()
231 c.protocol.Debugf("disconnected from queue manager")
232 }
233
234 // Close mqmetric connection (Connection #2)
235 mqmetric.EndConnection()
236 c.protocol.Debugf("closed metrics connection")
237
238 c.connected = false
239 c.metricsReady = false
240
241 c.protocol.Debugf("disconnect complete")
242 }