github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/coprocess_python.go (about) 1 // +build cgo 2 3 package gateway 4 5 import ( 6 "C" 7 "io/ioutil" 8 "path/filepath" 9 "runtime" 10 "unsafe" 11 12 "github.com/sirupsen/logrus" 13 14 "fmt" 15 16 "github.com/TykTechnologies/tyk/apidef" 17 "github.com/TykTechnologies/tyk/config" 18 "github.com/TykTechnologies/tyk/coprocess" 19 20 python "github.com/TykTechnologies/tyk/dlpython" 21 "github.com/golang/protobuf/proto" 22 ) 23 import ( 24 "os" 25 "sync" 26 ) 27 28 var ( 29 dispatcherClass unsafe.Pointer 30 dispatcherInstance unsafe.Pointer 31 pythonLock = sync.Mutex{} 32 ) 33 34 // PythonDispatcher implements a coprocess.Dispatcher 35 type PythonDispatcher struct { 36 coprocess.Dispatcher 37 } 38 39 // Dispatch takes a CoProcessMessage and sends it to the CP. 40 func (d *PythonDispatcher) Dispatch(object *coprocess.Object) (*coprocess.Object, error) { 41 // Prepare the PB object: 42 objectMsg, err := proto.Marshal(object) 43 if err != nil { 44 return nil, err 45 } 46 47 pythonLock.Lock() 48 // Find the dispatch_hook: 49 dispatchHookFunc, err := python.PyObjectGetAttr(dispatcherInstance, "dispatch_hook") 50 if err != nil { 51 log.WithFields(logrus.Fields{ 52 "prefix": "python", 53 }).Fatal(err) 54 python.PyErr_Print() 55 pythonLock.Unlock() 56 return nil, err 57 } 58 59 objectBytes, err := python.PyBytesFromString(objectMsg) 60 if err != nil { 61 log.WithFields(logrus.Fields{ 62 "prefix": "python", 63 }).Fatal(err) 64 python.PyErr_Print() 65 pythonLock.Unlock() 66 return nil, err 67 } 68 69 args, err := python.PyTupleNew(1) 70 if err != nil { 71 log.WithFields(logrus.Fields{ 72 "prefix": "python", 73 }).Fatal(err) 74 python.PyErr_Print() 75 pythonLock.Unlock() 76 return nil, err 77 } 78 79 python.PyTupleSetItem(args, 0, objectBytes) 80 result, err := python.PyObjectCallObject(dispatchHookFunc, args) 81 if err != nil { 82 log.WithFields(logrus.Fields{ 83 "prefix": "python", 84 }).Error(err) 85 python.PyErr_Print() 86 pythonLock.Unlock() 87 return nil, err 88 } 89 python.PyDecRef(args) 90 python.PyDecRef(dispatchHookFunc) 91 92 newObjectPtr, err := python.PyTupleGetItem(result, 0) 93 if err != nil { 94 log.WithFields(logrus.Fields{ 95 "prefix": "python", 96 }).Error(err) 97 python.PyErr_Print() 98 pythonLock.Unlock() 99 return nil, err 100 } 101 102 newObjectLen, err := python.PyTupleGetItem(result, 1) 103 if err != nil { 104 log.WithFields(logrus.Fields{ 105 "prefix": "python", 106 }).Error(err) 107 python.PyErr_Print() 108 pythonLock.Unlock() 109 return nil, err 110 } 111 112 newObjectBytes, err := python.PyBytesAsString(newObjectPtr, python.PyLongAsLong(newObjectLen)) 113 if err != nil { 114 log.WithFields(logrus.Fields{ 115 "prefix": "python", 116 }).Error(err) 117 python.PyErr_Print() 118 pythonLock.Unlock() 119 return nil, err 120 } 121 python.PyDecRef(result) 122 pythonLock.Unlock() 123 124 newObject := &coprocess.Object{} 125 err = proto.Unmarshal(newObjectBytes, newObject) 126 if err != nil { 127 log.WithFields(logrus.Fields{ 128 "prefix": "python", 129 }).Error(err) 130 return nil, err 131 } 132 return newObject, nil 133 134 } 135 136 // DispatchEvent dispatches a Tyk event. 137 func (d *PythonDispatcher) DispatchEvent(eventJSON []byte) { 138 /* 139 CEventJSON := C.CString(string(eventJSON)) 140 defer C.free(unsafe.Pointer(CEventJSON)) 141 C.Python_DispatchEvent(CEventJSON) 142 */ 143 } 144 145 // Reload triggers a reload affecting CP middlewares and event handlers. 146 func (d *PythonDispatcher) Reload() { 147 // C.Python_ReloadDispatcher() 148 } 149 150 // HandleMiddlewareCache isn't used by Python. 151 func (d *PythonDispatcher) HandleMiddlewareCache(b *apidef.BundleManifest, basePath string) { 152 pythonLock.Lock() 153 defer pythonLock.Unlock() 154 dispatcherLoadBundle, err := python.PyObjectGetAttr(dispatcherInstance, "load_bundle") 155 if err != nil { 156 log.WithFields(logrus.Fields{ 157 "prefix": "python", 158 }).Error(err) 159 return 160 } 161 162 args, err := python.PyTupleNew(1) 163 if err != nil { 164 log.WithFields(logrus.Fields{ 165 "prefix": "python", 166 }).Error(err) 167 python.PyErr_Print() 168 return 169 } 170 python.PyTupleSetItem(args, 0, basePath) 171 _, err = python.PyObjectCallObject(dispatcherLoadBundle, args) 172 if err != nil { 173 log.WithFields(logrus.Fields{ 174 "prefix": "python", 175 }).Error(err) 176 python.PyErr_Print() 177 } 178 } 179 180 // PythonInit initializes the Python interpreter. 181 func PythonInit() error { 182 ver, err := python.FindPythonConfig(config.Global().CoProcessOptions.PythonVersion) 183 if err != nil { 184 log.WithError(err).Errorf("Python version '%s' doesn't exist", ver) 185 return fmt.Errorf("python version '%s' doesn't exist", ver) 186 } 187 err = python.Init() 188 if err != nil { 189 log.WithFields(logrus.Fields{ 190 "prefix": "coprocess", 191 }).Fatal("Couldn't initialize Python") 192 } 193 log.WithFields(logrus.Fields{ 194 "prefix": "coprocess", 195 }).Infof("Python version '%s' loaded", ver) 196 return nil 197 } 198 199 // PythonLoadDispatcher creates reference to the dispatcher class. 200 func PythonLoadDispatcher() error { 201 pythonLock.Lock() 202 defer pythonLock.Unlock() 203 moduleDict, err := python.LoadModuleDict("dispatcher") 204 if err != nil { 205 log.WithFields(logrus.Fields{ 206 "prefix": "coprocess", 207 }).Fatalf("Couldn't initialize Python dispatcher") 208 python.PyErr_Print() 209 return err 210 } 211 dispatcherClass, err = python.GetItem(moduleDict, "TykDispatcher") 212 if err != nil { 213 log.WithFields(logrus.Fields{ 214 "prefix": "coprocess", 215 }).Fatalf("Couldn't initialize Python dispatcher") 216 python.PyErr_Print() 217 return err 218 } 219 return nil 220 } 221 222 // PythonNewDispatcher creates an instance of TykDispatcher. 223 func PythonNewDispatcher(bundleRootPath string) (coprocess.Dispatcher, error) { 224 pythonLock.Lock() 225 defer pythonLock.Unlock() 226 args, err := python.PyTupleNew(1) 227 if err != nil { 228 log.WithFields(logrus.Fields{ 229 "prefix": "python", 230 }).Fatal(err) 231 return nil, err 232 } 233 if err := python.PyTupleSetItem(args, 0, bundleRootPath); err != nil { 234 log.WithFields(logrus.Fields{ 235 "prefix": "python", 236 }).Error(err) 237 python.PyErr_Print() 238 return nil, err 239 } 240 dispatcherInstance, err = python.PyObjectCallObject(dispatcherClass, args) 241 if err != nil { 242 log.WithFields(logrus.Fields{ 243 "prefix": "python", 244 }).Error(err) 245 python.PyErr_Print() 246 return nil, err 247 } 248 dispatcher := &PythonDispatcher{} 249 return dispatcher, nil 250 } 251 252 // PythonSetEnv sets PYTHONPATH, it's called before initializing the interpreter. 253 func PythonSetEnv(pythonPaths ...string) { 254 python.SetPythonPath(pythonPaths) 255 } 256 257 // getBundlePaths will return an array of the available bundle directories: 258 func getBundlePaths() []string { 259 bundlePath := filepath.Join(config.Global().MiddlewarePath, "bundles") 260 directories := make([]string, 0) 261 bundles, _ := ioutil.ReadDir(bundlePath) 262 for _, f := range bundles { 263 if f.IsDir() { 264 fullPath := filepath.Join(bundlePath, f.Name()) 265 directories = append(directories, fullPath) 266 } 267 } 268 return directories 269 } 270 271 // NewPythonDispatcher wraps all the actions needed for this CP. 272 func NewPythonDispatcher() (dispatcher coprocess.Dispatcher, err error) { 273 workDir := config.Global().CoProcessOptions.PythonPathPrefix 274 if workDir == "" { 275 tykBin, _ := os.Executable() 276 workDir = filepath.Dir(tykBin) 277 log.WithFields(logrus.Fields{ 278 "prefix": "coprocess", 279 }).Debugf("Python path prefix isn't set, using '%s'", workDir) 280 } 281 dispatcherPath := filepath.Join(workDir, "coprocess", "python") 282 tykPath := filepath.Join(dispatcherPath, "tyk") 283 protoPath := filepath.Join(workDir, "coprocess", "python", "proto") 284 bundleRootPath := filepath.Join(config.Global().MiddlewarePath, "bundles") 285 286 paths := []string{dispatcherPath, tykPath, protoPath, bundleRootPath} 287 288 // initDone is used to signal the end of Python initialization step: 289 initDone := make(chan error) 290 291 go func() { 292 runtime.LockOSThread() 293 defer runtime.UnlockOSThread() 294 PythonSetEnv(paths...) 295 err := PythonInit() 296 if err != nil { 297 initDone <- err 298 return 299 } 300 if err := PythonLoadDispatcher(); err != nil { 301 initDone <- err 302 return 303 } 304 dispatcher, err = PythonNewDispatcher(bundleRootPath) 305 if err != nil { 306 log.WithFields(logrus.Fields{ 307 "prefix": "coprocess", 308 }).Error(err) 309 } 310 311 initDone <- err 312 }() 313 err = <-initDone 314 return dispatcher, err 315 }