4444import io .fabric8 .kubernetes .api .model .ContainerPortBuilder ;
4545import io .fabric8 .kubernetes .api .model .EnvVar ;
4646import io .fabric8 .kubernetes .api .model .EnvVarSourceBuilder ;
47+ import io .fabric8 .kubernetes .api .model .Event ;
4748import io .fabric8 .kubernetes .api .model .GenericKubernetesResource ;
4849import io .fabric8 .kubernetes .api .model .GenericKubernetesResourceList ;
4950import io .fabric8 .kubernetes .api .model .HasMetadata ;
5051import io .fabric8 .kubernetes .api .model .LocalObjectReference ;
5152import io .fabric8 .kubernetes .api .model .ObjectMetaBuilder ;
53+ import io .fabric8 .kubernetes .api .model .ObjectReferenceBuilder ;
5254import io .fabric8 .kubernetes .api .model .Pod ;
5355import io .fabric8 .kubernetes .api .model .PodBuilder ;
5456import io .fabric8 .kubernetes .api .model .ServiceBuilder ;
6870import io .fabric8 .kubernetes .client .ConfigBuilder ;
6971import io .fabric8 .kubernetes .client .DefaultKubernetesClient ;
7072import io .fabric8 .kubernetes .client .KubernetesClient ;
73+ import io .fabric8 .kubernetes .client .KubernetesClientException ;
7174import io .fabric8 .kubernetes .client .dsl .LogWatch ;
7275import io .fabric8 .kubernetes .client .dsl .NonNamespaceOperation ;
7376import io .fabric8 .kubernetes .client .dsl .Resource ;
8790import java .nio .file .Files ;
8891import java .nio .file .Path ;
8992import java .nio .file .Paths ;
93+ import java .time .LocalDateTime ;
94+ import java .time .OffsetDateTime ;
95+ import java .time .ZoneId ;
9096import java .util .ArrayList ;
9197import java .util .Arrays ;
9298import java .util .Collections ;
@@ -159,6 +165,7 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
159165 Container container = new Container ();
160166 container .setSpec (spec );
161167 container .setId (UUID .randomUUID ().toString ());
168+ container .setIndex (spec .getIndex ());
162169
163170 String kubeNamespace = getProperty (PROPERTY_NAMESPACE , DEFAULT_NAMESPACE );
164171 String apiVersion = getProperty (PROPERTY_API_VERSION , DEFAULT_API_VERSION );
@@ -281,29 +288,41 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
281288 final String effectiveKubeNamespace = patchedPod .getMetadata ().getNamespace (); // use the namespace of the patched Pod, in case the patch changes the namespace.
282289 container .getParameters ().put (PARAM_NAMESPACE , effectiveKubeNamespace );
283290
284- // create additional manifests -> use the effective (i.e. patched) namespace if no namespace is provided
291+ // create additional manifests -> use the effective f (i.e. patched) namespace if no namespace is provided
285292 createAdditionalManifests (proxy , effectiveKubeNamespace );
286293
294+ // tell the status service we are starting the pod/container
295+ proxyStatusService .containerStarting (proxy , container );
296+
297+ // create and start the pod
287298 Pod startedPod = kubeClient .pods ().inNamespace (effectiveKubeNamespace ).create (patchedPod );
288299
289300 int totalWaitMs = Integer .parseInt (environment .getProperty ("proxy.kubernetes.pod-wait-time" , "60000" ));
290- int maxTries = totalWaitMs / 1000 ;
291- Retrying .retry (i -> {
292- if (!Readiness .getInstance ().isReady (kubeClient .resource (startedPod ).fromServer ().get ())) {
293- if (i > 1 && log != null ) log .debug (String .format ("Container not ready yet, trying again (%d/%d)" , i , maxTries ));
294- return false ;
295- }
296- return true ;
301+ boolean podReady = Retrying .retry ((currentAttempt , maxAttempts ) -> {
302+ if (!Readiness .getInstance ().isReady (kubeClient .resource (startedPod ).fromServer ().get ())) {
303+ if (currentAttempt > 10 && log != null ) log .info (String .format ("Container not ready yet, trying again (%d/%d)" , currentAttempt , maxAttempts ));
304+ return false ;
305+ }
306+ return true ;
307+ }, totalWaitMs );
308+
309+ if (!podReady ) {
310+ // check a final time whether the pod is ready
311+ if (!Readiness .getInstance ().isReady (kubeClient .resource (startedPod ).fromServer ().get ())) {
312+ Pod pod = kubeClient .resource (startedPod ).fromServer ().get ();
313+ container .getParameters ().put (PARAM_POD , pod );
314+ proxy .getContainers ().add (container );
315+
316+ proxyStatusService .containerStartFailed (proxy , container );
317+ throw new ContainerProxyException ("Container did not become ready in time" );
297318 }
298- , maxTries , 1000 );
299- if (!Readiness .getInstance ().isReady (kubeClient .resource (startedPod ).fromServer ().get ())) {
300- Pod pod = kubeClient .resource (startedPod ).fromServer ().get ();
301- container .getParameters ().put (PARAM_POD , pod );
302- proxy .getContainers ().add (container );
303- throw new ContainerProxyException ("Container did not become ready in time" );
304319 }
320+
321+ proxyStatusService .containerStarted (proxy , container );
305322 Pod pod = kubeClient .resource (startedPod ).fromServer ().get ();
306323
324+ parseKubernetesEvents (proxy , container , pod );
325+
307326 Service service = null ;
308327 if (isUseInternalNetwork ()) {
309328 // If SP runs inside the cluster, it can access pods directly and doesn't need any port publishing service.
@@ -328,7 +347,7 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
328347 .build ());
329348
330349 // Workaround: waitUntilReady appears to be buggy.
331- Retrying .retry (i -> isServiceReady (kubeClient .resource (startupService ).fromServer ().get ()), 60 , 1000 );
350+ Retrying .retry (( currentAttempt , maxAttempts ) -> isServiceReady (kubeClient .resource (startupService ).fromServer ().get ()), 60_000 );
332351
333352 service = kubeClient .resource (startupService ).fromServer ().get ();
334353 }
@@ -354,6 +373,65 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
354373 return container ;
355374 }
356375
376+ private LocalDateTime getEventTime (Event event ) {
377+ if (event .getEventTime () != null && event .getEventTime ().getTime () != null ) {
378+ return OffsetDateTime .parse (event .getEventTime ().getTime ()).atZoneSameInstant (ZoneId .systemDefault ()).toLocalDateTime ();
379+ }
380+
381+ if (event .getFirstTimestamp () != null ) {
382+ return OffsetDateTime .parse (event .getFirstTimestamp ()).atZoneSameInstant (ZoneId .systemDefault ()).toLocalDateTime ();
383+ }
384+
385+ if (event .getLastTimestamp () != null ) {
386+ return OffsetDateTime .parse (event .getLastTimestamp ()).atZoneSameInstant (ZoneId .systemDefault ()).toLocalDateTime ();
387+ }
388+
389+ return null ;
390+ }
391+
392+ private void parseKubernetesEvents (Proxy proxy , Container container , Pod pod ) {
393+ List <Event > events ;
394+ try {
395+ events = kubeClient .v1 ().events ().withInvolvedObject (new ObjectReferenceBuilder ()
396+ .withKind ("Pod" )
397+ .withName (pod .getMetadata ().getName ())
398+ .withNamespace (pod .getMetadata ().getNamespace ())
399+ .build ()).list ().getItems ();
400+ } catch (KubernetesClientException ex ) {
401+ if (ex .getCode () == 403 ) {
402+ log .warn ("Cannot parse events of pod because of insufficient permissions. If fine-grained statistics are desired, give the ShinyProxy ServiceAccount permission to events of pods." );
403+ return ;
404+ }
405+ throw ex ;
406+ }
407+
408+ LocalDateTime pullingTime = null ;
409+ LocalDateTime pulledTime = null ;
410+ LocalDateTime scheduledTime = null ;
411+
412+ for (Event event : events ) {
413+ if (event .getCount () != null && event .getCount () > 1 ) {
414+ // ignore events which happened multiple time as we are unable to properly process them
415+ continue ;
416+ }
417+ if (event .getReason ().equalsIgnoreCase ("Pulling" )) {
418+ pullingTime = getEventTime (event );
419+ } else if (event .getReason ().equalsIgnoreCase ("Pulled" )) {
420+ pulledTime = getEventTime (event );
421+ } else if (event .getReason ().equalsIgnoreCase ("Scheduled" )) {
422+ scheduledTime = getEventTime (event );
423+ }
424+ }
425+
426+ if (pullingTime != null && pulledTime != null ) {
427+ proxyStatusService .imagePulled (proxy , container , pullingTime , pulledTime );
428+ }
429+
430+ if (scheduledTime != null ) {
431+ proxyStatusService .containerScheduled (proxy , container , scheduledTime );
432+ }
433+ }
434+
357435 private JsonPatch readPatchFromSpec (ContainerSpec containerSpec , Proxy proxy ) throws JsonMappingException , JsonProcessingException {
358436 String patchAsString = proxy .getSpec ().getKubernetesPodPatch ();
359437 if (patchAsString == null ) {
0 commit comments