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  }