#3664 Recreate timeouted session
Merged a year ago by tkopecek. Opened a year ago by tkopecek.
tkopecek/koji pr3543b  into  master

@@ -0,0 +1,13 @@ 

+ -- upgrade script to migrate the Koji database schema

+ -- from version 1.31 to 1.32

+ 

+ BEGIN;

+ 

+     -- fix duplicate extension in archivetypes

+     UPDATE archivetypes SET extensions = 'vhdx.gz vhdx.xz' WHERE name = 'vhdx-compressed';

+ 

+     -- for tag if session is closed or not

+     ALTER TABLE sessions ADD COLUMN closed BOOLEAN NOT NULL DEFAULT FALSE;

+     ALTER TABLE sessions ADD CONSTRAINT no_closed_exclusive CHECK (closed IS FALSE OR "exclusive" IS NULL);

+     ALTER TABLE sessions DROP CONSTRAINT exclusive_expired_sane;

+ COMMIT;

file modified
+3 -2
@@ -119,10 +119,11 @@ 

  	start_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),

  	update_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),

  	exclusive BOOLEAN CHECK (exclusive),

+ 	closed BOOLEAN NOT NULL DEFAULT FALSE,

  	CONSTRAINT no_exclusive_subsessions CHECK (

  		master IS NULL OR "exclusive" IS NULL),

- 	CONSTRAINT exclusive_expired_sane CHECK (

- 		expired IS FALSE OR "exclusive" IS NULL),

+ 	CONSTRAINT no_closed_exclusive CHECK (

+ 		closed IS FALSE OR "exclusive" IS NULL),

  	UNIQUE (user_id,exclusive)

  ) WITHOUT OIDS;

  CREATE INDEX sessions_master ON sessions(master);

file modified
+113 -20
@@ -2424,7 +2424,13 @@ 

  

  class ClientSession(object):

  

-     def __init__(self, baseurl, opts=None, sinfo=None):

+     def __init__(self, baseurl, opts=None, sinfo=None, auth_method=None):

+         """

+         :param baseurl str: hub url

+         :param dict opts: dictionary with content varying according to authentication method

+         :param dict sinfo: session info returned by login method

+         :param dict auth_method: method for reauthentication, shouldn't be ever set manually

+         """

          assert baseurl, "baseurl argument must not be empty"

          if opts is None:

              opts = {}
@@ -2444,6 +2450,8 @@ 

          self.rsession = None

          self.new_session()

          self.opts.setdefault('timeout', DEFAULT_REQUEST_TIMEOUT)

+         self.exclusive = False

+         self.auth_method = auth_method

  

      @property

      def multicall(self):
@@ -2480,8 +2488,21 @@ 

              self.callnum = 0

          self.sinfo = sinfo

  

-     def login(self, opts=None):

-         sinfo = self.callMethod('login', self.opts['user'], self.opts['password'], opts)

+     def login(self, opts=None, renew=False):

+         """

+         Username/password based login method

+ 

+         :param dict opts: dict used by hub "login" call, currently can

+                           contain only "host_ip" key.

+         :returns bool True: success or raises exception

+         """

+         # store calling parameters

+         self.auth_method = {'method': 'login', 'kwargs': {'opts': opts}}

+         kwargs = {'opts': opts}

+         if renew:

+             kwargs['renew'] = True

+             kwargs['exclusive'] = self.exclusive

+         sinfo = self.callMethod('login', self.opts['user'], self.opts['password'], **kwargs)

          if not sinfo:

              return False

          self.setSession(sinfo)
@@ -2491,14 +2512,32 @@ 

      def subsession(self):

          "Create a subsession"

          sinfo = self.callMethod('subsession')

-         return type(self)(self.baseurl, self.opts, sinfo)

+         return type(self)(self.baseurl, opts=self.opts, sinfo=sinfo, auth_method=self.auth_method)

  

      def gssapi_login(self, principal=None, keytab=None, ccache=None,

-                      proxyuser=None, proxyauthtype=None):

+                      proxyuser=None, proxyauthtype=None, renew=False):

