Unverified Commit a82f350a authored by julienmancuso's avatar julienmancuso Committed by GitHub
Browse files

feat: remove dynamoComponentRequest CRD (#856)

parent c544e8ec
......@@ -26,10 +26,24 @@ spec:
kind: DynamoComponent
listKind: DynamoComponentList
plural: dynamocomponents
shortNames:
- dc
singular: dynamocomponent
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- description: Dynamo component
jsonPath: .spec.dynamoComponent
name: DynamoComponent
type: string
- description: Image Exists
jsonPath: .status.conditions[?(@.type=='ImageExists')].status
name: Image-Exists
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
properties:
......@@ -41,10 +55,1358 @@ spec:
type: object
spec:
properties:
buildArgs:
items:
type: string
type: array
dockerConfigJsonSecretName:
type: string
downloadUrl:
type: string
downloaderContainerEnvFrom:
items:
properties:
configMapRef:
properties:
name:
default: ""
type: string
optional:
type: boolean
type: object
x-kubernetes-map-type: atomic
prefix:
type: string
secretRef:
properties:
name:
default: ""
type: string
optional:
type: boolean
type: object
x-kubernetes-map-type: atomic
type: object
type: array
dynamoComponent:
type: string
image:
type: string
imageBuildTimeout:
format: int64
type: integer
imageBuilderContainerResources:
properties:
claims:
items:
properties:
name:
type: string
request:
type: string
required:
- name
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
type: object
imageBuilderExtraContainerEnv:
items:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
default: ""
type: string
optional:
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
fieldRef:
properties:
apiVersion:
type: string
fieldPath:
type: string
required:
- fieldPath
type: object
x-kubernetes-map-type: atomic
resourceFieldRef:
properties:
containerName:
type: string
divisor:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
resource:
type: string
required:
- resource
type: object
x-kubernetes-map-type: atomic
secretKeyRef:
properties:
key:
type: string
name:
default: ""
type: string
optional:
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
type: object
required:
- name
type: object
type: array
imageBuilderExtraPodMetadata:
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
imageBuilderExtraPodSpec:
properties:
affinity:
properties:
nodeAffinity:
properties:
preferredDuringSchedulingIgnoredDuringExecution:
items:
properties:
preference:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchFields:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
type: object
x-kubernetes-map-type: atomic
weight:
format: int32
type: integer
required:
- preference
- weight
type: object
type: array
x-kubernetes-list-type: atomic
requiredDuringSchedulingIgnoredDuringExecution:
properties:
nodeSelectorTerms:
items:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchFields:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
type: object
x-kubernetes-map-type: atomic
type: array
x-kubernetes-list-type: atomic
required:
- nodeSelectorTerms
type: object
x-kubernetes-map-type: atomic
type: object
podAffinity:
properties:
preferredDuringSchedulingIgnoredDuringExecution:
items:
properties:
podAffinityTerm:
properties:
labelSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
matchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
mismatchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
namespaces:
items:
type: string
type: array
x-kubernetes-list-type: atomic
topologyKey:
type: string
required:
- topologyKey
type: object
weight:
format: int32
type: integer
required:
- podAffinityTerm
- weight
type: object
type: array
x-kubernetes-list-type: atomic
requiredDuringSchedulingIgnoredDuringExecution:
items:
properties:
labelSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
matchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
mismatchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
namespaces:
items:
type: string
type: array
x-kubernetes-list-type: atomic
topologyKey:
type: string
required:
- topologyKey
type: object
type: array
x-kubernetes-list-type: atomic
type: object
podAntiAffinity:
properties:
preferredDuringSchedulingIgnoredDuringExecution:
items:
properties:
podAffinityTerm:
properties:
labelSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
matchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
mismatchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
namespaces:
items:
type: string
type: array
x-kubernetes-list-type: atomic
topologyKey:
type: string
required:
- topologyKey
type: object
weight:
format: int32
type: integer
required:
- podAffinityTerm
- weight
type: object
type: array
x-kubernetes-list-type: atomic
requiredDuringSchedulingIgnoredDuringExecution:
items:
properties:
labelSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
matchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
mismatchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
namespaces:
items:
type: string
type: array
x-kubernetes-list-type: atomic
topologyKey:
type: string
required:
- topologyKey
type: object
type: array
x-kubernetes-list-type: atomic
type: object
type: object
containers:
items:
properties:
args:
items:
type: string
type: array
x-kubernetes-list-type: atomic
command:
items:
type: string
type: array
x-kubernetes-list-type: atomic
env:
items:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
default: ""
type: string
optional:
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
fieldRef:
properties:
apiVersion:
type: string
fieldPath:
type: string
required:
- fieldPath
type: object
x-kubernetes-map-type: atomic
resourceFieldRef:
properties:
containerName:
type: string
divisor:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
resource:
type: string
required:
- resource
type: object
x-kubernetes-map-type: atomic
secretKeyRef:
properties:
key:
type: string
name:
default: ""
type: string
optional:
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
type: object
required:
- name
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
envFrom:
items:
properties:
configMapRef:
properties:
name:
default: ""
type: string
optional:
type: boolean
type: object
x-kubernetes-map-type: atomic
prefix:
type: string
secretRef:
properties:
name:
default: ""
type: string
optional:
type: boolean
type: object
x-kubernetes-map-type: atomic
type: object
type: array
x-kubernetes-list-type: atomic
image:
type: string
imagePullPolicy:
type: string
lifecycle:
properties:
postStart:
properties:
exec:
properties:
command:
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
httpGet:
properties:
host:
type: string
httpHeaders:
items:
properties:
name:
type: string
value:
type: string
required:
- name
- value
type: object
type: array
x-kubernetes-list-type: atomic
path:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
scheme:
type: string
required:
- port
type: object
sleep:
properties:
seconds:
format: int64
type: integer
required:
- seconds
type: object
tcpSocket:
properties:
host:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
required:
- port
type: object
type: object
preStop:
properties:
exec:
properties:
command:
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
httpGet:
properties:
host:
type: string
httpHeaders:
items:
properties:
name:
type: string
value:
type: string
required:
- name
- value
type: object
type: array
x-kubernetes-list-type: atomic
path:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
scheme:
type: string
required:
- port
type: object
sleep:
properties:
seconds:
format: int64
type: integer
required:
- seconds
type: object
tcpSocket:
properties:
host:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
required:
- port
type: object
type: object
type: object
livenessProbe:
properties:
exec:
properties:
command:
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
failureThreshold:
format: int32
type: integer
grpc:
properties:
port:
format: int32
type: integer
service:
default: ""
type: string
required:
- port
type: object
httpGet:
properties:
host:
type: string
httpHeaders:
items:
properties:
name:
type: string
value:
type: string
required:
- name
- value
type: object
type: array
x-kubernetes-list-type: atomic
path:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
scheme:
type: string
required:
- port
type: object
initialDelaySeconds:
format: int32
type: integer
periodSeconds:
format: int32
type: integer
successThreshold:
format: int32
type: integer
tcpSocket:
properties:
host:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
required:
- port
type: object
terminationGracePeriodSeconds:
format: int64
type: integer
timeoutSeconds:
format: int32
type: integer
type: object
name:
type: string
ports:
items:
properties:
containerPort:
format: int32
type: integer
hostIP:
type: string
hostPort:
format: int32
type: integer
name:
type: string
protocol:
default: TCP
type: string
required:
- containerPort
type: object
type: array
x-kubernetes-list-map-keys:
- containerPort
- protocol
x-kubernetes-list-type: map
readinessProbe:
properties:
exec:
properties:
command:
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
failureThreshold:
format: int32
type: integer
grpc:
properties:
port:
format: int32
type: integer
service:
default: ""
type: string
required:
- port
type: object
httpGet:
properties:
host:
type: string
httpHeaders:
items:
properties:
name:
type: string
value:
type: string
required:
- name
- value
type: object
type: array
x-kubernetes-list-type: atomic
path:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
scheme:
type: string
required:
- port
type: object
initialDelaySeconds:
format: int32
type: integer
periodSeconds:
format: int32
type: integer
successThreshold:
format: int32
type: integer
tcpSocket:
properties:
host:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
required:
- port
type: object
terminationGracePeriodSeconds:
format: int64
type: integer
timeoutSeconds:
format: int32
type: integer
type: object
resizePolicy:
items:
properties:
resourceName:
type: string
restartPolicy:
type: string
required:
- resourceName
- restartPolicy
type: object
type: array
x-kubernetes-list-type: atomic
resources:
properties:
claims:
items:
properties:
name:
type: string
request:
type: string
required:
- name
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
type: object
restartPolicy:
type: string
securityContext:
properties:
allowPrivilegeEscalation:
type: boolean
appArmorProfile:
properties:
localhostProfile:
type: string
type:
type: string
required:
- type
type: object
capabilities:
properties:
add:
items:
type: string
type: array
x-kubernetes-list-type: atomic
drop:
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
privileged:
type: boolean
procMount:
type: string
readOnlyRootFilesystem:
type: boolean
runAsGroup:
format: int64
type: integer
runAsNonRoot:
type: boolean
runAsUser:
format: int64
type: integer
seLinuxOptions:
properties:
level:
type: string
role:
type: string
type:
type: string
user:
type: string
type: object
seccompProfile:
properties:
localhostProfile:
type: string
type:
type: string
required:
- type
type: object
windowsOptions:
properties:
gmsaCredentialSpec:
type: string
gmsaCredentialSpecName:
type: string
hostProcess:
type: boolean
runAsUserName:
type: string
type: object
type: object
startupProbe:
properties:
exec:
properties:
command:
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
failureThreshold:
format: int32
type: integer
grpc:
properties:
port:
format: int32
type: integer
service:
default: ""
type: string
required:
- port
type: object
httpGet:
properties:
host:
type: string
httpHeaders:
items:
properties:
name:
type: string
value:
type: string
required:
- name
- value
type: object
type: array
x-kubernetes-list-type: atomic
path:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
scheme:
type: string
required:
- port
type: object
initialDelaySeconds:
format: int32
type: integer
periodSeconds:
format: int32
type: integer
successThreshold:
format: int32
type: integer
tcpSocket:
properties:
host:
type: string
port:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
required:
- port
type: object
terminationGracePeriodSeconds:
format: int64
type: integer
timeoutSeconds:
format: int32
type: integer
type: object
stdin:
type: boolean
stdinOnce:
type: boolean
terminationMessagePath:
type: string
terminationMessagePolicy:
type: string
tty:
type: boolean
volumeDevices:
items:
properties:
devicePath:
type: string
name:
type: string
required:
- devicePath
- name
type: object
type: array
x-kubernetes-list-map-keys:
- devicePath
x-kubernetes-list-type: map
volumeMounts:
items:
properties:
mountPath:
type: string
mountPropagation:
type: string
name:
type: string
readOnly:
type: boolean
recursiveReadOnly:
type: string
subPath:
type: string
subPathExpr:
type: string
required:
- mountPath
- name
type: object
type: array
x-kubernetes-list-map-keys:
- mountPath
x-kubernetes-list-type: map
workingDir:
type: string
required:
- name
type: object
type: array
nodeSelector:
additionalProperties:
type: string
type: object
priorityClassName:
type: string
schedulerName:
type: string
serviceAccountName:
type: string
tolerations:
items:
properties:
effect:
type: string
key:
type: string
operator:
type: string
tolerationSeconds:
format: int64
type: integer
value:
type: string
type: object
type: array
topologySpreadConstraints:
items:
properties:
labelSelector:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
type: object
type: object
x-kubernetes-map-type: atomic
matchLabelKeys:
items:
type: string
type: array
x-kubernetes-list-type: atomic
maxSkew:
format: int32
type: integer
minDomains:
format: int32
type: integer
nodeAffinityPolicy:
type: string
nodeTaintsPolicy:
type: string
topologyKey:
type: string
whenUnsatisfiable:
type: string
required:
- maxSkew
- topologyKey
- whenUnsatisfiable
type: object
type: array
type: object
imagePullSecrets:
items:
properties:
......@@ -58,14 +1420,47 @@ spec:
type: string
required:
- dynamoComponent
- image
type: object
status:
properties:
ready:
type: boolean
conditions:
items:
properties:
lastTransitionTime:
format: date-time
type: string
message:
maxLength: 32768
type: string
observedGeneration:
format: int64
minimum: 0
type: integer
reason:
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
enum:
- "True"
- "False"
- Unknown
type: string
type:
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
required:
- ready
- conditions
type: object
type: object
served: true
......
......@@ -26,6 +26,8 @@ spec:
kind: DynamoGraphDeployment
listKind: DynamoGraphDeploymentList
plural: dynamographdeployments
shortNames:
- dgd
singular: dynamographdeployment
scope: Namespaced
versions:
......
......@@ -18,7 +18,6 @@
# It should be run by config/default
resources:
- bases/nvidia.com_dynamocomponentdeployments.yaml
- bases/nvidia.com_dynamocomponentrequests.yaml
- bases/nvidia.com_dynamocomponents.yaml
- bases/nvidia.com_dynamographdeployments.yaml
#+kubebuilder:scaffold:crdkustomizeresource
......@@ -31,7 +30,6 @@ patches: []
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- path: patches/cainjection_in_dynamocomponentdeployments.yaml
#- path: patches/cainjection_in_dynamocomponentrequests.yaml
#- path: patches/cainjection_in_dynamocomponents.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
......
......@@ -37,8 +37,8 @@ resources:
# if you do not want those helpers be installed with your Project.
# - dynamocomponent_editor_role.yaml
# - dynamocomponent_viewer_role.yaml
# - dynamocomponentrequest_editor_role.yaml
# - dynamocomponentrequest_viewer_role.yaml
# - dynamocomponent_editor_role.yaml
# - dynamocomponent_viewer_role.yaml
# - dynamocomponentdeployment_editor_role.yaml
# - dynamocomponentdeployment_viewer_role.yaml
......@@ -129,7 +129,6 @@ rules:
- nvidia.com
resources:
- dynamocomponentdeployments
- dynamocomponentrequests
- dynamocomponents
- dynamographdeployments
verbs:
......@@ -144,7 +143,7 @@ rules:
- nvidia.com
resources:
- dynamocomponentdeployments/finalizers
- dynamocomponentrequests/finalizers
- dynamocomponents/finalizers
- dynamographdeployments/finalizers
verbs:
- update
......@@ -152,7 +151,6 @@ rules:
- nvidia.com
resources:
- dynamocomponentdeployments/status
- dynamocomponentrequests/status
- dynamocomponents/status
- dynamographdeployments/status
verbs:
......
......@@ -16,7 +16,6 @@
## Append samples of your project ##
resources:
- nvidia.com_v1alpha1_dynamocomponentdeployment.yaml
- nvidia.com_v1alpha1_dynamocomponentrequest.yaml
- nvidia.com_v1alpha1_dynamocomponent.yaml
- nvidia.com_v1alpha1_dynamographdeployment.yaml
#+kubebuilder:scaffold:manifestskustomizesamples
......@@ -31,21 +31,17 @@ func GetDynamoImageBuilderNamespace(ctx context.Context) (namespace string, err
type DockerRegistryConfig struct {
DynamoComponentsRepositoryName string `yaml:"dynamo_components_repository_name"`
Server string `yaml:"server"`
InClusterServer string `yaml:"in_cluster_server"`
Username string `yaml:"username"`
Password string `yaml:"password"`
SecretName string `yaml:"secret_name"`
Secure bool `yaml:"secure"`
}
func GetDockerRegistryConfig() (conf *DockerRegistryConfig, err error) {
func GetDockerRegistryConfig() *DockerRegistryConfig {
return &DockerRegistryConfig{
DynamoComponentsRepositoryName: os.Getenv(consts.EnvDockerRegistryDynamoComponentsRepositoryName),
Server: os.Getenv(consts.EnvDockerRegistryServer),
InClusterServer: os.Getenv(consts.EnvDockerRegistryInClusterServer),
Username: os.Getenv(consts.EnvDockerRegistryUsername),
Password: os.Getenv(consts.EnvDockerRegistryPassword),
SecretName: os.Getenv(consts.EnvDockerRegistrySecret),
Secure: os.Getenv(consts.EnvDockerRegistrySecure) == "true",
}, nil
}
}
type ApiStoreConfig struct {
......
......@@ -26,11 +26,8 @@ const (
EnvDynamoServicePort = "PORT"
EnvDockerRegistryServer = "DOCKER_REGISTRY_SERVER"
EnvDockerRegistryInClusterServer = "DOCKER_REGISTRY_IN_CLUSTER_SERVER"
EnvDockerRegistryUsername = "DOCKER_REGISTRY_USERNAME"
// nolint:gosec
EnvDockerRegistryPassword = "DOCKER_REGISTRY_PASSWORD"
EnvDockerRegistryServer = "DOCKER_REGISTRY_SERVER"
EnvDockerRegistrySecret = "DOCKER_REGISTRY_SECRET_NAME"
EnvDockerRegistrySecure = "DOCKER_REGISTRY_SECURE"
EnvDockerRegistryDynamoComponentsRepositoryName = "DOCKER_REGISTRY_DYNAMO_COMPONENTS_REPOSITORY_NAME"
......@@ -49,8 +46,7 @@ const (
KubeLabelDynamoComponentType = "nvidia.com/dynamo-component-type"
KubeLabelIsDynamoImageBuilder = "nvidia.com/is-dynamo-image-builder"
KubeLabelDynamoComponentRequest = "nvidia.com/dynamo-component-request"
KubeLabelIsDynamoImageBuilder = "nvidia.com/is-dynamo-image-builder"
KubeLabelValueFalse = "false"
KubeLabelValueTrue = "true"
......@@ -64,10 +60,7 @@ const (
KubeResourceGPUNvidia = "nvidia.com/gpu"
// nolint: gosec
KubeSecretNameRegcred = "dynamo-regcred"
KubeAnnotationDynamoComponentRequestHash = "nvidia.com/dynamo-request-hash"
KubeAnnotationDynamoComponentRequestImageBuiderHash = "nvidia.com/dynamo-request-image-builder-hash"
KubeAnnotationDynamoComponentStorageNS = "nvidia.com/dynamo-storage-namespace"
KubeAnnotationDynamoComponentHash = "nvidia.com/dynamo-request-hash"
KubeAnnotationDynamoComponentImageBuiderHash = "nvidia.com/dynamo-request-image-builder-hash"
KubeAnnotationDynamoComponentStorageNS = "nvidia.com/dynamo-storage-namespace"
)
......@@ -26,34 +26,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
EntityHandlerTypeJob = "job"
// EntityHandlerCreated indicates that the EntityHandler is created.
EntityHandlerCreated = "ENTITY_HANDLER_CREATED"
// EntityHandlerCompleted indicates that the EntityHandler has completed.
EntityHandlerCompleted = "ENTITY_HANDLER_COMPLETED"
// EntityHandlerPending indicates that the EntityHandler is in pending state.
EntityHandlerPending = "ENTITY_HANDLER_PENDING"
EntityHandlerCreatedState = "EntityHandlerCreated"
EntityHandlerCompletedState = "EntityHandlerCompleted"
EntityHandlerFailedState = "EntityHandlerFailed"
EntityHandlerPendingState = "EntityHandlerPending"
EntityHandlerRunningState = "EntityHandlerRunning"
PVCCreatedState = "PVCCreated"
CrdRunning = "running"
CrdFailed = "failed"
CrdSuccessful = "successful"
PVCMountPath = "/pvc"
// TrainingJobPVCCreated indicates that the caching pvc is created.
PVCCreated = "PVC_CREATED"
)
func constructPVC(crd metav1.Object, pvcConfig v1alpha1.PVC) *corev1.PersistentVolumeClaim {
storageClassName := pvcConfig.StorageClass
return &corev1.PersistentVolumeClaim{
......
......@@ -29,7 +29,6 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"text/template"
......@@ -70,17 +69,21 @@ import (
nvidiacomv1alpha1 "github.com/ai-dynamo/dynamo/deploy/dynamo/operator/api/v1alpha1"
)
// DynamoComponentRequestReconciler reconciles a DynamoComponentRequest object
type DynamoComponentRequestReconciler struct {
const (
dockerConfigSecretKey = ".dockerconfigjson"
)
// DynamoComponentReconciler reconciles a DynamoComponent object
type DynamoComponentReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
Config controller_common.Config
}
// +kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponentrequests,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponentrequests/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponentrequests/finalizers,verbs=update
// +kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponents,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponents/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponents/finalizers,verbs=update
//+kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponents,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=nvidia.com,resources=dynamocomponents/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=get;list;watch;create;update;patch;delete
......@@ -94,7 +97,7 @@ type DynamoComponentRequestReconciler struct {
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the DynamoComponentRequest object against the actual cluster state, and then
// the DynamoComponent object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
......@@ -102,52 +105,50 @@ type DynamoComponentRequestReconciler struct {
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.2/pkg/reconcile
//
//nolint:gocyclo,nakedret
func (r *DynamoComponentRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
func (r *DynamoComponentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
logs := log.FromContext(ctx)
dynamoComponentRequest := &nvidiacomv1alpha1.DynamoComponentRequest{}
DynamoComponent := &nvidiacomv1alpha1.DynamoComponent{}
err = r.Get(ctx, req.NamespacedName, dynamoComponentRequest)
err = r.Get(ctx, req.NamespacedName, DynamoComponent)
if err != nil {
if k8serrors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
// For additional cleanup logic use finalizers.
logs.Info("dynamoComponentRequest resource not found. Ignoring since object must be deleted")
logs.Info("DynamoComponent resource not found. Ignoring since object must be deleted")
err = nil
return
}
// Error reading the object - requeue the request.
logs.Error(err, "Failed to get dynamoComponentRequest")
logs.Error(err, "Failed to get DynamoComponent")
return
}
for _, condition := range dynamoComponentRequest.Status.Conditions {
if condition.Type == nvidiacomv1alpha1.DynamoGraphDeploymentConditionTypeAvailable && condition.Status == metav1.ConditionTrue {
logs.Info("Skip available dynamoComponentRequest")
return
}
if DynamoComponent.IsReady() {
logs.Info("Skip available DynamoComponent")
return
}
if len(dynamoComponentRequest.Status.Conditions) == 0 {
dynamoComponentRequest, err = r.setStatusConditions(ctx, req,
if len(DynamoComponent.Status.Conditions) == 0 {
DynamoComponent, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeModelsSeeding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "Starting to reconcile dynamoComponentRequest",
Message: "Starting to reconcile DynamoComponent",
},
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExists,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "Starting to reconcile dynamoComponentRequest",
Message: "Starting to reconcile DynamoComponent",
},
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExists,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "Starting to reconcile dynamoComponentRequest",
Message: "Reconciling",
},
)
if err != nil {
......@@ -155,47 +156,32 @@ func (r *DynamoComponentRequestReconciler) Reconcile(ctx context.Context, req ct
}
}
logs = logs.WithValues("dynamoComponentRequest", dynamoComponentRequest.Name, "dynamoComponentRequestNamespace", dynamoComponentRequest.Namespace)
logs = logs.WithValues("DynamoComponent", DynamoComponent.Name, "DynamoComponentNamespace", DynamoComponent.Namespace)
defer func() {
if err == nil {
logs.Info("Reconcile success")
return
}
logs.Error(err, "Failed to reconcile dynamoComponentRequest.")
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeWarning, "ReconcileError", "Failed to reconcile dynamoComponentRequest: %v", err)
logs.Error(err, "Failed to reconcile DynamoComponent.")
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeWarning, "ReconcileError", "Failed to reconcile DynamoComponent: %v", err)
_, err_ := r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: err.Error(),
},
)
if err_ != nil {
logs.Error(err_, "Failed to update dynamoComponentRequest status")
logs.Error(err_, "Failed to update DynamoComponent status")
return
}
}()
dynamoComponentAvailableCondition := meta.FindStatusCondition(dynamoComponentRequest.Status.Conditions, nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable)
if dynamoComponentAvailableCondition == nil || dynamoComponentAvailableCondition.Status != metav1.ConditionUnknown {
dynamoComponentRequest, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "Reconciling",
},
)
if err != nil {
return
}
}
dynamoComponentRequest, imageInfo, imageExists, imageExistsResult, err := r.ensureImageExists(ctx, ensureImageExistsOption{
dynamoComponentRequest: dynamoComponentRequest,
req: req,
DynamoComponent, _, imageExists, imageExistsResult, err := r.ensureImageExists(ctx, ensureImageExistsOption{
DynamoComponent: DynamoComponent,
req: req,
})
if err != nil {
......@@ -205,9 +191,9 @@ func (r *DynamoComponentRequestReconciler) Reconcile(ctx context.Context, req ct
if !imageExists {
result = imageExistsResult
dynamoComponentRequest, err = r.setStatusConditions(ctx, req,
DynamoComponent, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "DynamoComponent image is building",
......@@ -219,64 +205,12 @@ func (r *DynamoComponentRequestReconciler) Reconcile(ctx context.Context, req ct
return
}
dynamoComponentCR := &nvidiacomv1alpha1.DynamoComponent{
ObjectMeta: metav1.ObjectMeta{
Name: dynamoComponentRequest.Name,
Namespace: dynamoComponentRequest.Namespace,
},
Spec: nvidiacomv1alpha1.DynamoComponentSpec{
DynamoComponent: dynamoComponentRequest.Spec.DynamoComponent,
Image: imageInfo.ImageName,
ServiceName: dynamoComponentRequest.Spec.ServiceName,
},
}
err = ctrl.SetControllerReference(dynamoComponentRequest, dynamoComponentCR, r.Scheme)
if err != nil {
err = errors.Wrap(err, "set controller reference")
return
}
if imageInfo.DockerConfigJSONSecretName != "" {
dynamoComponentCR.Spec.ImagePullSecrets = []corev1.LocalObjectReference{
{
Name: imageInfo.DockerConfigJSONSecretName,
},
}
}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "DynamoComponentImageBuilder", "Creating DynamoComponent CR %s in namespace %s", dynamoComponentCR.Name, dynamoComponentCR.Namespace)
err = r.Create(ctx, dynamoComponentCR)
isAlreadyExists := k8serrors.IsAlreadyExists(err)
if err != nil && !isAlreadyExists {
err = errors.Wrap(err, "create DynamoComponent resource")
return
}
if isAlreadyExists {
oldDynamoComponentCR := &nvidiacomv1alpha1.DynamoComponent{}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "DynamoComponentImageBuilder", "Updating DynamoComponent CR %s in namespace %s", dynamoComponentCR.Name, dynamoComponentCR.Namespace)
err = r.Get(ctx, types.NamespacedName{Name: dynamoComponentCR.Name, Namespace: dynamoComponentCR.Namespace}, oldDynamoComponentCR)
if err != nil {
err = errors.Wrap(err, "get DynamoComponent resource")
return
}
if !reflect.DeepEqual(oldDynamoComponentCR.Spec, dynamoComponentCR.Spec) {
oldDynamoComponentCR.OwnerReferences = dynamoComponentCR.OwnerReferences
oldDynamoComponentCR.Spec = dynamoComponentCR.Spec
err = r.Update(ctx, oldDynamoComponentCR)
if err != nil {
err = errors.Wrap(err, "update DynamoComponent resource")
return
}
}
}
dynamoComponentRequest, err = r.setStatusConditions(ctx, req,
DynamoComponent, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionTrue,
Reason: "Reconciling",
Message: "DynamoComponent is generated",
Message: "DynamoComponent image is generated",
},
)
if err != nil {
......@@ -291,100 +225,100 @@ func isEstargzEnabled() bool {
}
type ensureImageExistsOption struct {
dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest
req ctrl.Request
DynamoComponent *nvidiacomv1alpha1.DynamoComponent
req ctrl.Request
}
//nolint:gocyclo,nakedret
func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context, opt ensureImageExistsOption) (dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest, imageInfo ImageInfo, imageExists bool, result ctrl.Result, err error) { // nolint: unparam
func (r *DynamoComponentReconciler) ensureImageExists(ctx context.Context, opt ensureImageExistsOption) (DynamoComponent *nvidiacomv1alpha1.DynamoComponent, imageInfo ImageInfo, imageExists bool, result ctrl.Result, err error) { // nolint: unparam
logs := log.FromContext(ctx)
dynamoComponentRequest = opt.dynamoComponentRequest
DynamoComponent = opt.DynamoComponent
req := opt.req
imageInfo, err = r.getImageInfo(ctx, GetImageInfoOption{
DynamoComponentRequest: dynamoComponentRequest,
DynamoComponent: DynamoComponent,
})
if err != nil {
err = errors.Wrap(err, "get image info")
return
}
imageExistsCheckedCondition := meta.FindStatusCondition(dynamoComponentRequest.Status.Conditions, nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExistsChecked)
imageExistsCondition := meta.FindStatusCondition(dynamoComponentRequest.Status.Conditions, nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExists)
imageExistsCheckedCondition := meta.FindStatusCondition(DynamoComponent.Status.Conditions, nvidiacomv1alpha1.DynamoComponentConditionTypeImageExistsChecked)
imageExistsCondition := meta.FindStatusCondition(DynamoComponent.Status.Conditions, nvidiacomv1alpha1.DynamoComponentConditionTypeImageExists)
if imageExistsCheckedCondition == nil || imageExistsCheckedCondition.Status != metav1.ConditionTrue || imageExistsCheckedCondition.Message != imageInfo.ImageName {
imageExistsCheckedCondition = &metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExistsChecked,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExistsChecked,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: imageInfo.ImageName,
}
dynamoComponentAvailableCondition := &metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "Checking image exists",
}
dynamoComponentRequest, err = r.setStatusConditions(ctx, req, *imageExistsCheckedCondition, *dynamoComponentAvailableCondition)
DynamoComponent, err = r.setStatusConditions(ctx, req, *imageExistsCheckedCondition, *dynamoComponentAvailableCondition)
if err != nil {
return
}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "CheckingImage", "Checking image exists: %s", imageInfo.ImageName)
imageExists, err = checkImageExists(dynamoComponentRequest, imageInfo.DockerRegistry, imageInfo.InClusterImageName)
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "CheckingImage", "Checking image exists: %s", imageInfo.ImageName)
imageExists, err = checkImageExists(DynamoComponent, imageInfo.DockerRegistry, imageInfo.ImageName)
if err != nil {
err = errors.Wrapf(err, "check image %s exists", imageInfo.ImageName)
return
}
err = r.Get(ctx, req.NamespacedName, dynamoComponentRequest)
err = r.Get(ctx, req.NamespacedName, DynamoComponent)
if err != nil {
logs.Error(err, "Failed to re-fetch dynamoComponentRequest")
logs.Error(err, "Failed to re-fetch DynamoComponent")
return
}
if imageExists {
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "CheckingImage", "Image exists: %s", imageInfo.ImageName)
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "CheckingImage", "Image exists: %s", imageInfo.ImageName)
imageExistsCheckedCondition = &metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExistsChecked,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExistsChecked,
Status: metav1.ConditionTrue,
Reason: "Reconciling",
Message: imageInfo.ImageName,
}
imageExistsCondition = &metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExists,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExists,
Status: metav1.ConditionTrue,
Reason: "Reconciling",
Message: imageInfo.ImageName,
}
dynamoComponentRequest, err = r.setStatusConditions(ctx, req, *imageExistsCondition, *imageExistsCheckedCondition)
DynamoComponent, err = r.setStatusConditions(ctx, req, *imageExistsCondition, *imageExistsCheckedCondition)
if err != nil {
return
}
} else {
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "CheckingImage", "Image not exists: %s", imageInfo.ImageName)
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "CheckingImage", "Image not exists: %s", imageInfo.ImageName)
imageExistsCheckedCondition = &metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExistsChecked,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExistsChecked,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: fmt.Sprintf("Image not exists: %s", imageInfo.ImageName),
}
imageExistsCondition = &metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExists,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExists,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: fmt.Sprintf("Image %s is not exists", imageInfo.ImageName),
}
dynamoComponentRequest, err = r.setStatusConditions(ctx, req, *imageExistsCondition, *imageExistsCheckedCondition)
DynamoComponent, err = r.setStatusConditions(ctx, req, *imageExistsCondition, *imageExistsCheckedCondition)
if err != nil {
return
}
}
}
var dynamoComponentRequestHashStr string
dynamoComponentRequestHashStr, err = r.getHashStr(dynamoComponentRequest)
var DynamoComponentHashStr string
DynamoComponentHashStr, err = r.getHashStr(DynamoComponent)
if err != nil {
err = errors.Wrapf(err, "get dynamoComponentRequest %s/%s hash string", dynamoComponentRequest.Namespace, dynamoComponentRequest.Name)
err = errors.Wrapf(err, "get DynamoComponent %s/%s hash string", DynamoComponent.Namespace, DynamoComponent.Name)
return
}
......@@ -394,8 +328,8 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
}
jobLabels := map[string]string{
commonconsts.KubeLabelDynamoComponentRequest: dynamoComponentRequest.Name,
commonconsts.KubeLabelIsDynamoImageBuilder: commonconsts.KubeLabelValueTrue,
commonconsts.KubeLabelDynamoComponent: DynamoComponent.Name,
commonconsts.KubeLabelIsDynamoImageBuilder: commonconsts.KubeLabelValueTrue,
}
jobs := &batchv1.JobList{}
......@@ -409,9 +343,9 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
for _, job_ := range jobs.Items {
job_ := job_
oldHash := job_.Annotations[consts.KubeAnnotationDynamoComponentRequestHash]
if oldHash != dynamoComponentRequestHashStr {
logs.Info("Because hash changed, delete old job", "job", job_.Name, "oldHash", oldHash, "newHash", dynamoComponentRequestHashStr)
oldHash := job_.Annotations[consts.KubeAnnotationDynamoComponentHash]
if oldHash != DynamoComponentHashStr {
logs.Info("Because hash changed, delete old job", "job", job_.Name, "oldHash", oldHash, "newHash", DynamoComponentHashStr)
// --cascade=foreground
err = r.Delete(ctx, &job_, &client.DeleteOptions{
PropagationPolicy: &[]metav1.DeletionPropagation{metav1.DeletePropagationForeground}[0],
......@@ -447,31 +381,31 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
if job == nil {
job, err = r.generateImageBuilderJob(ctx, GenerateImageBuilderJobOption{
ImageInfo: imageInfo,
DynamoComponentRequest: dynamoComponentRequest,
ImageInfo: imageInfo,
DynamoComponent: DynamoComponent,
})
if err != nil {
err = errors.Wrap(err, "generate image builder job")
return
}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderJob", "Creating image builder job: %s", job.Name)
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderJob", "Creating image builder job: %s", job.Name)
err = r.Create(ctx, job)
if err != nil {
err = errors.Wrapf(err, "create image builder job %s", job.Name)
return
}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderJob", "Created image builder job: %s", job.Name)
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderJob", "Created image builder job: %s", job.Name)
return
}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "CheckingImageBuilderJob", "Found image builder job: %s", job.Name)
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "CheckingImageBuilderJob", "Found image builder job: %s", job.Name)
err = r.Get(ctx, req.NamespacedName, dynamoComponentRequest)
err = r.Get(ctx, req.NamespacedName, DynamoComponent)
if err != nil {
logs.Error(err, "Failed to re-fetch dynamoComponentRequest")
logs.Error(err, "Failed to re-fetch DynamoComponent")
return
}
imageBuildingCondition := meta.FindStatusCondition(dynamoComponentRequest.Status.Conditions, nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding)
imageBuildingCondition := meta.FindStatusCondition(DynamoComponent.Status.Conditions, nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding)
isJobFailed := false
isJobRunning := true
......@@ -496,23 +430,23 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
conditions := make([]metav1.Condition, 0)
if job.Status.Active > 0 {
conditions = append(conditions, metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding,
Status: metav1.ConditionTrue,
Reason: "Reconciling",
Message: fmt.Sprintf("Image building job %s is running", job.Name),
})
} else {
conditions = append(conditions, metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: fmt.Sprintf("Image building job %s is waiting", job.Name),
})
}
if dynamoComponentRequest.Spec.ImageBuildTimeout != nil {
if imageBuildingCondition != nil && imageBuildingCondition.LastTransitionTime.Add(time.Duration(*dynamoComponentRequest.Spec.ImageBuildTimeout)).Before(time.Now()) {
if DynamoComponent.Spec.ImageBuildTimeout != nil {
if imageBuildingCondition != nil && imageBuildingCondition.LastTransitionTime.Add(time.Duration(*DynamoComponent.Spec.ImageBuildTimeout)).Before(time.Now()) {
conditions = append(conditions, metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding,
Status: metav1.ConditionFalse,
Reason: "Timeout",
Message: fmt.Sprintf("Image building job %s is timeout", job.Name),
......@@ -525,27 +459,27 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
}
}
if dynamoComponentRequest, err = r.setStatusConditions(ctx, req, conditions...); err != nil {
if DynamoComponent, err = r.setStatusConditions(ctx, req, conditions...); err != nil {
return
}
if imageBuildingCondition != nil && imageBuildingCondition.Status != metav1.ConditionTrue && isJobRunning {
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "DynamoComponentImageBuilder", "Image is building now")
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "DynamoComponentImageBuilder", "Image is building now")
}
return
}
if isJobFailed {
dynamoComponentRequest, err = r.setStatusConditions(ctx, req,
DynamoComponent, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: fmt.Sprintf("Image building job %s is failed.", job.Name),
},
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeDynamoComponentAvailable,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeDynamoComponentAvailable,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: fmt.Sprintf("Image building job %s is failed.", job.Name),
......@@ -557,15 +491,15 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
return
}
dynamoComponentRequest, err = r.setStatusConditions(ctx, req,
DynamoComponent, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageBuilding,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageBuilding,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: fmt.Sprintf("Image building job %s is succeeded.", job.Name),
},
metav1.Condition{
Type: nvidiacomv1alpha1.DynamoComponentRequestConditionTypeImageExists,
Type: nvidiacomv1alpha1.DynamoComponentConditionTypeImageExists,
Status: metav1.ConditionTrue,
Reason: "Reconciling",
Message: imageInfo.ImageName,
......@@ -575,40 +509,40 @@ func (r *DynamoComponentRequestReconciler) ensureImageExists(ctx context.Context
return
}
r.Recorder.Eventf(dynamoComponentRequest, corev1.EventTypeNormal, "DynamoComponentImageBuilder", "Image has been built successfully")
r.Recorder.Eventf(DynamoComponent, corev1.EventTypeNormal, "DynamoComponentImageBuilder", "Image has been built successfully")
imageExists = true
return
}
func (r *DynamoComponentRequestReconciler) setStatusConditions(ctx context.Context, req ctrl.Request, conditions ...metav1.Condition) (dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest, err error) {
dynamoComponentRequest = &nvidiacomv1alpha1.DynamoComponentRequest{}
func (r *DynamoComponentReconciler) setStatusConditions(ctx context.Context, req ctrl.Request, conditions ...metav1.Condition) (DynamoComponent *nvidiacomv1alpha1.DynamoComponent, err error) {
DynamoComponent = &nvidiacomv1alpha1.DynamoComponent{}
/*
Please don't blame me when you see this kind of code,
this is to avoid "the object has been modified; please apply your changes to the latest version and try again" when updating CR status,
don't doubt that almost all CRD operators (e.g. cert-manager) can't avoid this stupid error and can only try to avoid this by this stupid way.
*/
for i := 0; i < 3; i++ {
if err = r.Get(ctx, req.NamespacedName, dynamoComponentRequest); err != nil {
err = errors.Wrap(err, "Failed to re-fetch dynamoComponentRequest")
if err = r.Get(ctx, req.NamespacedName, DynamoComponent); err != nil {
err = errors.Wrap(err, "Failed to re-fetch DynamoComponent")
return
}
for _, condition := range conditions {
meta.SetStatusCondition(&dynamoComponentRequest.Status.Conditions, condition)
meta.SetStatusCondition(&DynamoComponent.Status.Conditions, condition)
}
if err = r.Status().Update(ctx, dynamoComponentRequest); err != nil {
if err = r.Status().Update(ctx, DynamoComponent); err != nil {
time.Sleep(100 * time.Millisecond)
} else {
break
}
}
if err != nil {
err = errors.Wrap(err, "Failed to update dynamoComponentRequest status")
err = errors.Wrap(err, "Failed to update DynamoComponent status")
return
}
if err = r.Get(ctx, req.NamespacedName, dynamoComponentRequest); err != nil {
err = errors.Wrap(err, "Failed to re-fetch dynamoComponentRequest")
if err = r.Get(ctx, req.NamespacedName, DynamoComponent); err != nil {
err = errors.Wrap(err, "Failed to re-fetch DynamoComponent")
return
}
return
......@@ -635,85 +569,7 @@ func getDynamoComponentImageBuildEngine() DynamoComponentImageBuildEngine {
}
//nolint:nakedret
func (r *DynamoComponentRequestReconciler) makeSureDockerConfigJSONSecret(ctx context.Context, namespace string, dockerRegistryConf *commonconfig.DockerRegistryConfig) (dockerConfigJSONSecret *corev1.Secret, err error) {
if dockerRegistryConf.Username == "" {
return
}
// nolint: gosec
dockerConfigSecretName := commonconsts.KubeSecretNameRegcred
dockerConfigObj := struct {
Auths map[string]struct {
Auth string `json:"auth"`
} `json:"auths"`
}{
Auths: map[string]struct {
Auth string `json:"auth"`
}{
dockerRegistryConf.Server: {
Auth: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", dockerRegistryConf.Username, dockerRegistryConf.Password))),
},
},
}
dockerConfigContent, err := json.Marshal(dockerConfigObj)
if err != nil {
err = errors.Wrap(err, "marshal docker config")
return nil, err
}
dockerConfigJSONSecret = &corev1.Secret{}
err = r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: dockerConfigSecretName}, dockerConfigJSONSecret)
dockerConfigIsNotFound := k8serrors.IsNotFound(err)
// nolint: gocritic
if err != nil && !dockerConfigIsNotFound {
err = errors.Wrap(err, "get docker config secret")
return nil, err
}
err = nil
if dockerConfigIsNotFound {
dockerConfigJSONSecret = &corev1.Secret{
Type: corev1.SecretTypeDockerConfigJson,
ObjectMeta: metav1.ObjectMeta{
Name: dockerConfigSecretName,
Namespace: namespace,
},
Data: map[string][]byte{
".dockerconfigjson": dockerConfigContent,
},
}
err_ := r.Create(ctx, dockerConfigJSONSecret)
if err_ != nil {
dockerConfigJSONSecret = &corev1.Secret{}
err = r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: dockerConfigSecretName}, dockerConfigJSONSecret)
dockerConfigIsNotFound = k8serrors.IsNotFound(err)
if err != nil && !dockerConfigIsNotFound {
err = errors.Wrap(err, "get docker config secret")
return nil, err
}
if dockerConfigIsNotFound {
err_ = errors.Wrap(err_, "create docker config secret")
return nil, err_
}
if err != nil {
err = nil
}
}
} else {
dockerConfigJSONSecret.Data[".dockerconfigjson"] = dockerConfigContent
err = r.Update(ctx, dockerConfigJSONSecret)
if err != nil {
err = errors.Wrap(err, "update docker config secret")
return nil, err
}
}
return
}
//nolint:nakedret
func (r *DynamoComponentRequestReconciler) getApiStoreClient(ctx context.Context) (*apiStoreClient.ApiStoreClient, *commonconfig.ApiStoreConfig, error) {
func (r *DynamoComponentReconciler) getApiStoreClient(ctx context.Context) (*apiStoreClient.ApiStoreClient, *commonconfig.ApiStoreConfig, error) {
apiStoreConf, err := commonconfig.GetApiStoreConfig(ctx)
isNotFound := k8serrors.IsNotFound(err)
if err != nil && !isNotFound {
......@@ -738,84 +594,69 @@ func (r *DynamoComponentRequestReconciler) getApiStoreClient(ctx context.Context
return apiStoreClient, apiStoreConf, nil
}
//nolint:nakedret
func (r *DynamoComponentRequestReconciler) getDockerRegistry(ctx context.Context, dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest) (dockerRegistry schemas.DockerRegistrySchema, err error) {
if dynamoComponentRequest != nil && dynamoComponentRequest.Spec.DockerConfigJSONSecretName != "" {
secret := &corev1.Secret{}
err = r.Get(ctx, types.NamespacedName{
Namespace: dynamoComponentRequest.Namespace,
Name: dynamoComponentRequest.Spec.DockerConfigJSONSecretName,
}, secret)
if err != nil {
err = errors.Wrapf(err, "get docker config json secret %s", dynamoComponentRequest.Spec.DockerConfigJSONSecretName)
return
}
configJSON, ok := secret.Data[".dockerconfigjson"]
if !ok {
err = errors.Errorf("docker config json secret %s does not have .dockerconfigjson key", dynamoComponentRequest.Spec.DockerConfigJSONSecretName)
return
}
var configObj struct {
Auths map[string]struct {
Auth string `json:"auth"`
} `json:"auths"`
}
err = json.Unmarshal(configJSON, &configObj)
if err != nil {
err = errors.Wrapf(err, "unmarshal docker config json secret %s", dynamoComponentRequest.Spec.DockerConfigJSONSecretName)
return
func (r *DynamoComponentReconciler) RetrieveDockerRegistrySecret(ctx context.Context, secretName string, namespace string, dockerRegistry *schemas.DockerRegistrySchema) error {
secret := &corev1.Secret{}
err := r.Get(ctx, types.NamespacedName{
Namespace: namespace,
Name: secretName,
}, secret)
if err != nil {
err = errors.Wrapf(err, "get docker config json secret %s", secretName)
return err
}
configJSON, ok := secret.Data[dockerConfigSecretKey]
if !ok {
err = errors.Errorf("docker config json secret %s does not have %s key", secretName, dockerConfigSecretKey)
return err
}
var configObj struct {
Auths map[string]struct {
Auth string `json:"auth"`
} `json:"auths"`
}
err = json.Unmarshal(configJSON, &configObj)
if err != nil {
err = errors.Wrapf(err, "unmarshal docker config json secret %s", secretName)
return err
}
var server string
var auth string
if dockerRegistry.Server != "" {
for k, v := range configObj.Auths {
if k == dockerRegistry.Server {
server = k
auth = v.Auth
break
}
}
imageRegistryURI, _, _ := xstrings.Partition(dynamoComponentRequest.Spec.Image, "/")
var server string
var auth string
if imageRegistryURI != "" {
if server == "" {
for k, v := range configObj.Auths {
if k == imageRegistryURI {
if strings.Contains(k, dockerRegistry.Server) {
server = k
auth = v.Auth
break
}
}
if server == "" {
for k, v := range configObj.Auths {
if strings.Contains(k, imageRegistryURI) {
server = k
auth = v.Auth
break
}
}
}
}
if server == "" {
for k, v := range configObj.Auths {
server = k
auth = v.Auth
break
}
}
if server == "" {
err = errors.Errorf("no auth in docker config json secret %s", dynamoComponentRequest.Spec.DockerConfigJSONSecretName)
return
}
dockerRegistry.Server = server
var credentials []byte
credentials, err = base64.StdEncoding.DecodeString(auth)
if err != nil {
err = errors.Wrapf(err, "cannot base64 decode auth in docker config json secret %s", dynamoComponentRequest.Spec.DockerConfigJSONSecretName)
return
}
dockerRegistry.Username, _, dockerRegistry.Password = xstrings.Partition(string(credentials), ":")
if dynamoComponentRequest.Spec.OCIRegistryInsecure != nil {
dockerRegistry.Secure = !*dynamoComponentRequest.Spec.OCIRegistryInsecure
}
return
}
dockerRegistryConfig, err := commonconfig.GetDockerRegistryConfig()
if server == "" {
err = errors.Errorf("no auth in docker config json secret %s for server %s", secretName, dockerRegistry.Server)
return err
}
var credentials []byte
credentials, err = base64.StdEncoding.DecodeString(auth)
if err != nil {
err = errors.Wrap(err, "get docker registry")
return
err = errors.Wrapf(err, "cannot base64 decode auth in docker config json secret %s", secretName)
return err
}
dockerRegistry.Username, _, dockerRegistry.Password = xstrings.Partition(string(credentials), ":")
return nil
}
//nolint:nakedret
func (r *DynamoComponentReconciler) getDockerRegistry(ctx context.Context, DynamoComponent *nvidiacomv1alpha1.DynamoComponent) (dockerRegistry *schemas.DockerRegistrySchema, err error) {
dockerRegistryConfig := commonconfig.GetDockerRegistryConfig()
dynamoRepositoryName := "dynamo-components"
if dockerRegistryConfig.DynamoComponentsRepositoryName != "" {
......@@ -825,20 +666,22 @@ func (r *DynamoComponentRequestReconciler) getDockerRegistry(ctx context.Context
if strings.Contains(dockerRegistryConfig.Server, "docker.io") {
dynamoRepositoryURI = fmt.Sprintf("docker.io/%s", dynamoRepositoryName)
}
dynamoRepositoryInClusterURI := dynamoRepositoryURI
if dockerRegistryConfig.InClusterServer != "" {
dynamoRepositoryInClusterURI = fmt.Sprintf("%s/%s", strings.TrimRight(dockerRegistryConfig.InClusterServer, "/"), dynamoRepositoryName)
if strings.Contains(dockerRegistryConfig.InClusterServer, "docker.io") {
dynamoRepositoryInClusterURI = fmt.Sprintf("docker.io/%s", dynamoRepositoryName)
}
if DynamoComponent != nil && DynamoComponent.Spec.DockerConfigJSONSecretName != "" {
dockerRegistryConfig.SecretName = DynamoComponent.Spec.DockerConfigJSONSecretName
}
dockerRegistry = schemas.DockerRegistrySchema{
Server: dockerRegistryConfig.Server,
Username: dockerRegistryConfig.Username,
Password: dockerRegistryConfig.Password,
Secure: dockerRegistryConfig.Secure,
DynamoRepositoryURI: dynamoRepositoryURI,
DynamoRepositoryURIInCluster: dynamoRepositoryInClusterURI,
dockerRegistry = &schemas.DockerRegistrySchema{
Server: dockerRegistryConfig.Server,
Secure: dockerRegistryConfig.Secure,
DynamoRepositoryURI: dynamoRepositoryURI,
SecretName: dockerRegistryConfig.SecretName,
}
err = r.RetrieveDockerRegistrySecret(ctx, dockerRegistryConfig.SecretName, DynamoComponent.Namespace, dockerRegistry)
if err != nil {
err = errors.Wrap(err, "retrieve docker registry secret")
return
}
return
......@@ -848,49 +691,45 @@ func isAddNamespacePrefix() bool {
return os.Getenv("ADD_NAMESPACE_PREFIX_TO_IMAGE_NAME") == trueStr
}
func getDynamoComponentImagePrefix(dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest) string {
if dynamoComponentRequest == nil {
func getDynamoComponentImagePrefix(DynamoComponent *nvidiacomv1alpha1.DynamoComponent) string {
if DynamoComponent == nil {
return ""
}
prefix, exist := dynamoComponentRequest.Annotations[consts.KubeAnnotationDynamoComponentStorageNS]
prefix, exist := DynamoComponent.Annotations[consts.KubeAnnotationDynamoComponentStorageNS]
if exist && prefix != "" {
return fmt.Sprintf("%s.", prefix)
}
if isAddNamespacePrefix() {
return fmt.Sprintf("%s.", dynamoComponentRequest.Namespace)
return fmt.Sprintf("%s.", DynamoComponent.Namespace)
}
return ""
}
func getDynamoComponentImageName(dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest, dockerRegistry schemas.DockerRegistrySchema, dynamoComponentRepositoryName, dynamoComponentVersion string, inCluster bool) string {
if dynamoComponentRequest != nil && dynamoComponentRequest.Spec.Image != "" {
return dynamoComponentRequest.Spec.Image
func getDynamoComponentImageName(DynamoComponent *nvidiacomv1alpha1.DynamoComponent, dockerRegistry schemas.DockerRegistrySchema, dynamoComponentRepositoryName, dynamoComponentVersion string) string {
if DynamoComponent != nil && DynamoComponent.Spec.Image != "" {
return DynamoComponent.Spec.Image
}
var uri, tag string
if inCluster {
uri = dockerRegistry.DynamoRepositoryURIInCluster
} else {
uri = dockerRegistry.DynamoRepositoryURI
}
uri = dockerRegistry.DynamoRepositoryURI
tail := fmt.Sprintf("%s.%s", dynamoComponentRepositoryName, dynamoComponentVersion)
if isEstargzEnabled() {
tail += ".esgz"
}
tag = fmt.Sprintf("dynamo.%s%s", getDynamoComponentImagePrefix(dynamoComponentRequest), tail)
tag = fmt.Sprintf("dynamo.%s%s", getDynamoComponentImagePrefix(DynamoComponent), tail)
if len(tag) > 128 {
hashStr := hash(tail)
tag = fmt.Sprintf("dynamo.%s%s", getDynamoComponentImagePrefix(dynamoComponentRequest), hashStr)
tag = fmt.Sprintf("dynamo.%s%s", getDynamoComponentImagePrefix(DynamoComponent), hashStr)
if len(tag) > 128 {
tag = fmt.Sprintf("dynamo.%s", hash(fmt.Sprintf("%s%s", getDynamoComponentImagePrefix(dynamoComponentRequest), tail)))[:128]
tag = fmt.Sprintf("dynamo.%s", hash(fmt.Sprintf("%s%s", getDynamoComponentImagePrefix(DynamoComponent), tail)))[:128]
}
}
return fmt.Sprintf("%s:%s", uri, tag)
}
func checkImageExists(dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest, dockerRegistry schemas.DockerRegistrySchema, imageName string) (bool, error) {
if dynamoComponentRequest.Annotations["nvidia.com/force-build-image"] == commonconsts.KubeLabelValueTrue {
func checkImageExists(DynamoComponent *nvidiacomv1alpha1.DynamoComponent, dockerRegistry schemas.DockerRegistrySchema, imageName string) (bool, error) {
if DynamoComponent.Annotations["nvidia.com/force-build-image"] == commonconsts.KubeLabelValueTrue {
return false, nil
}
......@@ -930,72 +769,46 @@ type ImageInfo struct {
DockerRegistry schemas.DockerRegistrySchema
DockerConfigJSONSecretName string
ImageName string
InClusterImageName string
DockerRegistryInsecure bool
}
type GetImageInfoOption struct {
DynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest
DynamoComponent *nvidiacomv1alpha1.DynamoComponent
}
//nolint:nakedret
func (r *DynamoComponentRequestReconciler) getImageInfo(ctx context.Context, opt GetImageInfoOption) (imageInfo ImageInfo, err error) {
dynamoComponentRepositoryName, _, dynamoComponentVersion := xstrings.Partition(opt.DynamoComponentRequest.Spec.DynamoComponent, ":")
dockerRegistry, err := r.getDockerRegistry(ctx, opt.DynamoComponentRequest)
func (r *DynamoComponentReconciler) getImageInfo(ctx context.Context, opt GetImageInfoOption) (imageInfo ImageInfo, err error) {
dynamoComponentRepositoryName, _, dynamoComponentVersion := xstrings.Partition(opt.DynamoComponent.Spec.DynamoComponent, ":")
dockerRegistry, err := r.getDockerRegistry(ctx, opt.DynamoComponent)
if err != nil {
err = errors.Wrap(err, "get docker registry")
return
}
imageInfo.DockerRegistry = dockerRegistry
imageInfo.ImageName = getDynamoComponentImageName(opt.DynamoComponentRequest, dockerRegistry, dynamoComponentRepositoryName, dynamoComponentVersion, false)
imageInfo.InClusterImageName = getDynamoComponentImageName(opt.DynamoComponentRequest, dockerRegistry, dynamoComponentRepositoryName, dynamoComponentVersion, true)
imageInfo.DockerRegistry = *dockerRegistry
imageInfo.ImageName = getDynamoComponentImageName(opt.DynamoComponent, *dockerRegistry, dynamoComponentRepositoryName, dynamoComponentVersion)
imageInfo.DockerConfigJSONSecretName = opt.DynamoComponentRequest.Spec.DockerConfigJSONSecretName
imageInfo.DockerRegistryInsecure = opt.DynamoComponentRequest.Annotations[commonconsts.KubeAnnotationDynamoDockerRegistryInsecure] == "true"
if opt.DynamoComponentRequest.Spec.OCIRegistryInsecure != nil {
imageInfo.DockerRegistryInsecure = *opt.DynamoComponentRequest.Spec.OCIRegistryInsecure
}
imageInfo.DockerConfigJSONSecretName = dockerRegistry.SecretName
if imageInfo.DockerConfigJSONSecretName == "" {
var dockerRegistryConf *commonconfig.DockerRegistryConfig
dockerRegistryConf, err = commonconfig.GetDockerRegistryConfig()
if err != nil {
err = errors.Wrap(err, "get docker registry")
return
}
imageInfo.DockerRegistryInsecure = !dockerRegistryConf.Secure
var dockerConfigSecret *corev1.Secret
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Making sure docker config secret %s in namespace %s", commonconsts.KubeSecretNameRegcred, opt.DynamoComponentRequest.Namespace)
dockerConfigSecret, err = r.makeSureDockerConfigJSONSecret(ctx, opt.DynamoComponentRequest.Namespace, dockerRegistryConf)
if err != nil {
err = errors.Wrap(err, "make sure docker config secret")
return
}
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Docker config secret %s in namespace %s is ready", commonconsts.KubeSecretNameRegcred, opt.DynamoComponentRequest.Namespace)
if dockerConfigSecret != nil {
imageInfo.DockerConfigJSONSecretName = dockerConfigSecret.Name
}
}
imageInfo.DockerRegistryInsecure = opt.DynamoComponent.Annotations[commonconsts.KubeAnnotationDynamoDockerRegistryInsecure] == "true"
return
}
func (r *DynamoComponentRequestReconciler) getImageBuilderJobName() string {
func (r *DynamoComponentReconciler) getImageBuilderJobName() string {
guid := xid.New()
return fmt.Sprintf("dynamo-image-builder-%s", guid.String())
}
func (r *DynamoComponentRequestReconciler) getImageBuilderJobLabels(dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest) map[string]string {
func (r *DynamoComponentReconciler) getImageBuilderJobLabels(DynamoComponent *nvidiacomv1alpha1.DynamoComponent) map[string]string {
return map[string]string{
commonconsts.KubeLabelDynamoComponentRequest: dynamoComponentRequest.Name,
commonconsts.KubeLabelIsDynamoImageBuilder: "true",
commonconsts.KubeLabelDynamoComponent: DynamoComponent.Name,
commonconsts.KubeLabelIsDynamoImageBuilder: "true",
}
}
func (r *DynamoComponentRequestReconciler) getImageBuilderPodLabels(dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest) map[string]string {
func (r *DynamoComponentReconciler) getImageBuilderPodLabels(DynamoComponent *nvidiacomv1alpha1.DynamoComponent) map[string]string {
return map[string]string{
commonconsts.KubeLabelDynamoComponentRequest: dynamoComponentRequest.Name,
commonconsts.KubeLabelIsDynamoImageBuilder: "true",
commonconsts.KubeLabelDynamoComponent: DynamoComponent.Name,
commonconsts.KubeLabelIsDynamoImageBuilder: "true",
}
}
......@@ -1007,12 +820,12 @@ func hash(text string) string {
}
type GenerateImageBuilderJobOption struct {
ImageInfo ImageInfo
DynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest
ImageInfo ImageInfo
DynamoComponent *nvidiacomv1alpha1.DynamoComponent
}
//nolint:nakedret
func (r *DynamoComponentRequestReconciler) generateImageBuilderJob(ctx context.Context, opt GenerateImageBuilderJobOption) (job *batchv1.Job, err error) {
func (r *DynamoComponentReconciler) generateImageBuilderJob(ctx context.Context, opt GenerateImageBuilderJobOption) (job *batchv1.Job, err error) {
// nolint: gosimple
podTemplateSpec, err := r.generateImageBuilderPodTemplateSpec(ctx, GenerateImageBuilderPodTemplateSpecOption(opt))
if err != nil {
......@@ -1020,17 +833,17 @@ func (r *DynamoComponentRequestReconciler) generateImageBuilderJob(ctx context.C
return
}
kubeAnnotations := make(map[string]string)
hashStr, err := r.getHashStr(opt.DynamoComponentRequest)
hashStr, err := r.getHashStr(opt.DynamoComponent)
if err != nil {
err = errors.Wrap(err, "failed to get hash string")
return
}
kubeAnnotations[consts.KubeAnnotationDynamoComponentRequestHash] = hashStr
kubeAnnotations[consts.KubeAnnotationDynamoComponentHash] = hashStr
job = &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: r.getImageBuilderJobName(),
Namespace: opt.DynamoComponentRequest.Namespace,
Labels: r.getImageBuilderJobLabels(opt.DynamoComponentRequest),
Namespace: opt.DynamoComponent.Namespace,
Labels: r.getImageBuilderJobLabels(opt.DynamoComponent),
Annotations: kubeAnnotations,
},
Spec: batchv1.JobSpec{
......@@ -1052,7 +865,7 @@ func (r *DynamoComponentRequestReconciler) generateImageBuilderJob(ctx context.C
Template: *podTemplateSpec,
},
}
err = ctrl.SetControllerReference(opt.DynamoComponentRequest, job, r.Scheme)
err = ctrl.SetControllerReference(opt.DynamoComponent, job, r.Scheme)
if err != nil {
err = errors.Wrapf(err, "set controller reference for job %s", job.Name)
return
......@@ -1060,7 +873,7 @@ func (r *DynamoComponentRequestReconciler) generateImageBuilderJob(ctx context.C
return
}
func injectPodAffinity(podSpec *corev1.PodSpec, dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest) {
func injectPodAffinity(podSpec *corev1.PodSpec, DynamoComponent *nvidiacomv1alpha1.DynamoComponent) {
if podSpec.Affinity == nil {
podSpec.Affinity = &corev1.Affinity{}
}
......@@ -1074,7 +887,7 @@ func injectPodAffinity(podSpec *corev1.PodSpec, dynamoComponentRequest *nvidiaco
PodAffinityTerm: corev1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
commonconsts.KubeLabelDynamoComponentRequest: dynamoComponentRequest.Name,
commonconsts.KubeLabelDynamoComponent: DynamoComponent.Name,
},
},
TopologyKey: corev1.LabelHostname,
......@@ -1088,16 +901,16 @@ const ModelSeederContainerName = "seeder"
const ModelSeederJobFailedExitCode = 42
type GenerateImageBuilderPodTemplateSpecOption struct {
ImageInfo ImageInfo
DynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest
ImageInfo ImageInfo
DynamoComponent *nvidiacomv1alpha1.DynamoComponent
}
//nolint:gocyclo,nakedret
func (r *DynamoComponentRequestReconciler) generateImageBuilderPodTemplateSpec(ctx context.Context, opt GenerateImageBuilderPodTemplateSpecOption) (pod *corev1.PodTemplateSpec, err error) {
dynamoComponentRepositoryName, _, dynamoComponentVersion := xstrings.Partition(opt.DynamoComponentRequest.Spec.DynamoComponent, ":")
kubeLabels := r.getImageBuilderPodLabels(opt.DynamoComponentRequest)
func (r *DynamoComponentReconciler) generateImageBuilderPodTemplateSpec(ctx context.Context, opt GenerateImageBuilderPodTemplateSpecOption) (pod *corev1.PodTemplateSpec, err error) {
dynamoComponentRepositoryName, _, dynamoComponentVersion := xstrings.Partition(opt.DynamoComponent.Spec.DynamoComponent, ":")
kubeLabels := r.getImageBuilderPodLabels(opt.DynamoComponent)
inClusterImageName := opt.ImageInfo.InClusterImageName
imageName := opt.ImageInfo.ImageName
dockerConfigJSONSecretName := opt.ImageInfo.DockerConfigJSONSecretName
......@@ -1151,7 +964,7 @@ func (r *DynamoComponentRequestReconciler) generateImageBuilderPodTemplateSpec(c
}
var dynamoComponent *schemas.DynamoComponent
dynamoComponentDownloadURL := opt.DynamoComponentRequest.Spec.DownloadURL
dynamoComponentDownloadURL := opt.DynamoComponent.Spec.DownloadURL
if dynamoComponentDownloadURL == "" {
var apiStoreClient *apiStoreClient.ApiStoreClient
......@@ -1168,23 +981,23 @@ func (r *DynamoComponentRequestReconciler) generateImageBuilderPodTemplateSpec(c
return
}
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Getting dynamoComponent %s from api store service", opt.DynamoComponentRequest.Spec.DynamoComponent)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Getting dynamoComponent %s from api store service", opt.DynamoComponent.Spec.DynamoComponent)
dynamoComponent, err = apiStoreClient.GetDynamoComponent(ctx, dynamoComponentRepositoryName, dynamoComponentVersion)
if err != nil {
err = errors.Wrap(err, "get dynamoComponent")
return
}
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Got dynamoComponent %s from api store service", opt.DynamoComponentRequest.Spec.DynamoComponent)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Got dynamoComponent %s from api store service", opt.DynamoComponent.Spec.DynamoComponent)
if dynamoComponent.TransmissionStrategy != nil && *dynamoComponent.TransmissionStrategy == schemas.TransmissionStrategyPresignedURL {
var dynamoComponent_ *schemas.DynamoComponent
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Getting presigned url for dynamoComponent %s from api store service", opt.DynamoComponentRequest.Spec.DynamoComponent)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Getting presigned url for dynamoComponent %s from api store service", opt.DynamoComponent.Spec.DynamoComponent)
dynamoComponent_, err = apiStoreClient.PresignDynamoComponentDownloadURL(ctx, dynamoComponentRepositoryName, dynamoComponentVersion)
if err != nil {
err = errors.Wrap(err, "presign dynamoComponent download url")
return
}
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Got presigned url for dynamoComponent %s from api store service", opt.DynamoComponentRequest.Spec.DynamoComponent)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Got presigned url for dynamoComponent %s from api store service", opt.DynamoComponent.Spec.DynamoComponent)
dynamoComponentDownloadURL = dynamoComponent_.PresignedDownloadUrl
} else {
dynamoComponentDownloadURL = fmt.Sprintf("%s/api/v1/dynamo_nims/%s/versions/%s/download", apiStoreConf.Endpoint, dynamoComponentRepositoryName, dynamoComponentVersion)
......@@ -1256,7 +1069,7 @@ echo "Done"
},
}
downloaderContainerEnvFrom := opt.DynamoComponentRequest.Spec.DownloaderContainerEnvFrom
downloaderContainerEnvFrom := opt.DynamoComponent.Spec.DownloaderContainerEnvFrom
initContainers := []corev1.Container{
{
......@@ -1295,7 +1108,7 @@ echo "Done"
}
configCmName := "dynamo-image-builder-config"
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Getting configmap %s from namespace %s", configCmName, configNamespace)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Getting configmap %s from namespace %s", configCmName, configNamespace)
configCm := &corev1.ConfigMap{}
err = r.Get(ctx, types.NamespacedName{Name: configCmName, Namespace: configNamespace}, configCm)
configCmIsNotFound := k8serrors.IsNotFound(err)
......@@ -1306,7 +1119,7 @@ echo "Done"
err = nil // nolint: ineffassign
if !configCmIsNotFound {
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Configmap %s is got from namespace %s", configCmName, configNamespace)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Configmap %s is got from namespace %s", configCmName, configNamespace)
globalExtraPodMetadata = &dynamoCommon.ExtraPodMetadata{}
......@@ -1367,15 +1180,15 @@ echo "Done"
}
logrus.Info("passed in builder args: ", builderArgs)
} else {
r.Recorder.Eventf(opt.DynamoComponentRequest, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Configmap %s is not found in namespace %s", configCmName, configNamespace)
r.Recorder.Eventf(opt.DynamoComponent, corev1.EventTypeNormal, "GenerateImageBuilderPod", "Configmap %s is not found in namespace %s", configCmName, configNamespace)
}
if buildArgs == nil {
buildArgs = make([]string, 0)
}
if opt.DynamoComponentRequest.Spec.BuildArgs != nil {
buildArgs = append(buildArgs, opt.DynamoComponentRequest.Spec.BuildArgs...)
if opt.DynamoComponent.Spec.BuildArgs != nil {
buildArgs = append(buildArgs, opt.DynamoComponent.Spec.BuildArgs...)
}
dockerFilePath := "/workspace/buildcontext/env/docker/Dockerfile"
......@@ -1394,11 +1207,11 @@ echo "Done"
kanikoCacheRepo := os.Getenv("KANIKO_CACHE_REPO")
if kanikoCacheRepo == "" {
kanikoCacheRepo = opt.ImageInfo.DockerRegistry.DynamoRepositoryURIInCluster
kanikoCacheRepo = opt.ImageInfo.DockerRegistry.DynamoRepositoryURI
}
kubeAnnotations := make(map[string]string)
kubeAnnotations[consts.KubeAnnotationDynamoComponentRequestImageBuiderHash] = opt.DynamoComponentRequest.Annotations[consts.KubeAnnotationDynamoComponentRequestImageBuiderHash]
kubeAnnotations[consts.KubeAnnotationDynamoComponentImageBuiderHash] = opt.DynamoComponent.Annotations[consts.KubeAnnotationDynamoComponentImageBuiderHash]
command := []string{
"/kaniko/executor",
......@@ -1414,7 +1227,7 @@ echo "Done"
"--compression-level=-7",
fmt.Sprintf("--dockerfile=%s", dockerFilePath),
fmt.Sprintf("--insecure=%v", dockerRegistryInsecure),
fmt.Sprintf("--destination=%s", inClusterImageName),
fmt.Sprintf("--destination=%s", imageName),
}
kanikoSnapshotMode := os.Getenv("KANIKO_SNAPSHOT_MODE")
......@@ -1444,7 +1257,7 @@ echo "Done"
isBuildkit := buildEngine == DynamoComponentImageBuildEngineBuildkit || buildEngine == DynamoComponentImageBuildEngineBuildkitRootless
if isBuildkit {
output := fmt.Sprintf("type=image,name=%s,push=true,registry.insecure=%v", inClusterImageName, dockerRegistryInsecure)
output := fmt.Sprintf("type=image,name=%s,push=true,registry.insecure=%v", imageName, dockerRegistryInsecure)
buildkitdFlags := []string{}
if !privileged {
buildkitdFlags = append(buildkitdFlags, "--oci-worker-no-process-sandbox")
......@@ -1540,8 +1353,8 @@ echo "Done"
container.Resources = *globalDefaultImageBuilderContainerResources
}
if opt.DynamoComponentRequest.Spec.ImageBuilderContainerResources != nil {
container.Resources = *opt.DynamoComponentRequest.Spec.ImageBuilderContainerResources
if opt.DynamoComponent.Spec.ImageBuilderContainerResources != nil {
container.Resources = *opt.DynamoComponent.Spec.ImageBuilderContainerResources
}
containers = append(containers, container)
......@@ -1569,12 +1382,12 @@ echo "Done"
}
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodMetadata != nil {
for k, v := range opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodMetadata.Annotations {
if opt.DynamoComponent.Spec.ImageBuilderExtraPodMetadata != nil {
for k, v := range opt.DynamoComponent.Spec.ImageBuilderExtraPodMetadata.Annotations {
pod.Annotations[k] = v
}
for k, v := range opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodMetadata.Labels {
for k, v := range opt.DynamoComponent.Spec.ImageBuilderExtraPodMetadata.Labels {
pod.Labels[k] = v
}
}
......@@ -1589,45 +1402,45 @@ echo "Done"
pod.Spec.ServiceAccountName = globalExtraPodSpec.ServiceAccountName
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec != nil {
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.PriorityClassName != "" {
pod.Spec.PriorityClassName = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.PriorityClassName
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec != nil {
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.PriorityClassName != "" {
pod.Spec.PriorityClassName = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.PriorityClassName
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.SchedulerName != "" {
pod.Spec.SchedulerName = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.SchedulerName
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.SchedulerName != "" {
pod.Spec.SchedulerName = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.SchedulerName
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.NodeSelector != nil {
pod.Spec.NodeSelector = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.NodeSelector
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.NodeSelector != nil {
pod.Spec.NodeSelector = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.NodeSelector
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.Affinity != nil {
pod.Spec.Affinity = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.Affinity
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.Affinity != nil {
pod.Spec.Affinity = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.Affinity
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.Tolerations != nil {
pod.Spec.Tolerations = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.Tolerations
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.Tolerations != nil {
pod.Spec.Tolerations = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.Tolerations
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.TopologySpreadConstraints != nil {
pod.Spec.TopologySpreadConstraints = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.TopologySpreadConstraints
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.TopologySpreadConstraints != nil {
pod.Spec.TopologySpreadConstraints = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.TopologySpreadConstraints
}
if opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.ServiceAccountName != "" {
pod.Spec.ServiceAccountName = opt.DynamoComponentRequest.Spec.ImageBuilderExtraPodSpec.ServiceAccountName
if opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.ServiceAccountName != "" {
pod.Spec.ServiceAccountName = opt.DynamoComponent.Spec.ImageBuilderExtraPodSpec.ServiceAccountName
}
}
injectPodAffinity(&pod.Spec, opt.DynamoComponentRequest)
injectPodAffinity(&pod.Spec, opt.DynamoComponent)
if pod.Spec.ServiceAccountName == "" {
serviceAccounts := &corev1.ServiceAccountList{}
err = r.List(ctx, serviceAccounts, client.InNamespace(opt.DynamoComponentRequest.Namespace), client.MatchingLabels{
err = r.List(ctx, serviceAccounts, client.InNamespace(opt.DynamoComponent.Namespace), client.MatchingLabels{
commonconsts.KubeLabelDynamoImageBuilderPod: commonconsts.KubeLabelValueTrue,
})
if err != nil {
err = errors.Wrapf(err, "failed to list service accounts in namespace %s", opt.DynamoComponentRequest.Namespace)
err = errors.Wrapf(err, "failed to list service accounts in namespace %s", opt.DynamoComponent.Namespace)
return
}
if len(serviceAccounts.Items) > 0 {
......@@ -1642,7 +1455,7 @@ echo "Done"
if globalExtraContainerEnv != nil {
env = append(env, globalExtraContainerEnv...)
}
env = append(env, opt.DynamoComponentRequest.Spec.ImageBuilderExtraContainerEnv...)
env = append(env, opt.DynamoComponent.Spec.ImageBuilderExtraContainerEnv...)
pod.Spec.InitContainers[i].Env = env
}
for i, c := range pod.Spec.Containers {
......@@ -1650,26 +1463,26 @@ echo "Done"
if globalExtraContainerEnv != nil {
env = append(env, globalExtraContainerEnv...)
}
env = append(env, opt.DynamoComponentRequest.Spec.ImageBuilderExtraContainerEnv...)
env = append(env, opt.DynamoComponent.Spec.ImageBuilderExtraContainerEnv...)
pod.Spec.Containers[i].Env = env
}
return
}
func (r *DynamoComponentRequestReconciler) getHashStr(dynamoComponentRequest *nvidiacomv1alpha1.DynamoComponentRequest) (string, error) {
func (r *DynamoComponentReconciler) getHashStr(DynamoComponent *nvidiacomv1alpha1.DynamoComponent) (string, error) {
var hash uint64
hash, err := hashstructure.Hash(struct {
Spec nvidiacomv1alpha1.DynamoComponentRequestSpec
Spec nvidiacomv1alpha1.DynamoComponentSpec
Labels map[string]string
Annotations map[string]string
}{
Spec: dynamoComponentRequest.Spec,
Labels: dynamoComponentRequest.Labels,
Annotations: dynamoComponentRequest.Annotations,
Spec: DynamoComponent.Spec,
Labels: DynamoComponent.Labels,
Annotations: DynamoComponent.Annotations,
}, hashstructure.FormatV2, nil)
if err != nil {
err = errors.Wrap(err, "get dynamoComponentRequest CR spec hash")
err = errors.Wrap(err, "get DynamoComponent CR spec hash")
return "", err
}
hashStr := strconv.FormatUint(hash, 10)
......@@ -1681,13 +1494,13 @@ const (
)
// SetupWithManager sets up the controller with the Manager.
func (r *DynamoComponentRequestReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *DynamoComponentReconciler) SetupWithManager(mgr ctrl.Manager) error {
err := ctrl.NewControllerManagedBy(mgr).
For(&nvidiacomv1alpha1.DynamoComponentRequest{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
For(&nvidiacomv1alpha1.DynamoComponent{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&nvidiacomv1alpha1.DynamoComponent{}).
Owns(&batchv1.Job{}).
WithEventFilter(controller_common.EphemeralDeploymentEventFilter(r.Config)).
Complete(r)
return errors.Wrap(err, "failed to setup DynamoComponentRequest controller")
return errors.Wrap(err, "failed to setup DynamoComponent controller")
}
......@@ -39,6 +39,7 @@ import (
dynamoCommon "github.com/ai-dynamo/dynamo/deploy/dynamo/operator/api/dynamo/common"
"github.com/ai-dynamo/dynamo/deploy/dynamo/operator/api/dynamo/schemas"
"github.com/ai-dynamo/dynamo/deploy/dynamo/operator/api/v1alpha1"
"github.com/ai-dynamo/dynamo/deploy/dynamo/operator/internal/config"
commonconsts "github.com/ai-dynamo/dynamo/deploy/dynamo/operator/internal/consts"
"github.com/ai-dynamo/dynamo/deploy/dynamo/operator/internal/controller_common"
commonController "github.com/ai-dynamo/dynamo/deploy/dynamo/operator/internal/controller_common"
......@@ -60,10 +61,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
......@@ -161,7 +160,7 @@ func (r *DynamoComponentDeploymentReconciler) Reconcile(ctx context.Context, req
Message: "Starting to reconcile DynamoComponentDeployment",
},
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentFound,
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentReady,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "Starting to reconcile DynamoComponentDeployment",
......@@ -191,135 +190,48 @@ func (r *DynamoComponentDeploymentReconciler) Reconcile(ctx context.Context, req
}
}()
dynamoComponentFoundCondition := meta.FindStatusCondition(dynamoComponentDeployment.Status.Conditions, v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentFound)
if dynamoComponentFoundCondition != nil && dynamoComponentFoundCondition.Status == metav1.ConditionUnknown {
logs.Info(fmt.Sprintf("Getting Dynamo Component %s", dynamoComponentDeployment.Spec.DynamoComponent))
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeNormal, "GetDynamoComponent", "Getting Dynamo Component %s", dynamoComponentDeployment.Spec.DynamoComponent)
}
dynamoComponentRequest := &v1alpha1.DynamoComponentRequest{}
// retrieve the dynamo component
dynamoComponentCR := &v1alpha1.DynamoComponent{}
err = r.Get(ctx, types.NamespacedName{
Namespace: dynamoComponentDeployment.Namespace,
Name: getK8sName(dynamoComponentDeployment.Spec.DynamoComponent),
}, dynamoComponentCR)
dynamoComponentIsNotFound := k8serrors.IsNotFound(err)
if err != nil && !dynamoComponentIsNotFound {
err = errors.Wrapf(err, "get DynamoComponent %s/%s", dynamoComponentDeployment.Namespace, dynamoComponentDeployment.Spec.DynamoComponent)
err = r.Get(ctx, types.NamespacedName{Name: getK8sName(dynamoComponentDeployment.Spec.DynamoComponent), Namespace: dynamoComponentDeployment.Namespace}, dynamoComponentCR)
if err != nil {
logs.Error(err, "Failed to get DynamoComponent")
return
}
if dynamoComponentIsNotFound {
if dynamoComponentFoundCondition != nil && dynamoComponentFoundCondition.Status == metav1.ConditionUnknown {
logs.Info(fmt.Sprintf("DynamoComponent %s not found", dynamoComponentDeployment.Spec.DynamoComponent))
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeNormal, "GetDynamoComponent", "DynamoComponent %s not found", dynamoComponentDeployment.Spec.DynamoComponent)
}
// check if the component is ready
if dynamoComponentCR.IsReady() {
logs.Info(fmt.Sprintf("DynamoComponent %s ready", dynamoComponentDeployment.Spec.DynamoComponent))
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeNormal, "GetDynamoComponent", "DynamoComponent %s is ready", dynamoComponentDeployment.Spec.DynamoComponent)
dynamoComponentDeployment, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentFound,
Status: metav1.ConditionFalse,
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentReady,
Status: metav1.ConditionTrue,
Reason: "Reconciling",
Message: "DynamoComponent not found",
Message: "DynamoComponent is ready",
},
)
if err != nil {
return
}
dynamoComponentRequestFoundCondition := meta.FindStatusCondition(dynamoComponentDeployment.Status.Conditions, v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentRequestFound)
if dynamoComponentRequestFoundCondition == nil || dynamoComponentRequestFoundCondition.Status != metav1.ConditionUnknown {
dynamoComponentDeployment, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentRequestFound,
Status: metav1.ConditionUnknown,
Reason: "Reconciling",
Message: "DynamoComponent not found",
},
)
if err != nil {
return
}
}
if dynamoComponentRequestFoundCondition != nil && dynamoComponentRequestFoundCondition.Status == metav1.ConditionUnknown {
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeNormal, "GetDynamoComponentRequest", "Getting DynamoComponentRequest %s", dynamoComponentDeployment.Spec.DynamoComponent)
}
err = r.Get(ctx, types.NamespacedName{
Namespace: dynamoComponentDeployment.Namespace,
Name: getK8sName(dynamoComponentDeployment.Spec.DynamoComponent),
}, dynamoComponentRequest)
if err != nil {
err = errors.Wrapf(err, "get DynamoComponentRequest %s/%s", dynamoComponentDeployment.Namespace, dynamoComponentDeployment.Spec.DynamoComponent)
dynamoComponentDeployment, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentFound,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: err.Error(),
},
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentRequestFound,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: err.Error(),
},
)
if err != nil {
return
}
}
if dynamoComponentRequestFoundCondition != nil && dynamoComponentRequestFoundCondition.Status == metav1.ConditionUnknown {
logs.Info(fmt.Sprintf("DynamoComponentRequest %s found", dynamoComponentDeployment.Spec.DynamoComponent))
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeNormal, "GetDynamoComponentRequest", "DynamoComponentRequest %s is found and waiting for its dynamoComponent to be provided", dynamoComponentDeployment.Spec.DynamoComponent)
}
dynamoComponentDeployment, err = r.setStatusConditions(ctx, req,
} else {
logs.Info(fmt.Sprintf("DynamoComponent %s not ready", dynamoComponentDeployment.Spec.DynamoComponent))
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeWarning, "GetDynamoComponent", "DynamoComponent %s is not ready", dynamoComponentDeployment.Spec.DynamoComponent)
_, err_ := r.setStatusConditions(ctx, req,
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentRequestFound,
Status: metav1.ConditionTrue,
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentReady,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: "DynamoComponent not found",
Message: "DynamoComponent not ready",
},
)
if err != nil {
return
}
dynamoComponentRequestAvailableCondition := meta.FindStatusCondition(dynamoComponentRequest.Status.Conditions, v1alpha1.DynamoGraphDeploymentConditionTypeAvailable)
if dynamoComponentRequestAvailableCondition != nil && dynamoComponentRequestAvailableCondition.Status == metav1.ConditionFalse {
err = errors.Errorf("DynamoComponentRequest %s/%s is not available: %s", dynamoComponentRequest.Namespace, dynamoComponentRequest.Name, dynamoComponentRequestAvailableCondition.Message)
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeWarning, "GetDynamoComponentRequest", err.Error())
_, err_ := r.setStatusConditions(ctx, req,
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentFound,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: err.Error(),
},
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeAvailable,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: err.Error(),
},
)
if err_ != nil {
err = err_
return
}
return
}
return
} else {
if dynamoComponentFoundCondition != nil && dynamoComponentFoundCondition.Status != metav1.ConditionTrue {
logs.Info(fmt.Sprintf("DynamoComponent %s found", dynamoComponentDeployment.Spec.DynamoComponent))
r.Recorder.Eventf(dynamoComponentDeployment, corev1.EventTypeNormal, "GetDynamoComponent", "DynamoComponent %s is found", dynamoComponentDeployment.Spec.DynamoComponent)
}
dynamoComponentDeployment, err = r.setStatusConditions(ctx, req,
metav1.Condition{
Type: v1alpha1.DynamoGraphDeploymentConditionTypeDynamoComponentFound,
Status: metav1.ConditionTrue,
Type: v1alpha1.DynamoGraphDeploymentConditionTypeAvailable,
Status: metav1.ConditionFalse,
Reason: "Reconciling",
Message: "DynamoComponent found",
Message: "DynamoComponent not ready",
},
)
if err != nil {
return
}
err = err_
return
}
modified := false
......@@ -1291,7 +1203,10 @@ func (r *DynamoComponentDeploymentReconciler) generatePodTemplateSpec(ctx contex
})
}
imageName := opt.dynamoComponent.Spec.Image
imageName := opt.dynamoComponent.GetImage()
if imageName == "" {
return nil, errors.Errorf("image is not ready for component %s", opt.dynamoComponent.Name)
}
var securityContext *corev1.SecurityContext
var mainContainerSecurityContext *corev1.SecurityContext
......@@ -1417,7 +1332,17 @@ func (r *DynamoComponentDeploymentReconciler) generatePodTemplateSpec(ctx contex
Volumes: volumes,
}
podSpec.ImagePullSecrets = opt.dynamoComponent.Spec.ImagePullSecrets
podSpec.ImagePullSecrets = []corev1.LocalObjectReference{
{
Name: config.GetDockerRegistryConfig().SecretName,
},
}
if opt.dynamoComponent.Spec.DockerConfigJSONSecretName != "" {
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, corev1.LocalObjectReference{
Name: opt.dynamoComponent.Spec.DockerConfigJSONSecretName,
})
}
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, opt.dynamoComponent.Spec.ImagePullSecrets...)
extraPodMetadata := opt.dynamoComponentDeployment.Spec.ExtraPodMetadata
......@@ -1669,68 +1594,7 @@ func (r *DynamoComponentDeploymentReconciler) SetupWithManager(mgr ctrl.Manager)
Owns(&corev1.Service{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&networkingv1.Ingress{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&corev1.PersistentVolumeClaim{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Watches(&v1alpha1.DynamoComponentRequest{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, dynamoComponentRequest client.Object) []reconcile.Request {
reqs := make([]reconcile.Request, 0)
logs := log.Log.WithValues("func", "Watches", "kind", "DynamoComponentRequest", "name", dynamoComponentRequest.GetName(), "namespace", dynamoComponentRequest.GetNamespace())
logs.Info("Triggering reconciliation for DynamoComponentRequest", "DynamoComponentRequestName", dynamoComponentRequest.GetName(), "Namespace", dynamoComponentRequest.GetNamespace())
dynamoComponent := &v1alpha1.DynamoComponent{}
err := r.Get(context.Background(), types.NamespacedName{
Name: dynamoComponentRequest.GetName(),
Namespace: dynamoComponentRequest.GetNamespace(),
}, dynamoComponent)
dynamoComponentIsNotFound := k8serrors.IsNotFound(err)
if err != nil && !dynamoComponentIsNotFound {
logs.Info("Failed to get DynamoComponent", "name", dynamoComponentRequest.GetName(), "namespace", dynamoComponentRequest.GetNamespace(), "error", err)
return reqs
}
if !dynamoComponentIsNotFound {
logs.Info("DynamoComponent found, skipping enqueue as it's already present", "DynamoComponentName", dynamoComponentRequest.GetName())
return reqs
}
dynamoComponentDeployments := &v1alpha1.DynamoComponentDeploymentList{}
err = r.List(context.Background(), dynamoComponentDeployments, &client.ListOptions{
Namespace: dynamoComponentRequest.GetNamespace(),
})
if err != nil {
logs.Info("Failed to list DynamoComponentDeployments", "Namespace", dynamoComponentRequest.GetNamespace(), "error", err)
return reqs
}
for _, dynamoComponentDeployment := range dynamoComponentDeployments.Items {
dynamoComponentDeployment := dynamoComponentDeployment
if getK8sName(dynamoComponentDeployment.Spec.DynamoComponent) == dynamoComponentRequest.GetName() {
reqs = append(reqs, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(&dynamoComponentDeployment),
})
}
}
// Log the list of DynamoComponentDeployments being enqueued for reconciliation
logs.Info("Enqueuing DynamoComponentDeployments for reconciliation", "ReconcileRequests", reqs)
return reqs
})).WithEventFilter(controller_common.EphemeralDeploymentEventFilter(r.Config)).
Watches(&v1alpha1.DynamoComponent{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, dynamoComponent client.Object) []reconcile.Request {
logs := log.Log.WithValues("func", "Watches", "kind", "DynamoComponent", "name", dynamoComponent.GetName(), "namespace", dynamoComponent.GetNamespace())
logs.Info("Triggering reconciliation for DynamoComponent", "DynamoComponentName", dynamoComponent.GetName(), "Namespace", dynamoComponent.GetNamespace())
dynamoComponentDeployments := &v1alpha1.DynamoComponentDeploymentList{}
err := r.List(context.Background(), dynamoComponentDeployments, &client.ListOptions{
Namespace: dynamoComponent.GetNamespace(),
})
if err != nil {
logs.Info("Failed to list DynamoComponentDeployments", "Namespace", dynamoComponent.GetNamespace(), "error", err)
return []reconcile.Request{}
}
reqs := make([]reconcile.Request, 0)
for _, dynamoComponentDeployment := range dynamoComponentDeployments.Items {
dynamoComponentDeployment := dynamoComponentDeployment
if getK8sName(dynamoComponentDeployment.Spec.DynamoComponent) == dynamoComponent.GetName() {
reqs = append(reqs, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(&dynamoComponentDeployment),
})
}
}
// Log the list of DynamoComponentDeployments being enqueued for reconciliation
logs.Info("Enqueuing DynamoComponentDeployments for reconciliation", "ReconcileRequests", reqs)
return reqs
}))
WithEventFilter(controller_common.EphemeralDeploymentEventFilter(r.Config))
if r.UseVirtualService {
m.Owns(&networkingv1beta1.VirtualService{}, builder.WithPredicates(predicate.GenerationChangedPredicate{}))
......
......@@ -20,13 +20,11 @@ package controller
import (
"context"
"fmt"
"strings"
"dario.cat/mergo"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
......@@ -156,26 +154,33 @@ func (r *DynamoGraphDeploymentReconciler) Reconcile(ctx context.Context, req ctr
}
}
// reconcile the dynamoComponentRequest
// reconcile the dynamoComponent
// for now we use the same component for all the services and we differentiate them by the service name when launching the component
dynamoComponentRequest := &nvidiacomv1alpha1.DynamoComponentRequest{
dynamoComponent := &nvidiacomv1alpha1.DynamoComponent{
ObjectMeta: metav1.ObjectMeta{
Name: strings.ReplaceAll(dynamoDeployment.Spec.DynamoGraph, ":", "--"),
Name: getK8sName(dynamoDeployment.Spec.DynamoGraph),
Namespace: dynamoDeployment.Namespace,
},
Spec: nvidiacomv1alpha1.DynamoComponentRequestSpec{
Spec: nvidiacomv1alpha1.DynamoComponentSpec{
DynamoComponent: dynamoDeployment.Spec.DynamoGraph,
},
}
if err := ctrl.SetControllerReference(dynamoDeployment, dynamoComponentRequest, r.Scheme); err != nil {
reason = "failed_to_set_the_controller_reference_for_the_DynamoComponentRequest"
if err := ctrl.SetControllerReference(dynamoDeployment, dynamoComponent, r.Scheme); err != nil {
reason = "failed_to_set_the_controller_reference_for_the_DynamoComponent"
return ctrl.Result{}, err
}
_, err = commonController.SyncResource(ctx, r.Client, dynamoComponentRequest, types.NamespacedName{Name: dynamoComponentRequest.Name, Namespace: dynamoComponentRequest.Namespace}, false)
dynamoComponent, err = commonController.SyncResource(ctx, r.Client, dynamoComponent, false)
if err != nil {
reason = "failed_to_sync_the_DynamoComponentRequest"
reason = "failed_to_sync_the_DynamoComponent"
return ctrl.Result{}, err
}
if !dynamoComponent.IsReady() {
logger.Info("The DynamoComponent is not ready")
reason = "dynamoComponent_is_not_ready"
message = "The DynamoComponent is not ready"
readyStatus = metav1.ConditionFalse
return ctrl.Result{}, nil
}
notReadyDeployments := []string{}
// reconcile the dynamoComponentsDeployments
......@@ -185,7 +190,7 @@ func (r *DynamoGraphDeploymentReconciler) Reconcile(ctx context.Context, req ctr
reason = "failed_to_set_the_controller_reference_for_the_DynamoComponentDeployment"
return ctrl.Result{}, err
}
dynamoComponentDeployment, err = commonController.SyncResource(ctx, r.Client, dynamoComponentDeployment, types.NamespacedName{Name: dynamoComponentDeployment.Name, Namespace: dynamoComponentDeployment.Namespace}, false)
dynamoComponentDeployment, err = commonController.SyncResource(ctx, r.Client, dynamoComponentDeployment, false)
if err != nil {
reason = "failed_to_sync_the_DynamoComponentDeployment"
return ctrl.Result{}, err
......@@ -276,6 +281,13 @@ func (r *DynamoGraphDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) err
UpdateFunc: func(de event.UpdateEvent) bool { return true },
GenericFunc: func(ge event.GenericEvent) bool { return true },
})).
Owns(&nvidiacomv1alpha1.DynamoComponent{}, builder.WithPredicates(predicate.Funcs{
// ignore creation cause we don't want to be called again after we create the deployment
CreateFunc: func(ce event.CreateEvent) bool { return false },
DeleteFunc: func(de event.DeleteEvent) bool { return true },
UpdateFunc: func(de event.UpdateEvent) bool { return true },
GenericFunc: func(ge event.GenericEvent) bool { return true },
})).
WithEventFilter(commonController.EphemeralDeploymentEventFilter(r.Config)).
Complete(r)
}
......@@ -41,7 +41,7 @@ type Resource interface {
SetSpec(spec any)
}
func SyncResource[T Resource](ctx context.Context, c client.Client, desired T, namespacedName types.NamespacedName, createOnly bool) (T, error) {
func SyncResource[T Resource](ctx context.Context, c client.Client, desired T, createOnly bool) (T, error) {
// Retrieve the GroupVersionKind (GVK) of the desired object
gvk, err := apiutil.GVKForObject(desired, c.Scheme())
if err != nil {
......@@ -59,6 +59,10 @@ func SyncResource[T Resource](ctx context.Context, c client.Client, desired T, n
if !ok {
return desired, fmt.Errorf("failed to cast object to the expected type %T", desired)
}
namespacedName := types.NamespacedName{
Name: desired.GetName(),
Namespace: desired.GetNamespace(),
}
// Retrieve the existing resource
err = c.Get(ctx, namespacedName, current)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment