sangoma / switchy

async FreeSWITCH cluster control
https://switchy.readthedocs.io/en/latest/
Mozilla Public License 2.0
69 stars 18 forks source link

Switchy proper use case #64

Closed k4ml closed 7 years ago

k4ml commented 7 years ago

From what I read in the docs and also initial announcement on FS mailing list, I guess switchy original use case was for load testing. In my case however, we need to handle incoming/outgoing call services. Currently we use the esl library to handle making and receiving calls. But switchy seem to also has all the call handling code that we need, and seem using better approach than what we currently has.

So just to handle incoming/outgoing calls, what do you suggest the proper way forward ? Implement our logic from the Service class:-

from switchy import Service, event_callback

class Phone(object):
    """Proxy all inbound calls to the destination specified in the SIP
    Request-URI.
    """
    @event_callback('CHANNEL_PARK')
    def on_park(self, sess):
        # our call handling logic start here
        pass

s = Service(['FS_host1.com', 'FS_host2.com', 'FS_host3.com'])
s.apps.load_app(Proxier, app_id='default')
s.run()

Or implement it as Router ? Or load it as app from switch serve command ?

goodboy commented 7 years ago

Hey again @k4ml :)

So I'd actually suggest using both.

Take a look at the flask-like routing docs. The examples there show that you can make a Router, use it to define your extensions using decorated functions, and then load the router like you would any other app using a Service instance:

from switchy import Service
from switchy.apps.routers import Router

router = Router(guards={
    'Call-Direction': 'inbound',
    'variable_sofia_profile': 'external'})

@router.route('00(.*)|011(.*)', response='407')
def reject_international(sess, match, router, response):
    sess.respond(response)
    sess.hangup()

@router.route("^(9054941990)$")
def test_did(sess, match, router):
    # call our route function from above
    return bridge2dest(sess, match, router, profile='external')

s = Service(['FS_host1.com', 'FS_host2.com', 'FS_host3.com'])
s.apps.load_app(router, app_id='default')
s.run()  # blocks forever

To get some better insight you might want to take a look at how the Router app is implemented.

In terms of integration with switchy serve (from #63) I wonder if it makes sense to be able to pass a --module app_module.py option (or similar) which tells switchy to load the app_module.py module and then make any defined apps available for the service to load? What do you think?

If you have any questions please let me know :) All of the cluster service api stuff is very new so we'd appreciate any feedback you have. Also feel welcome to contribute any new ideas you come up with :+1:

k4ml commented 7 years ago

Aah, right. I missed this. You can decorate multiple functions with router instance, and pass it to Service. Now it make sense.

On loading app, does it mean currently we can only load the app from switchy ? If yes then I think using full import path will open possibility of using any custom app, as long as they're on the import path. I think that more pythonic and inline with approach in other project like django. Just passing the class name seem a bit magic to me.

goodboy commented 7 years ago

@k4ml ok sounds good I'll add support for the full module path and change the docs. Thanks for the feedback!

Are you having any luck accomplishing your goal with the new Service stuff? I'm interested to know :)

PS: also make sure to mention me with @tgoodlet because I almost missed this reply ;)

k4ml commented 7 years ago

@tgoodlet I have problem with ring_ready yesterday as I wrote in #65. While working on that I noticed that you're using uuid_broadcast (through sess.broadcast()) for executing command. Before discovering that, I try to execute ring_ready through sess.con.execute('ring_ready', '', sess.uuid). Looking at switchy log, I didn't see the command get executed.

Any reason why uuid_broadcast is preferred over con.execute() or con.send()` ?

goodboy commented 7 years ago

@k4ml I answered in #65 btw.

Yes so broadcast() is guaranteed to be async so you can use it on apps that normally block. Also FreeSWITCH will internally queue up the app requests made with broadcast() so you can guarantee ordering.

We don't use con.execute() because it only let's you call dialplan apps and not mod_commands commands. Also I'm pretty sure it's not async unless the underlying app is.

con.execute() should still work but switchy won't log anything since we haven't re-wrapped that method like we have for api(). Feel free to make a PR for that if you want.

con.send() doesn't return back a response like con.api() does.

switchy is an async cluster controller and so far con.api() and con.bgapi() generally have worked well for that use case.