+         """

+         GSSAPI/Kerberos login method

+ 

+         :param str principal: Kerberos principal

+         :param str keytab: path to keytab file

+         :param str ccache: path to ccache file/dir

+         :param str proxyuser: name of proxied user (e.g. forwarding by web ui)

+         :param int proxyauthtype: AUTHTYPE used by proxied user (can be different from ours)

+         :returns bool True: success or raises exception

+         """

          if not reqgssapi:

              raise PythonImportError(

                  "Please install python-requests-gssapi to use GSSAPI."

              )

+         # store calling parameters

+         self.auth_method = {

+             'method': 'gssapi_login',

+             'kwargs': {

+                 'principal': principal, 'keytab': keytab, 'ccache': ccache, 'proxyuser': proxyuser,

+                 'proxyauthtype': proxyauthtype

+             }

+         }

          # force https

          old_baseurl = self.baseurl

          uri = six.moves.urllib.parse.urlsplit(self.baseurl)
@@ -2540,6 +2579,9 @@ 

                  # For this case we're now using retry=False and test errors for

                  # this exact usecase.

                  kwargs = {'proxyuser': proxyuser}

+                 if renew:

+                     kwargs['renew'] = True

+                     kwargs['exclusive'] = self.exclusive

                  if proxyauthtype is not None:

                      kwargs['proxyauthtype'] = proxyauthtype

                  for tries in range(self.opts.get('max_retries', 30)):
@@ -2587,7 +2629,26 @@ 

          self.authtype = AUTHTYPES['GSSAPI']

          return True

  

-     def ssl_login(self, cert=None, ca=None, serverca=None, proxyuser=None, proxyauthtype=None):

+     def ssl_login(self, cert=None, ca=None, serverca=None, proxyuser=None, proxyauthtype=None,

+                   renew=False):

+         """

+         SSL cert based login

+ 

+         :param str cert: path to SSL certificate

+         :param str ca: deprecated, not used anymore

+         :param str serverca: path for CA public cert, otherwise system-wide CAs are used

+         :param str proxyuser: name of proxied user (e.g. forwarding by web ui)

+         :param int proxyauthtype: AUTHTYPE used by proxied user (can be different from ours)

+         :returns bool: success

+         """

+         # store calling parameters

+         self.auth_method = {

+             'method': 'ssl_login',

+             'kwargs': {

+                 'cert': cert, 'ca': ca, 'serverca': serverca,

+                 'proxyuser': proxyuser, 'proxyauthtype': proxyauthtype,

+             }

+         }

          cert = cert or self.opts.get('cert')

          serverca = serverca or self.opts.get('serverca')

          if cert is None:
@@ -2617,6 +2678,9 @@ 

          e_str = None

          try:

              kwargs = {'proxyuser': proxyuser}

+             if renew:

+                 kwargs['renew'] = True

+                 kwargs['exclusive'] = self.exclusive

              if proxyauthtype is not None:

                  kwargs['proxyauthtype'] = proxyauthtype

              sinfo = self._callMethod('sslLogin', [], kwargs)
@@ -2628,6 +2692,7 @@ 

              sinfo = None

          finally:

              self.opts = old_opts

+ 

          if not sinfo:

              err = 'unable to obtain a session'

              if e_str:
@@ -2680,8 +2745,6 @@ 

          self.new_session()

  

          # forget our login session, if any

-         if not self.logged_in:

-             return

          self.setSession(None)

  

      # we've had some trouble with this method causing strange problems
@@ -2707,24 +2770,28 @@ 

              return self._prepUpload(*args, **kwargs)

          args = encode_args(*args, **kwargs)

          headers = []

-         if self.logged_in:

+ 

+         sinfo = None

+         if getattr(self, 'sinfo') is not None:

+             # send sinfo in headers if we have it

+             # still needed if not logged in for renewal case

              sinfo = self.sinfo.copy()

              sinfo['callnum'] = self.callnum

              self.callnum += 1

-             if sinfo.get('header-auth'):

-                 handler = self.baseurl

-                 headers += [

-                     ('Koji-Session-Id', str(self.sinfo['session-id'])),

-                     ('Koji-Session-Key', str(self.sinfo['session-key'])),

-                     ('Koji-Session-Callnum', str(sinfo['callnum'])),

-                 ]

-             else:

-                 # old server

-                 handler = "%s?%s" % (self.baseurl, six.moves.urllib.parse.urlencode(sinfo))

+             headers += [

+                 ('Koji-Session-Id', str(sinfo['session-id'])),

+                 ('Koji-Session-Key', str(sinfo['session-key'])),

+                 ('Koji-Session-Callnum', str(sinfo['callnum'])),

+             ]

+ 

+         if self.logged_in and not self.sinfo.get('header-auth'):

+             # old server

+             handler = "%s?%s" % (self.baseurl, six.moves.urllib.parse.urlencode(sinfo))

          elif name == 'sslLogin':

              handler = self.baseurl + '/ssllogin'

          else:

              handler = self.baseurl

+ 

          request = dumps(args, name, allow_none=1)

          if six.PY3:

              # For python2, dumps() without encoding specified means return a str
@@ -2833,9 +2900,30 @@ 

              result = result[0]

          return result

  

+     def _renew_session(self):

+         """Renew expirated session or subsession."""

+         if not hasattr(self, 'auth_method'):

+             raise GenericError("Missing info for reauthentication")

+         auth_method = getattr(self, self.auth_method['method'])

+         args = self.auth_method.get('args', [])

+         kwargs = self.auth_method.get('kwargs', {})

+         kwargs['renew'] = True

+         self.logged_in = False

+         auth_method(*args, **kwargs)

+ 

+     def renew_expired_session(func):

+         """Decorator to renew expirated session or subsession."""

+         def _renew_expired_session(self, *args, **kwargs):

+             try:

+                 return func(self, *args, **kwargs)

+             except AuthExpired:

+                 self._renew_session()

+                 return func(self, *args, **kwargs)

+         return _renew_expired_session

+ 

+     @renew_expired_session

      def _callMethod(self, name, args, kwargs=None, retry=True):

          """Make a call to the hub with retries and other niceties"""

- 

          if self.multicall:

              if kwargs is None:

                  kwargs = {}
@@ -3183,6 +3271,11 @@ 

          result = self.callMethod('downloadTaskOutput', taskID, fileName, **dlopts)

          return base64.b64decode(result)

  

+     def exclusiveSession(self, force=False):

+         """Make this session exclusive"""

+         self._callMethod('exclusiveSession', {'force': force})

+         self.exclusive = True

