Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upload and download methods fail when using stdio connection to SAS running within Docker. #450

Closed
royalsouvenir opened this issue Apr 12, 2022 · 17 comments

Comments

@royalsouvenir
Copy link

Describe the bug
The Analytics Pro Cloud Native product runs within a Docker container. When using the stdio over ssh connection method, the download and upload methods of the SASsession object fail due to the configuration of the socket filename.

Download Method

For the download method, the filename socket host should be set to host.docker.internal. I have tried to use the localhost parameter in sascfg_personal.py to set this however within saspy/sasiostdio.py, the localhost parameter is not utilised. The hostip is just using socket.gethostname() which will return the hostname of the saspy client.

if self.sascfg.ssh:
         if not self.sascfg.tunnel:
            host = self.sascfg.hostip #socks.gethostname()
         else:
            host = 'localhost'
      else:
         host = ''

The download method will work if the socket is set to filename sock socket 'host.docker.internal:{rtunnel} where rtunnel is the rtunnel parameter in sascfg_personal.py.

Upload Method

The upload method has a similar problem however I have no workaround for it. The socket host is set to :{rtunnel} however I can't get it to work if changing to host.docker.internal:{rtunnel} and the socket.connect method to either the IP address of the container or localhost.

To Reproduce

  • Create a new Analytics Pro Cloud Native container using the required CAP_ADD attributes to allow SSH connections. Add port mappings as well for SSH and socket connections such as -p 22:8222 and -p 4444:8444
  • Configure passwordless SSH for the generated user sasdemo by default by creating a .ssh folder under /data , adding relevant key and setting permissions to correct bits.
  • Create a new ssh connection in sascfg_personal.py as follows: python { 'saspath': '/opt/sas/viya/home/SASFoundation/sas', 'ssh': 'ssh', 'host': 'sasdemo@localhost', 'port': {ssh port binding from docker config}, 'tunnel': {local port binding from docker config}, 'rtunnel': {remote port binding from docker config}, 'localhost': 'host.docker.internal', 'options': ["-fullstimer"], 'encoding': 'utf_8' }
  • Instantiate a new sas session eg: `session = saspy.SASsession(cfgname=, cfgfile=)
  • Attempt to perform a download and upload using session.upload and session.download. Both should hang or error out.

Simplified Workaround code for Download
As localhost is private my adapter class sets localhost via

self.sascfg = getattr(self.session.sascfg.SAScfg,self.session.sascfg.name)
self.localhost = self.sascfg.get('localhost','localhost')
        try:
            code = f"""
            filename saspydir "{remotefile}" recfm=F encoding=binary lrecl=4096;   
            filename sock socket '{self.localhost}:{self.tunnel_port}' recfm=S lrecl=4096;
            data _null_;
                file sock;
                infile saspydir;
                input;
                put _infile_;
            run;
            """
            sock = socket.socket()
            sock.bind(("localhost", self.tunnel_port))
            sock.listen(1)
            self.session.submit(code)
            data = self.__recvall(sock)
            sock.close()

            with open(localfile, "wb") as f:
                f.write(data)
            rc = dict(Success=True, LOG="File downloaded")
            logger.debug("in try: rc=%s", rc)
        except Exception as e:
            rc = dict(Success=False, LOG=e)
            logger.debug("in except: rc=%s", rc)

        if rc.get("Success"):
            rc.update(dict(file=localfile))
        logger.debug("rc=%s", rc)
        return rc

Expected behavior
I can't see a valid workaround at the moment for upload. I simply can't get it to work with using localhost or the ip of the container on the client side. I'm also unsure of the intent of the localhost parameter and whether or not I'm bastardizing it by using it? As a result I'm really not sure what the right resolution here is as to whether the stdio connection class should be altered to use the localhost parameter, extend the class further with additional attributes for docker instances or an entirely new connection method for Docker / k8s?

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Linux
  • SAS Version: Analytics Pro Cloud Native - All Versions
  • SASPy Version: latest

Additional context
NA

@tomweber-sas
Copy link
Contributor

Hey @royalsouvenir, I will look into this. I haven't used this APro Container myself, but I understand what you're describing and given that it works for you if you can set both the host and ports, then I expect I can make it work with the right enhancements. The 'localhost' parm was one I added not too long ago, when working on my home network where DNS was returning some arbitrary internet addr instead of my local IP's on my network. That was the only case I had seen there be an issue. So, this seems like case number 2 for this kind of thing. Thus it isn't used everywhere and I only had it for the cases I found. This sounds like APro container is similar but not completely the same.
I'll need to refresh myself on the detail, but I expect I can work this case in also and resolve this for you. I'll need to get a working version of the container and debug this and see about a fix, so that might take me a little bit. But, I'll get on it and see what I find. Thanks for the helpful information you provided, that really helps!
Thanks,
Tom

@royalsouvenir
Copy link
Author

Thanks @tomweber-sas . Reach out if you need assistance with test cases etc as I'd be happy to contribute. If you are following the example docs for SASpy in the Apro documentation site note that it is slightly incorrect on windows. The Docker IP address doesn't resolve on Windows. You need to use localhost. I'll raise a track for the Apro team to fix that.

At the moment I have a workaround in place. Basically I'm sub-classing saspy.SASsession and overriding the download and upload methods. For the download, I'm using the raw value supplied for the localhost parameter and for the upload I'm using an old-fashioned put statement in the SAS code stub.

fh = ""
        try:
            with open(local_file) as f:
                fh = f.read()
        except (FileNotFoundError, OSError) as e:
            raise Exception(e)
        logger.debug('fl=%s...', fh[0:76])
        code = f"%let _local_file=%nrstr({fh});\n"
        code += "data _null_;\n"
        code += f'   file "{remote_file}";\n'
        code += '   put "&_local_file";\n'
        code += "run;\n"

@tomweber-sas
Copy link
Contributor

Hey, thanks. Yesterday I was able to get Apro container up and running on one of my linux systems. Are you running this on a windows box, or from linux? Now I need to do whatever needs to be done to get ssh enabled. And then try to get connected w/ SASPy and start looking at the actual problem. I do see the section of doc for SASPy (which they spelled wrong :( ), FWIW, I did the Quick Start in the following doc to get to where I am: https://go.documentation.sas.com/doc/en/anprocdc/v_009/dplyviya0ctr/p19vjw0smdpog4n1qsrq3q98siqh.htm
Now on to the section about SSH in the Custom Config section. Is this the same as how you've gone about it? There seem to be a number of different directions for setting this up, so I don't know if anything is different between them. Hopefully I'll be up and running soon ...
Tom

@tomweber-sas
Copy link
Contributor

Well, I'd have never figured out that sssd stuff; no instructions at all, but someone gave me what I needed to get that to finally work. So, then I was able to get SASPy to work 3 different ways. I never used 'localhost' as the host, which then makes upload and download work as expected.
From a different host than where the docker container is running, or even from the same machine, using the hostname of that machine and the port forwarding port (to ssh - 22), which by default? (though I set it in my docker command too) is 2222 I can connect and since it's not 'localhost', everything goes through the right paths in saspy. I also never ser either tunnel options either. So:

apro     = {
            'saspath' : '/opt/sas/viya/home/SASFoundation/bin/sas_u8',
            'ssh'     : '/usr/bin/ssh',
            'host'    : 'tom64-4.unx.sas.com',
            'port'    : 2222,
            }

The 3rd case was from the same machine as where the container was running (which worked as above), but I used the IP addr of the container itself, and port 22 itself (default), based upon the IP addr I got back from running the docker command from the doc in the SASPy section (bullet item 4):

4 Retrieve the IP address for SAS Analytics Pro:

docker inspect -f "{{ .NetworkSettings.IPAddress }}" sas-analytics-pro

which happened to be 172.17.0.2 (no telling what you get). but then my config was:

apro     = {
            'saspath' : '/opt/sas/viya/home/SASFoundation/bin/sas_u8',
            'ssh'     : '/usr/bin/ssh',
            'host'    : '172.17.0.2',
            }

In all 3 cases, up/download worked fin, as did everything else I tried too!

Can you try adjusting your config and see if it works as expected? Actually, in bullet 5 is says to use that IP from bullet 4, nevee saying to use 'host' : 'localhost'. So using 'host' : '172.17.0.2' is what it says to use which does work - from the same machine, else use the real host name and the forwarded port (2222).

Tom

@royalsouvenir
Copy link
Author

Hi @tomweber-sas . It sounds like this might be a windows quirk likely due to Docker running through hyperV on windows. I'm using Windows 11 with Docker Desktop for development. I used the quickstart from the Apro docs which is how I found that I needed to use localhost as the host instead of the docker IP address.

I'll put together some unittest scripts today testing the following. I'll also spin up a linux machine to show the differences. I'll get a colleague to try it out on a Mac as well:

  • Settings as per Apro docs / your tests
  • Settings with localhost as hostname and no localhost, tunnel or rtunnel settings
  • Settings with localhost as hostname and the localhost parameter set to host.docker.internal and using tunnel and rtunnel.

Thanks for your help on this. I appreciate it.

@royalsouvenir
Copy link
Author

Hi @tomweber-sas ,
So after some testing I think the problem is partly me (or my home network more specifically) and Windows quirks. I have managed to get the upload and download methods working but it does require the setting of the localhost parameter to host.docker.internal and host to localhost.

The original issue is a red-herring where I thought I needed tunnel and rtunnel set. I think the problem is on my side with my DNS. I'm using Pi.hole at home and I think it's getting confused with the new IP's of the docker containers.
Sorry about the run-around on that bit but I'm still pretty confused as to how docker is allowing a socket connection on a random port when it's meant to be isolated...

I confirmed this by changing networks (or more specifically going to my local bar and testing again over some craft beers...) and it works perfectly. It stopped working when I got back home and I had to disable my DNS.

Ultimately I don't think there are any coding changes required and it sounds like you have a concrete use case for when localhost is required. I'm guessing it is probably worth a documentation update for using containers on Windows? I'm still going to spin up a linux and osx vm to test further as I use SASPy for app development and it's good to know the quirks between platforms.

Do you need a PR to update docs or test cases? If so, let me know and I'll generate some.

So where I've landed:

Using the following sascfg_personal.py connection where port 8222 exposes port 22 in the docker container and I use the standard sasdemo userid:

SAS_config_names   = ['apro']

apro = {
    'saspath' : '/opt/sas/viya/home/SASFoundation/bin/sas_u8',
    'ssh'     : 'ssh',
    'host'    : 'localhost',  # Windows... love it, hate it.
    'luser'   : 'sasdemo',
    'port'    : 8222,
    'encoding': 'utf_8',
    'localhost': 'host.docker.internal' #Windows again!
}

Testcase:

import unittest
import socket
import tempfile

import saspy

class TestAproCloudNative(unittest.TestCase):
    """
    Test Requirements:
     - A valid Docker container instance of Apro with SSH enabled on the chosen account being used.
     - Configuration in sascfg_personal.py with the following set if running on windows:
        - port set for the exposed ssh port
        - localhost set to ``host.docker.internal``
        - The host set to ``localhost`` and not the ip address of the container.

     - To see failures set the sascfg_personal.py config to something vanilla like removing the ``localhost`` key or setting ``host`` to the ip address
       of the docker container.
    """
    @classmethod
    def setUpClass(cls) -> None:
        # If I wasn't so lazy I'd use the docker SDK here to create a container instance.  But alas...
        cls.connectionid='gitissuev4'
        cls.cfgfile='tests/sascfg_personal.py'
        data = '1'
        cls.tmpfile = tempfile.NamedTemporaryFile(delete=False)
        cls.tmpfile.write(data.encode())
        cls.tmpfile.seek(0)
        cls.infile = cls.tmpfile.name        
        super().setUpClass()

    @classmethod
    def tearDownClass(cls) -> None:
        cls.tmpfile.close()
        super().tearDownClass()


    def test_connection(self):
        with saspy.SASsession(cfgname=self.connectionid,cfgfile=self.cfgfile) as sas:
            self.assertIsNotNone(sas.SASpid)

    def test_submit(self):
        code = 'NOTE: success'
        with saspy.SASsession(cfgname=self.connectionid,cfgfile=self.cfgfile) as sas:
            rc = sas.submit(f'%put {code};')
            self.assertEqual(sas.SYSERR(),0,rc.get('LOG'))

    def test_upload(self):
        socket.setdefaulttimeout(300)
        with saspy.SASsession(cfgname=self.connectionid,cfgfile=self.cfgfile) as sas:
            rc = sas.upload(self.infile,'/data')
            self.assertTrue(rc.get('Success'),rc.get('LOG'))
        
    def test_download(self):
        socket.setdefaulttimeout(300)
        with saspy.SASsession(cfgname=self.connectionid,cfgfile=self.cfgfile) as sas:
            rc = sas.download('unittestdownload.txt','/data/.authinfo')
            self.assertTrue(rc.get('Success'),rc.get('LOG'))        


if __name__ == '__main__':
    unittest.main()

and invoked (via pytest):

pytest test_apro.py -v
======================================================================================================================================= test session starts =======================================================================================================================================
platform win32 -- Python 3.9.0, pytest-7.1.1, pluggy-1.0.0 -- c:\users\camer\documents\codelocal\selerity-test4sas-client\venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\camer\Documents\codelocal\selerity-test4sas-client, configfile: pytest.ini
collected 4 items

test_apro.py::TestAproCloudNative::test_connection PASSED                                                                                                                                                                                                                                    [ 25%]
test_apro.py::TestAproCloudNative::test_download PASSED                                                                                                                                                                                                                                      [ 50%]
test_apro.py::TestAproCloudNative::test_submit PASSED                                                                                                                                                                                                                                        [ 75%]
test_apro.py::TestAproCloudNative::test_upload PASSED                                                                                                                                                                                                                                        [100%]

Docker config (launchapro.ps1)

Write-Host "Launch SAS Analytics Pro"

$image="cr.sas.com/viya-4-x64_oci_linux_2-docker/sas-analytics-pro:0.12.28-20220315.1647335918443"

$run_args="-u root " +
"--name sas-analytics-pro " +
"--rm " +
"--detach " +
"--cap-add AUDIT_WRITE " +
"--cap-add SYS_ADMIN " +
"--hostname sas-analytics-pro " +
"--env RUN_MODE=developer " +
"--env SASLICENSEFILE=license.jwt " +
"--env SASLOCKDOWN=0 " +
"--publish 8080:80 " + # Studio
"--publish 8222:22 " + # SSH
"--volume 'my\windows\path\config:/sasinside' " +
"--volume 'my\windows\path\home\camer:/data' "

$cmd="docker run $run_args $image"
$dockercmd="& $cmd"

Invoke-Expression $dockercmd

sascfg_personal.py

SAS_config_names   = ['apro']

apro = {
    'saspath' : '/opt/sas/viya/home/SASFoundation/bin/sas_u8',
    'ssh'     : 'ssh',
    'host'    : 'localhost',  # Windows... love it, hate it.
    'luser'   : 'sasdemo',
    'port'    : 8222,
    'encoding': 'utf_8',
    'localhost': 'host.docker.internal' #Windows again!
}

Once again, thanks for your help on this.

@tomweber-sas
Copy link
Contributor

Hey, that's a lot of great work and investigation! And, first, I'm glad you've got a config that's working. Knowing it's a local network windows environment helps in the understanding too! That and Docker together, make things more complicated. While I was figuring out what I did yesterday, after getting Apro configured to be able to connect, I had an internal (SAS) consultant get up with me about a problem he was having and it turned out to be very similar to this (what a coincidence).

The first problem he had was that Docker screws up you etc/hosts file (at least on windows) replacing the loopback adapter name (127.0.0.1) from 'localhost' to 'host.docker.internal' which breaks using 'localhost', since that no longer resolves. I can't believe Docker does this, but I've seen it over and over. 127.0.0.1 localhost is a standard that's been around since networking started, so that screws up any software that tries to use the name 'localhost'. So, this may be part of the problem too!

Once we fixed that, he ran into more issues as he was running python in a container, trying to connect to Arpo (it's own container) and similarly, hostnames of the containers didn't resolve between the two so it was back to having to use IP addrs to get anything to work right.

All of this makes sense and works right when using machines that have names and ip addrs that DNS has ever heard of. That's why it worked at the pub but not at home! BTW, that's a genius idea for a testing environment :) I am still going to do a little more investigation on my side as to if/how localhost is being used in this case w.r.t. what you were saying with ephemeral ports and docker. The loopback adapter should be used between the real host and the containers running on it, but then the other thing is how my code is trying to generate code; I have slightly different code paths depending upon if I think localhost is to be used as opposed to not localhost. And, again, me trying to resolve hostnames to IP,s doesn't provide the right thing in these environments.

With your configuration that works, can you send me (you can email if you don't want to post here; Id like to see the real info) the LOGs from your upload and download; they each return a dict w/ 'LOG' and 'Success' (boolean) as the keys. That will show me which code path you happen to be going through and maybe provide a little more insight into your specific situation that may be different than mine. More data points is a good thing.

Thanks,
Tom

@royalsouvenir
Copy link
Author

royalsouvenir commented Apr 14, 2022

Hi Tom,
Not a problem. Here you go. Ultimately I think the library is resilient enough to handle these more esoteric cases. You've done a great job with it. As mentioned previously, I use SASPy almost exclusively in software development as an interface to SAS as opposed to analytics / data science and I haven't encountered an issue yet that can't be resolved.

With your colleague's issue get them to try spinning up their Python / Jupyterlab container with apro via docker-compose. You can simply reference the service name as the host in that case. Alternatively, I publish jupyterlab images on dockerhub with SAS Kernel and SASPy already configured at https://hub.docker.com/u/selerity :) You just need to add keys to each container, and change the jupyter username and uid to the same as your apro container user if you want to share home drives and I turn off stricthostkeychecking as well to make my life easier.

I'm working through another fun scenario at the moment where I'm spinning up Apro containers from a management interface product I am developing in Django and the Django app is hosted within a container :) I'm about to tackle the next step of using SASPy to validate connectivity etc.

Anyway here is the code and output logs. Thanks again :)

sascfg_personal.py

gitissuev4 = {
    'saspath' : '/opt/sas/viya/home/SASFoundation/bin/sas_u8',
    'ssh'     : 'ssh',
    'host'    : 'localhost',
    'luser'   : 'sasdemo',
    'port'    : 8222,
    'localhost': 'host.docker.internal'
}

Test Code

import tempfile
import socket
import pathlib
import filecmp
import pprint

import saspy

socket.setdefaulttimeout(300)
results = {}

with tempfile.NamedTemporaryFile(delete=False) as f:
    f.write(b'1')
    f.seek(0)
    with saspy.SASsession(cfgname='gitissuev4',cfgfile='tests/sascfg_personal.py') as sas:
        # Derive the target locations and name from the tempfile
        thefile = pathlib.Path(f.name)
        cwd = pathlib.Path.cwd()
        filename = f'{thefile.stem}{thefile.suffix}'
        local_target = cwd.joinpath(filename)
        remote_target = f'/data/{filename}'

        print(f'tempfile is {f.name}')
        print(f'local target is {local_target}')
        print(f'remote target is {remote_target}')

        results['upload'] = sas.upload(f.name, '/data')
        results['download'] = sas.download(local_target,remote_target)
        results['File comparison match'] = filecmp.cmp(local_target, f.name)
        f.close()

pprint.pprint(results)

Log Output

Pandas module not available. Setting results to HTML
SAS Connection established. Subprocess id is 19832

tempfile is C:\Users\camer\AppData\Local\Temp\tmpndpt9fle
local target is C:\Users\camer\Documents\codelocal\selerity-test4sas-client\tmpndpt9fle
remote target is /data/tmpndpt9fle
SAS Connection terminated. Subprocess id was 19832
{'File comparison match': True,
 'download': {'LOG': '\n'
                     '136  \n'
                     "137           filename saspydir '/data/tmpndpt9fle' "
                     'recfm=F encoding=binary lrecl=4096;\n'
                     '138           filename sock socket '
                     "'host.docker.internal:54719' recfm=S lrecl=4096;\n"
                     '139           /* filename sock socket '
                     "'host.docker.internal:54719' recfm=S encoding=binary; "
                     '*/\n'
                     '140           data _null_;\n'
                     '141           file sock;\n'
                     '142           infile saspydir;\n'
                     '143           input;\n'
                     '144           put _infile_;\n'
                     '145           run;\n'
                     'NOTE: The file SOCK is:\n'
                     '      Local Host Name=sas-analytics-pro,\n'
                     '      Local Host IP addr=172.17.0.2,\n'
                     '      Peer Hostname Name=host.docker.internal,\n'
                     '      Peer IP addr=192.168.65.2,Peer Name=N/A,\n'
                     '      Peer Portno=54719,Lrecl=4096,Recfm=Stream\n'
                     '\n'
                     'NOTE: The infile SASPYDIR is:\n'
                     '      Filename=/data/tmpndpt9fle,\n'
                     '      Owner Name=sasdemo,Group Name=sas,\n'
                     '      Access Permission=-rw-r--r--,\n'
                     '      Last Modified=14Apr2022:22:29:09,\n'
                     '      File Size (bytes)=1\n'
                     '\n'
                     'NOTE: 1 record was written to the file SOCK.\n'
                     '      The minimum record length was 1.\n'
                     '      The maximum record length was 1.\n'
                     'NOTE: 1 record was read from the infile SASPYDIR.\n'
                     'NOTE: DATA statement used (Total process time):\n'
                     '      real time           2.20 seconds\n'
                     '      cpu time            0.00 seconds\n'
                     '      \n'
                     '146  \n'
                     '147  \n'
                     '148  filename saspydir;\n'
                     'NOTE: Fileref SASPYDIR has been deassigned.\n'
                     '149  ',
              'Success': True},
 'upload': {'LOG': '\n'
                   '85   \n'
                   "86            filename saspydir '/data/tmpndpt9fle' "
                   "recfm=F encoding=binary lrecl=1 permission='';\n"
                   '87            filename sock socket '
                   "'host.docker.internal:54717' recfm=S lrecl=4096;\n"
                   '88            /* filename sock socket '
                   "'host.docker.internal:54717' recfm=S encoding=binary "
                   'lrecl=4096; */\n'
                   '89   \n'
                   '90            data _null_; nb = -1;\n'
                   '91            infile sock nbyte=nb;\n'
                   '92            file saspydir;\n'
                   '93            input;\n'
                   '94            put _infile_;\n'
                   '95            run;\n'
                   'NOTE: The infile SOCK is:\n'
                   '      Local Host Name=sas-analytics-pro,\n'
                   '      Local Host IP addr=172.17.0.2,\n'
                   '      Peer Hostname Name=host.docker.internal,\n'
                   '      Peer IP addr=192.168.65.2,Peer Name=N/A,\n'
                   '      Peer Portno=54717,Lrecl=4096,Recfm=Stream\n'
                   '\n'
                   'NOTE: The file SASPYDIR is:\n'
                   '      Filename=/data/tmpndpt9fle,\n'
                   '      Owner Name=sasdemo,Group Name=sas,\n'
                   '      Access Permission=-rw-r--r--,\n'
                   '      Last Modified=14Apr2022:22:29:09\n'
                   '\n'
                   'NOTE: 1 record was read from the infile SOCK.\n'
                   '      The minimum record length was 1.\n'
                   '      The maximum record length was 1.\n'
                   'NOTE: 1 record was written to the file SASPYDIR.\n'
                   'NOTE: DATA statement used (Total process time):\n'
                   '      real time           2.13 seconds\n'
                   '      cpu time            0.01 seconds\n'
                   '      \n'
                   '96   \n'
                   '97            filename saspydir;\n'
                   'NOTE: Fileref SASPYDIR has been deassigned.\n'
                   '98            filename sock;\n'
                   'NOTE: Fileref SOCK has been deassigned.\n'
                   '99   \n'
                   '100  \n'
                   '101  \n'
                   '102  ',
            'Success': True}}

@tomweber-sas
Copy link
Contributor

Ok cool, these are what I was wondering about:

                     '      Local Host Name=sas-analytics-pro,\n'
                     '      Local Host IP addr=172.17.0.2,\n'
                     '      Peer Hostname Name=host.docker.internal,\n'
                     '      Peer IP addr=192.168.65.2,Peer Name=N/A,\n'

I also have similar, where the ip in Apro is, obviously, the 172.17.0.2 (or there abouts), and the ip for the host machine is the real IP of it, as opposed to the loopback address or something. What I found, monitoring my network adapters is that nothing is going through the loopback adapter (127.0.0.1), but nothing is going through the real network adapter either (associated w/ that address; eth0). Rather it's going through a 'docker0' network and another virtual one vethnnnn. I believe those are both things set up from docker, so I don't see any traffic actually going over the actual network; all just local to the host machine. That's what I wanted to look into, given it's using the real ip of the host machine (this is a 'remote' connection from machine to machine); but it (docker) knows it's all local. So that's good.

So, I think we're good? I've been multitasking too much to be sure; is there anything we need to do at this point? Anything you still need?

Thanks!
Tom

@royalsouvenir
Copy link
Author

Hi,
I think we're all good. I would suggest maybe this is just an update to the docs to the localhost parameter and potentially a test for it (It would have to be skipped though for most test runs I would assume)? Let me know if you want a PR and happy to oblige. I'll email the Apro team in R&D to let them know about the docs on the Apro site. We're in fairly regular contact with them. Happy to have this closed though. Thanks again for your help in troubleshooting.

@tomweber-sas
Copy link
Contributor

Yes, that sounds like a plan. BTW, who are you working with; the 'apro team'? I'll see about getting the doc updated. I don't need a PR or anything, can do it here. Just to be sure I'm not missing anything, you have 'localhost': 'host.docker.internal' and for that to have been resolved to your correct IP for your host machine (where python and docker are running), did you have that name and associated IP in your etc/hosts file? Or, is that just a special host name that docker resolves itself to the host IP? I googled and found the following in some docker documentation which seems to imply that on windows this name is resolved (it's not on my linux machine), though I don't know if it's because docker added it to your etc/hosts, or if somehow docker catches it and provides the right IP for the name.

From this link: https://docs.docker.com/desktop/windows/networking/

The host has a changing IP address (or none if you have no network access). We recommend that you
connect to the special DNS name host.docker.internal which resolves to the internal IP address used by
the host. This is for development purpose and will not work in a production environment outside of 
Docker Desktop for Windows.

I just want to be sure I understand your use of localhost key. It really does seem to be the same as the first reason I implemented it: DNS on your client machine doesn't resolve to the correct IP (cuz it was a local widows network with no DNS support for these local, internal host names - same as mine).

So, what localhost key is needed for is to provide the right IP for the python client machine when DNS doesn't do it.

Thanks again!
Tom

@royalsouvenir
Copy link
Author

Hi @tomweber-sas ,
I work with a SAS partner and we were involved in initial testing of the apro container originally with Mark S before he retired and now with Deva. We provide SAS/Viya reselling, hosting and administration and have some custom offerings around Apro for personal and small customers.

You are correct about the hosts file. Docker Desktop adds the following (Not sure what it adds on Linux though. There is a beta version of the product for linux now).

# Added by Docker Desktop
xx,x,x,xxx host.docker.internal
xx,x,x,xxx gateway.docker.internal
# To allow the same kube context to work on the host and the container:
127.0.0.1 kubernetes.docker.internal
# End of section

where x.x.x.xxx is the preferred IPv4 address of the host.

The steps I have need in my sascfg_personal.py configuration are as follows:

  • Use localhost for the host key.
  • Set the localhost key to host.docker.internal
  • Set the port key to the publish port of the apro container.

I think the documentation on the SASPy side is minimal, maybe just a mention of When using Docker Desktop or something. I think it's more on the Apro side and it looks like they have just tested with Linux / Docker engine only which doesn't have this requirement.

Thanks again,
Cam

@tomweber-sas
Copy link
Contributor

tomweber-sas commented Apr 19, 2022

Great, thanks for confirming that; docker does put that in the etc/host along with hijacking the loopback adapter from all other software :)

Speaking of which, in your config, you are using the IP of Apro as the host key, not 'localhost', as that was the original problem and can't work, right? 172.17.0.2 or whatever number it rolls to (depending on if you have other containers running) is what host should be. Oh, but wait, I see, it's the same as my remote connection. If you use 'localhost' (and is that still defined as 127.0.0.1 in your etc/hosts, cuz it looks like docker removed that), then you would have to set the port to other than 22; whatever you exposed it to on the client machine; 8222 in your case - it was 2222 for me. So yes, that would work too.

But again, the only reason you would need to use the localhost key, is if DNS can't resolve your hostname (of your client machine; python machine) to the correct IP address. That is likely if you are on a home network, or if your Python process is running in another docker container (like my colleague here), or the next case I haven't seen yet. That's why you needed it set at home but not at the pub.

So, as for doc changes, I thing the following are what I'm seeing:

  1. update the localhost key doc (in STDIO over SSH config doc) to mention the Docker case as well as home network.
  2. update the APro doc to mention that you may need to use the localhost key in these circumstances.
  3. update the APro doc to spell SASPy correctly throughout

That sound correct to you?
Thanks again!
Tom

@royalsouvenir
Copy link
Author

Hi Tom,
In my sascfg_personal.py config I am using "localhost" for the host key. On Windows, I can't get it to recognise the IP address of the container. This may also be dependent on the type of network a user launches the apro image into within Docker. The default network docker0 is a bridge network which maps the Windows hosts file to /etc/resolv.conf in the container. Docker also introduced it's own internal DNS a few versions back. I haven't tested other supported network types (overlay and custom) so I think the docs should just state "you may need to xxxx" in all it's ambiguous glory :)

As for everything else, I think you've nailed it. :)

Thanks again,
Cam

@tomweber-sas
Copy link
Contributor

I've gotten in touch w/ the doc write for APRO; already fixed SASPy spelling :) Gave them some info for adding in localhost config option and pointing right to that doc in saspy. I will make mention of docker in that doc too for localhost config option.
I'm thinking we can close this one?

Thanks again for all the help!
Tom

@royalsouvenir
Copy link
Author

Hi Tom,
I agree. Also as an FYI, I referenced this rather enjoyable conversation in a company blog post. Thanks again for looking into this. https://seleritysas.com/blog/2022/04/20/saspy-configuration-with-sas-analytics-pro-cloud-native/

@tomweber-sas
Copy link
Contributor

That's awesome! I'm gonna send the link to our doc writer! :) maybe we should just reference it from ours!
Thanks,
Tom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants