17170: Add interactive_session_started flag to containers.
authorTom Clegg <tom@curii.com>
Tue, 26 Jan 2021 19:53:16 +0000 (14:53 -0500)
committerTom Clegg <tom@curii.com>
Tue, 26 Jan 2021 20:28:09 +0000 (15:28 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

doc/api/methods/containers.html.textile.liquid
lib/controller/localdb/container_gateway.go
lib/controller/localdb/container_gateway_test.go
sdk/go/arvados/container.go
services/api/app/models/container.rb
services/api/db/migrate/20210126183521_add_interactive_session_started_to_containers.rb [new file with mode: 0644]
services/api/db/structure.sql
services/api/test/unit/container_test.rb

index 8a7ebc36e5b33613fc6947c88f833ad093aaf351..096a1fcaa9b628e6d5907ac33e8c6625f1114fd7 100644 (file)
@@ -57,6 +57,8 @@ Generally this will contain additional keys that are not present in any correspo
 |auth_uuid|string|UUID of a token to be passed into the container itself, used to access Keep-backed mounts, etc.  Automatically assigned.|Null if state∉{"Locked","Running"} or if @runtime_token@ was provided.|
 |locked_by_uuid|string|UUID of a token, indicating which dispatch process changed state to Locked. If null, any token can be used to lock. If not null, only the indicated token can modify this container.|Null if state∉{"Locked","Running"}|
 |runtime_token|string|A v2 token to be passed into the container itself, used to access Keep-backed mounts, etc.|Not returned in API responses.  Reset to null when state is "Complete" or "Cancelled".|
+|gateway_address|string|Address (host:port) of gateway server.|Internal use only.|
+|interactive_session_started|boolean|Indicates whether @arvados-client shell@ has been used to run commands in the container, which may have altered the container's behavior and output.||
 
 h2(#container_states). Container states
 
index b8d85516a2369366b6daed5d8aa064ed722f29e9..e509278773ae288f9368e78e98ee059f656560a8 100644 (file)
@@ -38,12 +38,12 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
        if err != nil {
                return
        }
+       ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
        if !user.IsAdmin || !conn.cluster.Containers.ShellAccess.Admin {
                if !conn.cluster.Containers.ShellAccess.User {
                        err = httpserver.ErrorWithStatus(errors.New("shell access is disabled in config"), http.StatusServiceUnavailable)
                        return
                }
-               ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
                var crs arvados.ContainerRequestList
                crs, err = conn.railsProxy.ContainerRequestList(ctxRoot, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"container_uuid", "=", opts.UUID}}})
                if err != nil {
@@ -153,6 +153,20 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
                netconn.Close()
                return
        }
+
+       if !ctr.InteractiveSessionStarted {
+               _, err = conn.railsProxy.ContainerUpdate(ctxRoot, arvados.UpdateOptions{
+                       UUID: opts.UUID,
+                       Attrs: map[string]interface{}{
+                               "interactive_session_started": true,
+                       },
+               })
+               if err != nil {
+                       netconn.Close()
+                       return
+               }
+       }
+
        sshconn.Conn = netconn
        sshconn.Bufrw = &bufio.ReadWriter{Reader: bufr, Writer: bufw}
        sshconn.Logger = ctxlog.FromContext(ctx)
index 54c1b2149a0fd1d0d8a9ac01c12519c02ee88ead..aff569b0988d177b1be42eca9fdffc33a40d55d0 100644 (file)
@@ -77,6 +77,8 @@ func (s *ContainerGatewaySuite) SetUpSuite(c *check.C) {
 func (s *ContainerGatewaySuite) SetUpTest(c *check.C) {
        s.cluster.Containers.ShellAccess.Admin = true
        s.cluster.Containers.ShellAccess.User = true
+       _, err := arvadostest.DB(c, s.cluster).Exec(`update containers set interactive_session_started=$1 where uuid=$2`, false, s.ctrUUID)
+       c.Check(err, check.IsNil)
 }
 
 func (s *ContainerGatewaySuite) TestConfig(c *check.C) {
@@ -152,6 +154,9 @@ func (s *ContainerGatewaySuite) TestConnect(c *check.C) {
        case <-time.After(time.Second):
                c.Fail()
        }
+       ctr, err := s.localdb.ContainerGet(s.ctx, arvados.GetOptions{UUID: s.ctrUUID})
+       c.Check(err, check.IsNil)
+       c.Check(ctr.InteractiveSessionStarted, check.Equals, true)
 }
 
 func (s *ContainerGatewaySuite) TestConnectFail(c *check.C) {
index 4d1511b412dd315f134713c60ac1eaad337444dc..86b7c86846247c8b994ca9379800471b58e02580 100644 (file)
@@ -8,29 +8,30 @@ import "time"
 
 // Container is an arvados#container resource.
 type Container struct {
-       UUID                 string                 `json:"uuid"`
-       Etag                 string                 `json:"etag"`
-       CreatedAt            time.Time              `json:"created_at"`
-       ModifiedByClientUUID string                 `json:"modified_by_client_uuid"`
-       ModifiedByUserUUID   string                 `json:"modified_by_user_uuid"`
-       ModifiedAt           time.Time              `json:"modified_at"`
-       Command              []string               `json:"command"`
-       ContainerImage       string                 `json:"container_image"`
-       Cwd                  string                 `json:"cwd"`
-       Environment          map[string]string      `json:"environment"`
-       LockedByUUID         string                 `json:"locked_by_uuid"`
-       Mounts               map[string]Mount       `json:"mounts"`
-       Output               string                 `json:"output"`
-       OutputPath           string                 `json:"output_path"`
-       Priority             int64                  `json:"priority"`
-       RuntimeConstraints   RuntimeConstraints     `json:"runtime_constraints"`
-       State                ContainerState         `json:"state"`
-       SchedulingParameters SchedulingParameters   `json:"scheduling_parameters"`
-       ExitCode             int                    `json:"exit_code"`
-       RuntimeStatus        map[string]interface{} `json:"runtime_status"`
-       StartedAt            *time.Time             `json:"started_at"`  // nil if not yet started
-       FinishedAt           *time.Time             `json:"finished_at"` // nil if not yet finished
-       GatewayAddress       string                 `json:"gateway_address"`
+       UUID                      string                 `json:"uuid"`
+       Etag                      string                 `json:"etag"`
+       CreatedAt                 time.Time              `json:"created_at"`
+       ModifiedByClientUUID      string                 `json:"modified_by_client_uuid"`
+       ModifiedByUserUUID        string                 `json:"modified_by_user_uuid"`
+       ModifiedAt                time.Time              `json:"modified_at"`
+       Command                   []string               `json:"command"`
+       ContainerImage            string                 `json:"container_image"`
+       Cwd                       string                 `json:"cwd"`
+       Environment               map[string]string      `json:"environment"`
+       LockedByUUID              string                 `json:"locked_by_uuid"`
+       Mounts                    map[string]Mount       `json:"mounts"`
+       Output                    string                 `json:"output"`
+       OutputPath                string                 `json:"output_path"`
+       Priority                  int64                  `json:"priority"`
+       RuntimeConstraints        RuntimeConstraints     `json:"runtime_constraints"`
+       State                     ContainerState         `json:"state"`
+       SchedulingParameters      SchedulingParameters   `json:"scheduling_parameters"`
+       ExitCode                  int                    `json:"exit_code"`
+       RuntimeStatus             map[string]interface{} `json:"runtime_status"`
+       StartedAt                 *time.Time             `json:"started_at"`  // nil if not yet started
+       FinishedAt                *time.Time             `json:"finished_at"` // nil if not yet finished
+       GatewayAddress            string                 `json:"gateway_address"`
+       InteractiveSessionStarted bool                   `json:"interactive_session_started"`
 }
 
 // ContainerRequest is an arvados#container_request resource.
index 52fc79e2c036be9e1cf536aba0975f0a07f5ca90..49be3df558536d86af80535a22cb13232683c2dc 100644 (file)
@@ -77,6 +77,7 @@ class Container < ArvadosModel
     t.add :runtime_auth_scopes
     t.add :lock_count
     t.add :gateway_address
+    t.add :interactive_session_started
   end
 
   # Supported states for a container
@@ -481,6 +482,9 @@ class Container < ArvadosModel
       if self.state_changed?
         permitted.push :started_at, :gateway_address
       end
+      if !self.interactive_session_started_was
+        permitted.push :interactive_session_started
+      end
 
     when Complete
       if self.state_was == Running
diff --git a/services/api/db/migrate/20210126183521_add_interactive_session_started_to_containers.rb b/services/api/db/migrate/20210126183521_add_interactive_session_started_to_containers.rb
new file mode 100644 (file)
index 0000000..3fe23f6
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+class AddInteractiveSessionStartedToContainers < ActiveRecord::Migration[5.2]
+  def change
+    add_column :containers, :interactive_session_started, :boolean, null: false, default: false
+  end
+end
index 249ec67ac315f17b571f88c14928a6b663ea0390..14eca609eb0e35c91215a2d70e5af898d90168d4 100644 (file)
@@ -522,7 +522,8 @@ CREATE TABLE public.containers (
     runtime_auth_scopes jsonb,
     runtime_token text,
     lock_count integer DEFAULT 0 NOT NULL,
-    gateway_address character varying
+    gateway_address character varying,
+    interactive_session_started boolean DEFAULT false NOT NULL
 );
 
 
@@ -3189,6 +3190,7 @@ INSERT INTO "schema_migrations" (version) VALUES
 ('20201103170213'),
 ('20201105190435'),
 ('20201202174753'),
-('20210108033940');
+('20210108033940'),
+('20210126183521');
 
 
index 98e60e057910f034194ad9f47289627a245f97e4..7853b6f6a9152c6a492e366cc9f13a1edce0add7 100644 (file)
@@ -797,6 +797,8 @@ class ContainerTest < ActiveSupport::TestCase
     [Container::Running, {priority: 123456789}],
     [Container::Running, {runtime_status: {'error' => 'oops'}}],
     [Container::Running, {cwd: '/'}],
+    [Container::Running, {gateway_address: "172.16.0.1:12345"}],
+    [Container::Running, {interactive_session_started: true}],
     [Container::Complete, {state: Container::Cancelled}],
     [Container::Complete, {priority: 123456789}],
     [Container::Complete, {runtime_status: {'error' => 'oops'}}],