+ 

  

  class MultiCallHack(object):

      """Workaround of a terribly overloaded namespace

file modified
+75 -43
@@ -56,6 +56,8 @@ 

      'repoProblem',

  ]

  

+ AUTH_METHODS = ['login', 'sslLogin']

+ 

  logger = logging.getLogger('koji.auth')

  

  
@@ -82,8 +84,8 @@ 

          args = environ.get('QUERY_STRING', '')

          # prefer new header-based sessions

          if 'HTTP_KOJI_SESSION_ID' in environ:

-             id = int(environ['HTTP_KOJI_SESSION_ID'])

-             key = environ['HTTP_KOJI_SESSION_KEY']

+             self.id = int(environ['HTTP_KOJI_SESSION_ID'])

+             self.key = environ['HTTP_KOJI_SESSION_KEY']

              try:

                  callnum = int(environ['HTTP_KOJI_CALLNUM'])

              except KeyError:
@@ -96,8 +98,8 @@ 

                  return

              args = urllib.parse.parse_qs(args, strict_parsing=True)

              try:

-                 id = int(args['session-id'][0])

-                 key = args['session-key'][0]

+                 self.id = int(args['session-id'][0])

+                 self.key = args['session-key'][0]

              except KeyError as field:

                  raise koji.AuthError('%s not specified in session args' % field)

              try:
@@ -118,24 +120,28 @@ 

          columns, aliases = zip(*fields)

  

          query = QueryProcessor(tables=['sessions'], columns=columns, aliases=aliases,

-                                clauses=['id = %(id)i', 'key = %(key)s', 'hostip = %(hostip)s'],

-                                values={'id': id, 'key': key, 'hostip': hostip},

+                                clauses=['id = %(id)i', 'key = %(key)s', 'hostip = %(hostip)s',

+                                         'closed IS FALSE'],

+                                values={'id': self.id, 'key': self.key, 'hostip': hostip},

                                 opts={'rowlock': True})

          session_data = query.executeOne(strict=False)

          if not session_data:

              query = QueryProcessor(tables=['sessions'], columns=['key', 'hostip'],

-                                    clauses=['id = %(id)i'], values={'id': id})

+                                    clauses=['id = %(id)i'], values={'id': self.id})

              row = query.executeOne(strict=False)

              if row:

-                 if key != row['key']:

-                     logger.warning("Session ID %s is not related to session key %s.", id, key)

+                 if self.key != row['key']:

+                     logger.warning("Session ID %s is not related to session key %s.",

+                                    self.id, self.key)

                  elif hostip != row['hostip']:

-                     logger.warning("Session ID %s is not related to host IP %s.", id, hostip)

+                     logger.warning("Session ID %s is not related to host IP %s.", self.id, hostip)

              raise koji.AuthError('Invalid session or bad credentials')

  

          # check for expiration

          if session_data['expired']:

-             raise koji.AuthExpired('session "%i" has expired' % id)

+             if getattr(context, 'method') not in AUTH_METHODS:

+                 raise koji.AuthExpired('session "%s" has expired' % self.id)

+ 

          # check for callnum sanity

          if callnum is not None:

              try:
@@ -145,8 +151,7 @@ 

              lastcall = session_data['callnum']

              if lastcall is not None:

                  if lastcall > callnum:

-                     raise koji.SequenceError("%d > %d (session %d)"

-                                              % (lastcall, callnum, id))

+                     raise koji.SequenceError("%s > %s (session %s)" % (lastcall, callnum, self.id))

                  elif lastcall == callnum:

                      # Some explanation:

                      # This function is one of the few that performs its own commit.
@@ -159,8 +164,11 @@ 

                      method = getattr(context, 'method', 'UNKNOWN')

                      if method not in RetryWhitelist:

                          raise koji.RetryError(

-                             "unable to retry call %d (method %s) for session %d"

-                             % (callnum, method, id))

+                             "unable to retry call %s (method %s) for session %s" %

+                             (callnum, method, self.id))

+ 

+         if session_data['expired']:

+             return

  

          # read user data

          # historical note:
@@ -182,7 +190,7 @@ 

              # see if an exclusive session exists

              query = QueryProcessor(tables=['sessions'], columns=['id'],

                                     clauses=['user_id=%(user_id)s', 'exclusive = TRUE',

-                                             'expired = FALSE'],

+                                             'closed = FALSE'],

                                     values=session_data)

              excl_id = query.singleValue(strict=False)

  
@@ -200,21 +208,19 @@ 

  

          # update timestamp

          update = UpdateProcessor('sessions', rawdata={'update_time': 'NOW()'},

-                                  clauses=['id = %(id)i'], values={'id': id})

+                                  clauses=['id = %(id)i'], values={'id': self.id})

          update.execute()

          context.cnx.commit()

          # update callnum (this is deliberately after the commit)

          # see earlier note near RetryError

          if callnum is not None:

              update = UpdateProcessor('sessions', data={'callnum': callnum},

-                                      clauses=['id = %(id)i'], values={'id': id})

+                                      clauses=['id = %(id)i'], values={'id': self.id})

              update.execute()

              # we only want to commit the callnum change if there are other commits

              context.commit_pending = False

  

          # record the login data

-         self.id = id

-         self.key = key

          self.hostip = hostip

          self.callnum = callnum

          self.user_id = session_data['user_id']
@@ -280,7 +286,7 @@ 

          if result['status'] != koji.USER_STATUS['NORMAL']:

              raise koji.AuthError('logins by %s are not allowed' % result['name'])

  

-     def login(self, user, password, opts=None):

+     def login(self, user, password, opts=None, renew=False, exclusive=False):

          """create a login session"""

          if opts is None:

              opts = {}
@@ -301,7 +307,9 @@ 

          self.checkLoginAllowed(user_id)

  

          # create session and return

-         sinfo = self.createSession(user_id, hostip, koji.AUTHTYPES['NORMAL'])

+         sinfo = self.createSession(user_id, hostip, koji.AUTHTYPES['NORMAL'], renew=renew)

+         if sinfo and exclusive and not self.exclusive:

+             self.makeExclusive()

          context.cnx.commit()

          return sinfo

  
@@ -326,7 +334,7 @@ 

  

          return (local_ip, local_port, remote_ip, remote_port)

  

-     def sslLogin(self, proxyuser=None, proxyauthtype=None):

+     def sslLogin(self, proxyuser=None, proxyauthtype=None, renew=False, exclusive=None):

  

          """Login into brew via SSL. proxyuser name can be specified and if it is

          allowed in the configuration file then connection is allowed to login as
@@ -404,7 +412,9 @@ 

  

          hostip = self.get_remote_ip()

  

-         sinfo = self.createSession(user_id, hostip, authtype)

+         sinfo = self.createSession(user_id, hostip, authtype, renew=renew)

+         if sinfo and exclusive and not self.exclusive:

+             self.makeExclusive()

          return sinfo

  

      def makeExclusive(self, force=False):
@@ -420,16 +430,17 @@ 

          query = QueryProcessor(tables=['users'], columns=['id'], clauses=['id=%(user_id)s'],

                                 values={'user_id': user_id}, opts={'rowlock': True})

          query.execute()

-         # check that no other sessions for this user are exclusive

+         # check that no other sessions for this user are exclusive (including expired)

          query = QueryProcessor(tables=['sessions'], columns=['id'],

-                                clauses=['user_id=%(user_id)s', 'expired = FALSE',

+                                clauses=['user_id=%(user_id)s', 'closed = FALSE',

                                          'exclusive = TRUE'],

                                 values={'user_id': user_id}, opts={'rowlock': True})

          excl_id = query.singleValue(strict=False)

          if excl_id:

              if force:

-                 # expire the previous exclusive session and try again

-                 update = UpdateProcessor('sessions', data={'expired': True, 'exclusive': None},

+                 # close the previous exclusive sessions and try again

+                 update = UpdateProcessor('sessions',

+                                          data={'expired': True, 'exclusive': None, 'closed': True},

                                           clauses=['id=%(excl_id)s'], values={'excl_id': excl_id},)

                  update.execute()

              else:
@@ -449,7 +460,7 @@ 

          context.cnx.commit()

  

      def logout(self, session_id=None):

-         """expire a login session"""

+         """close a login session"""

          if not self.logged_in:

              # XXX raise an error?

              raise koji.AuthError("Not logged in")
@@ -464,7 +475,8 @@ 

              ses_id = session_id

          else:

              ses_id = self.id

-         update = UpdateProcessor('sessions', data={'expired': True, 'exclusive': None},

+         update = UpdateProcessor('sessions',

+                                  data={'expired': True, 'exclusive': None, 'closed': True},

                                   clauses=['id = %(id)i OR master = %(id)i'],

                                   values={'id': ses_id})

          update.execute()
@@ -473,17 +485,18 @@ 

              self.logged_in = False

  

      def logoutChild(self, session_id):

-         """expire a subsession"""

+         """close a subsession"""

          if not self.logged_in:

              # XXX raise an error?

              raise koji.AuthError("Not logged in")

-         update = UpdateProcessor('sessions', data={'expired': True, 'exclusive': None},

+         update = UpdateProcessor('sessions',

+                                  data={'expired': True, 'exclusive': None, 'closed': True},

                                   clauses=['id = %(session_id)i', 'master = %(master)i'],

                                   values={'session_id': session_id, 'master': self.id})

          update.execute()

          context.cnx.commit()

  

-     def createSession(self, user_id, hostip, authtype, master=None):

+     def createSession(self, user_id, hostip, authtype, master=None, renew=False):

          """Create a new session for the given user.

  

          Return a map containing the session-id and session-key.
@@ -495,14 +508,34 @@ 

                           ''.join([random.choice(alnum) for x in range(1, 20)]))

          # use sha? sha.new(phrase).hexdigest()

  

-         # get a session id

-         session_id = nextval('sessions_id_seq')

- 

-         # add session id to database

-         insert = InsertProcessor('sessions',

-                                  data={'id': session_id, 'user_id': user_id, 'key': key,

-                                        'hostip': hostip, 'authtype': authtype, 'master': master})

-         insert.execute()

+         if renew and self.id is not None:

+             # just update key

+             session_id = self.id

+             self.key = key

+             if self.master:

+                 # check if master session died meanwhile (expired is ok)

+                 query = QueryProcessor(tables=['sessions'],

+                                        clauses=['id = %(master_id)d', 'closed IS FALSE'],

+                                        values={'master_id': self.master},

+                                        opts={'countOnly': True})

+                 if query.executeOne() == 0:

+                     return None

+ 

+             update = UpdateProcessor('sessions',

+                                      clauses=['id=%(id)i'],

+                                      rawdata={'update_time': 'NOW()'},

+                                      data={'key': self.key, 'expired': False},

+                                      values={'id': self.id})

+             update.execute()

+         else:

+             # get a session id

+             session_id = nextval('sessions_id_seq')

+             # add session id to database

+             insert = InsertProcessor('sessions',

+                                      data={'id': session_id, 'user_id': user_id, 'key': key,

+                                            'hostip': hostip, 'authtype': authtype,

+                                            'master': master})

+             insert.execute()

          context.cnx.commit()

  

          # return session info
@@ -519,8 +552,7 @@ 

          master = self.master

          if master is None:

              master = self.id

-         return self.createSession(self.user_id, self.hostip, self.authtype,

-                                   master=master)

+         return self.createSession(self.user_id, self.hostip, self.authtype, master=master)

  

      def getPerms(self):

          if not self.logged_in:

file modified
+10 -17
@@ -140,7 +140,8 @@ 

          query = self.queries[0]

          self.assertEqual(query.tables, ['sessions'])

          self.assertEqual(query.joins, None)

-         self.assertEqual(query.clauses, ['hostip = %(hostip)s', 'id = %(id)i', 'key = %(key)s'])

+         self.assertEqual(query.clauses, ['closed IS FALSE', 'hostip = %(hostip)s', 'id = %(id)i',

+                                          'key = %(key)s'])

          self.assertEqual(query.columns, ['authtype', 'callnum', 'exclusive', 'expired', 'master',

                                           'start_time', "date_part('epoch', start_time)",

                                           'update_time', "date_part('epoch', update_time)",
@@ -160,7 +161,7 @@ 

          query = self.queries[2]

          self.assertEqual(query.tables, ['sessions'])

          self.assertEqual(query.joins, None)

-         self.assertEqual(query.clauses, ['exclusive = TRUE', 'expired = FALSE',

+         self.assertEqual(query.clauses, ['closed = FALSE', 'exclusive = TRUE',

                                           'user_id=%(user_id)s'])

          self.assertEqual(query.columns, ['id'])

  
@@ -190,7 +191,8 @@ 

          query = self.queries[0]

          self.assertEqual(query.tables, ['sessions'])

          self.assertEqual(query.joins, None)

-         self.assertEqual(query.clauses, ['hostip = %(hostip)s', 'id = %(id)i', 'key = %(key)s'])

+         self.assertEqual(query.clauses, ['closed IS FALSE', 'hostip = %(hostip)s', 'id = %(id)i',

+                                          'key = %(key)s'])

          self.assertEqual(query.columns, ['authtype', 'callnum', 'exclusive', 'expired', 'master',

                                           'start_time', "date_part('epoch', start_time)",

                                           'update_time', "date_part('epoch', update_time)",
@@ -210,7 +212,7 @@ 

          query = self.queries[2]

          self.assertEqual(query.tables, ['sessions'])

          self.assertEqual(query.joins, None)

-         self.assertEqual(query.clauses, ['exclusive = TRUE', 'expired = FALSE',

+         self.assertEqual(query.clauses, ['closed = FALSE', 'exclusive = TRUE',

                                           'user_id=%(user_id)s'])

          self.assertEqual(query.columns, ['id'])

  
@@ -434,7 +436,7 @@ 

          self.assertEqual(update.table, 'sessions')

          self.assertEqual(update.values, {'id': 123, 'id': 123})

          self.assertEqual(update.clauses, ['id = %(id)i OR master = %(id)i'])

-         self.assertEqual(update.data, {'expired': True, 'exclusive': None})

+         self.assertEqual(update.data, {'closed': True, 'expired': True, 'exclusive': None})

          self.assertEqual(update.rawdata, {})

  

      def test_logoutChild_not_logged(self):
@@ -460,7 +462,7 @@ 

          self.assertEqual(update.table, 'sessions')

          self.assertEqual(update.values, {'session_id': 111, 'master': 123})

          self.assertEqual(update.clauses, ['id = %(session_id)i', 'master = %(master)i'])

-         self.assertEqual(update.data, {'expired': True, 'exclusive': None})

+         self.assertEqual(update.data, {'expired': True, 'exclusive': None, 'closed': True})

          self.assertEqual(update.rawdata, {})

  

      def test_makeExclusive_not_master(self):
@@ -513,7 +515,7 @@ 

          query = self.queries[4]

          self.assertEqual(query.tables, ['sessions'])

          self.assertEqual(query.joins, None)

-         self.assertEqual(query.clauses, ['exclusive = TRUE', 'expired = FALSE',

+         self.assertEqual(query.clauses, ['closed = FALSE', 'exclusive = TRUE',

                                           'user_id=%(user_id)s'])

          self.assertEqual(query.columns, ['id'])

          self.assertEqual(query.values, {'user_id': 1})
@@ -525,7 +527,7 @@ 

          self.assertEqual(update.table, 'sessions')

          self.assertEqual(update.values, {'excl_id': 123})

          self.assertEqual(update.clauses, ['id=%(excl_id)s'])

-         self.assertEqual(update.data, {'expired': True, 'exclusive': None})

+         self.assertEqual(update.data, {'expired': True, 'exclusive': None, 'closed': True})

          self.assertEqual(update.rawdata, {})

  

          update = self.updates[3]
@@ -668,15 +670,6 @@ 

          self.assertEqual(query.clauses, ['active = TRUE', 'user_id=%(user_id)s'])

          self.assertEqual(query.columns, ['name'])

  

-     def test_logout_not_logged(self):

-         s, cntext = self.get_session()

- 

-         # not logged

-         s.logged_in = False

-         with self.assertRaises(koji.AuthError) as ex:

-             s.logout()

-         self.assertEqual("Not logged in", str(ex.exception))

- 

      @mock.patch('koji.auth.context')

      def test_logout_logged_not_owner(self, context):

          s, cntext = self.get_session()

#3543 with additonal changes, see discussion in #3543

rebased onto 19987b174aa6f4090136f0e9d1973fef454e501a

a year ago

2 new commits added

  • fix tests
  • fix decorator
a year ago

Metadata Update from @tkopecek:
- Pull-request tagged with: testing-ready

a year ago

This syntax is not supported by py2. Auth.py could be installed on py2 instances.

This syntax is not supported by py2. Auth.py could be installed on py2 instances.

fstring is not supported by py2. Auth.py could be installed on py2 instances.

fstring is not supported by py2. Auth.py could be installed on py2 instances.

It is a server-side module, so it is always py3. What we should do is to move it to hub subpackage. But that's for another PR.

1 new commit added

  • remove f-strings for py2 compatibility
a year ago

rebased onto 3a81253

a year ago

I'm confused by this commit

commit 40780155a228326c9b950b71a1509003b56bead8 (HEAD -> pagure/pr/3664)
Author: Tomas Kopecek <tkopecek@redhat.com>
Date:   Wed Jan 25 15:23:33 2023 +0100

    remove staticmethod due to py2.7 compatibility

diff --git a/koji/__init__.py b/koji/__init__.py
index 5dbd8535..70354b48 100644
--- a/koji/__init__.py
+++ b/koji/__init__.py
@@ -2911,7 +2911,6 @@ class ClientSession(object):
         self.logged_in = False
         auth_method(*args, **kwargs)

-    @staticmethod
     def renew_expired_session(func):
         """Decorator to renew expirated session or subsession."""
         def _renew_expired_session(self, *args, **kwargs):

AFAICT, using staticmethod as a decorator is supported in 2.7. I think you'd have to go much further back to lose that (and even if you did, you'd just switch to using the function invocation for staticmethod).

It has a different behaviour in 2.7 and 3.x. In 2.7 I'll get unbound call aka "TypeError: 'staticmethod' object is not callable" while in 3.x it works as expected. Just removing staticmethod works. I was a bit inclined to rip it out of the class completely to be clear with its "staticness".

Metadata Update from @mfilip:
- Pull-request tagged with: testing-done

a year ago

Commit d5ed48f fixes this pull-request

Pull-Request has been merged by tkopecek

a year ago