Skip to content

Commit 1f088f5

Browse files
committed
Add ability to allocate gpu, cpu, mem for jupyterlab workspace
1 parent 350b1bc commit 1f088f5

File tree

2 files changed

+70
-21
lines changed

2 files changed

+70
-21
lines changed

Kubernetes/README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,21 @@ The following options/arguments are optional:
109109

110110
```
111111
-c, --volume-snapshot-class= Kubernetes VolumeSnapshotClass to use when creating clone. If not specified, "csi-snapclass" will be used. Note: VolumeSnapshotClass must be configured to use Trident.
112+
-g, --nvidia-gpu= Number of NVIDIA GPUs to allocate to new JupyterLab workspace. Format: '1', '4', etc. If not specified, no GPUs will be allocated.
112113
-h, --help Print help text.
114+
-j, --source-workspace-name= Name of JupyterLab workspace to use as source for clone. Either -s/--source-snapshot-name or -j/--source-workspace-name must be specified.
115+
-m, --memory= Amount of memory to reserve for new JupyterLab workspace. Format: '1024Mi', '100Gi', '10Ti', etc. If not specified, no memory will be reserved.
113116
-n, --namespace= Kubernetes namespace that source workspace is located in. If not specified, namespace "default" will be used.
117+
-p, --cpu= Number of CPUs to reserve for new JupyterLab workspace. Format: '0.5', '1', etc. If not specified, no CPUs will be reserved.
114118
-s, --source-snapshot-name= Name of Kubernetes VolumeSnapshot to use as source for clone. Either -s/--source-snapshot-name or -j/--source-workspace-name must be specified.
115-
-j, --source-workspace-name= Name of JupyterLab workspace to use as source for clone. Either -s/--source-snapshot-name or -j/--source-workspace-name must be specified.
116119
```
117120

118121
##### Example Usage
119122

120-
Near-instantaneously create a new JupyterLab workspace, named 'project1-experiment3', that is an exact copy of the current contents of existing JupyterLab workspace 'project1' in namespace 'default'.
123+
Near-instantaneously create a new JupyterLab workspace, named 'project1-experiment3', that is an exact copy of the current contents of existing JupyterLab workspace 'project1' in namespace 'default'. Allocate 2 NVIDIA GPUs to the new workspace.
121124

122125
```sh
123-
./ntap_dsutil_k8s.py clone jupyterlab --new-workspace-name=project1-experiment3 --source-workspace-name=project1
126+
./ntap_dsutil_k8s.py clone jupyterlab --new-workspace-name=project1-experiment3 --source-workspace-name=project1 --nvidia-gpu=2
124127
Creating new JupyterLab workspace 'project1-experiment3' from source workspace 'project1' in namespace 'default'...
125128
126129
Creating new VolumeSnapshot 'ntap-dsutil.for-clone.20210315185504' for source PVC 'ntap-dsutil-jupyterlab-project1' in namespace 'default' to use as source for clone...
@@ -196,17 +199,20 @@ The following options/arguments are optional:
196199

197200
```
198201
-c, --storage-class= Kubernetes StorageClass to use when provisioning backing volume for new workspace. If not specified, default StorageClass will be used. Note: StorageClass must be configured to use Trident.
202+
-g, --nvidia-gpu= Number of NVIDIA GPUs to allocate to JupyterLab workspace. Format: '1', '4', etc. If not specified, no GPUs will be allocated.
199203
-h, --help Print help text.
200204
-i, --image= Container image to use when creating workspace. If not specified, "jupyter/tensorflow-notebook" will be used.
205+
-m, --memory= Amount of memory to reserve for JupyterLab workspace. Format: '1024Mi', '100Gi', '10Ti', etc. If not specified, no memory will be reserved.
201206
-n, --namespace= Kubernetes namespace to create new workspace in. If not specified, workspace will be created in namespace "default".
207+
-p, --cpu= Number of CPUs to reserve for JupyterLab workspace. Format: '0.5', '1', etc. If not specified, no CPUs will be reserved.
202208
```
203209

204210
##### Example Usage
205211

206-
Provision a new JupyterLab workspace named 'mike' of size 10GB in namespace 'default'.
212+
Provision a new JupyterLab workspace named 'mike' of size 10GB in namespace 'default'. Allocate 1 NVIDIA GPU to the new workspace.
207213

208214
```sh
209-
./ntap_dsutil_k8s.py create jupyterlab --workspace-name=mike --size=10Gi
215+
./ntap_dsutil_k8s.py create jupyterlab --workspace-name=mike --size=10Gi --nvidia-gpu=1
210216
Set workspace password (this password will be required in order to access the workspace):
211217
Re-enter password:
212218
@@ -813,6 +819,9 @@ def cloneJupyterLab(
813819
newWorkspacePassword: str = None, # Workspace password (this password will be required in order to access the workspace). If not specified, you will be prompted to enter a password via the console.
814820
volumeSnapshotClass: str = "csi-snapclass", # Kubernetes VolumeSnapshotClass to use when creating clone. If not specified, "csi-snapclass" will be used. Note: VolumeSnapshotClass must be configured to use Trident.
815821
namespace: str = "default", # Kubernetes namespace that source workspace is located in. If not specified, namespace "default" will be used.
822+
requestCpu: str = None, # Number of CPUs to reserve for new JupyterLab workspace. Format: '0.5', '1', etc. If not specified, no CPUs will be reserved.
823+
requestMemory: str = None, # Amount of memory to reserve for newe JupyterLab workspace. Format: '1024Mi', '100Gi', '10Ti', etc. If not specified, no memory will be reserved.
824+
requestNvidiaGpu: str = None, # Number of NVIDIA GPUs to allocate to new JupyterLab workspace. Format: '1', '4', etc. If not specified, no GPUs will be allocated.
816825
printOutput: bool = False # Denotes whether or not to print messages to the console during execution.
817826
) :
818827
```
@@ -848,6 +857,9 @@ def createJupyterLab(
848857
namespace: str = "default", # Kubernetes namespace to create new workspace in. If not specified, workspace will be created in namespace "default".
849858
workspacePassword: str = None, # Workspace password (this password will be required in order to access the workspace). If not specified, you will be prompted to enter a password via the console.
850859
workspaceImage: str = "jupyter/tensorflow-notebook", # Container image to use when creating workspace. If not specified, "jupyter/tensorflow-notebook" will be used.
860+
requestCpu: str = None, # Number of CPUs to reserve for JupyterLab workspace. Format: '0.5', '1', etc. If not specified, no CPUs will be reserved.
861+
requestMemory: str = None, # Amount of memory to reserve for JupyterLab workspace. Format: '1024Mi', '100Gi', '10Ti', etc. If not specified, no memory will be reserved.
862+
requestNvidiaGpu: str = None, # Number of NVIDIA GPUs to allocate to JupyterLab workspace. Format: '1', '4', etc. If not specified, no GPUs will be allocated.
851863
printOutput: bool = False # Denotes whether or not to print messages to the console during execution.
852864
) -> str :
853865
```

Kubernetes/ntap_dsutil_k8s.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ def jupyterLabDeployment(workspaceName: str) -> str :
598598
## Functions relating to JupyterLab workspaces
599599

600600
# Function for retrieving JupyterLab access url
601-
def retrieveJupyterLabURL(workspaceName: str, printOutput: bool = False) -> str :
601+
def retrieveJupyterLabURL(workspaceName: str, namespace: str = "default", printOutput: bool = False) -> str :
602602
# Retrieve kubeconfig
603603
try :
604604
loadKubeConfig()
@@ -707,7 +707,7 @@ def retrieveImageForJupyterLabDeployment(workspaceName: str, namespace: str = "d
707707

708708

709709
## Function for creating a new JupyterLab workspace
710-
def createJupyterLab(workspaceName: str, workspaceSize: str, storageClass: str = None, namespace: str = "default", workspacePassword: str = None, workspaceImage: str = "jupyter/tensorflow-notebook", printOutput: bool = False, pvcAlreadyExists: bool = False, labels: dict = None) -> str :
710+
def createJupyterLab(workspaceName: str, workspaceSize: str, storageClass: str = None, namespace: str = "default", workspacePassword: str = None, workspaceImage: str = "jupyter/tensorflow-notebook", requestCpu: str = None, requestMemory: str = None, requestNvidiaGpu: str = None, printOutput: bool = False, pvcAlreadyExists: bool = False, labels: dict = None) -> str :
711711
# Retrieve kubeconfig
712712
try :
713713
loadKubeConfig()
@@ -838,14 +838,27 @@ def createJupyterLab(workspaceName: str, workspaceSize: str, storageClass: str =
838838
name = "workspace",
839839
mount_path = "/home/jovyan"
840840
)
841-
]
841+
],
842+
resources = {
843+
"limits": dict(),
844+
"requests": dict()
845+
}
842846
)
843847
]
844848
)
845849
)
846850
)
847851
)
848852

853+
# Apply resource requests
854+
if requestCpu :
855+
deployment.spec.template.spec.containers[0].resources["requests"]["cpu"] = requestCpu
856+
if requestMemory :
857+
deployment.spec.template.spec.containers[0].resources["requests"]["memory"] = requestMemory
858+
if requestNvidiaGpu :
859+
deployment.spec.template.spec.containers[0].resources["requests"]["nvidia.com/gpu"] = requestNvidiaGpu
860+
deployment.spec.template.spec.containers[0].resources["limits"]["nvidia.com/gpu"] = requestNvidiaGpu
861+
849862
# Create deployment
850863
if printOutput :
851864
print("\nCreating Deployment '" + jupyterLabDeployment(workspaceName=workspaceName) + "' in namespace '" + namespace + "'.")
@@ -868,7 +881,7 @@ def createJupyterLab(workspaceName: str, workspaceSize: str, storageClass: str =
868881

869882
# Step 4 - Retrieve access URL
870883
try :
871-
url = retrieveJupyterLabURL(workspaceName=workspaceName, printOutput=printOutput)
884+
url = retrieveJupyterLabURL(workspaceName=workspaceName, namespace=namespace, printOutput=printOutput)
872885
except APIConnectionError as err :
873886
if printOutput :
874887
print("Aborting workspace creation...")
@@ -967,7 +980,7 @@ def listJupyterLabs(namespace: str = "default", printOutput: bool = False) -> li
967980
workspaceDict["StorageClass"] = ""
968981

969982
# Retrieve access URL
970-
workspaceDict["Access URL"] = retrieveJupyterLabURL(workspaceName=workspaceName, printOutput=printOutput)
983+
workspaceDict["Access URL"] = retrieveJupyterLabURL(workspaceName=workspaceName, namespace=namespace, printOutput=printOutput)
971984

972985
# Retrieve clone details
973986
try :
@@ -1062,7 +1075,7 @@ def restoreJupyterLabSnapshot(snapshotName: str = None, namespace: str = "defaul
10621075

10631076

10641077
## Function for cloning a JupyterLab workspace
1065-
def cloneJupyterLab(newWorkspaceName: str, sourceWorkspaceName: str, sourceSnapshotName: str = None, newWorkspacePassword: str = None, volumeSnapshotClass: str = "csi-snapclass", namespace: str = "default", printOutput: bool = False) :
1078+
def cloneJupyterLab(newWorkspaceName: str, sourceWorkspaceName: str, sourceSnapshotName: str = None, newWorkspacePassword: str = None, volumeSnapshotClass: str = "csi-snapclass", namespace: str = "default", requestCpu: str = None, requestMemory: str = None, requestNvidiaGpu: str = None, printOutput: bool = False) :
10661079
# Determine source PVC details
10671080
if sourceSnapshotName :
10681081
sourcePvcName, workspaceSize = retrieveSourceVolumeDetailsForVolumeSnapshot(snapshotName=sourceSnapshotName, namespace=namespace, printOutput=printOutput)
@@ -1093,7 +1106,7 @@ def cloneJupyterLab(newWorkspaceName: str, sourceWorkspaceName: str, sourceSnaps
10931106

10941107
# Create new workspace
10951108
print()
1096-
createJupyterLab(workspaceName=newWorkspaceName, workspaceSize=workspaceSize, namespace=namespace, workspacePassword=newWorkspacePassword, workspaceImage=sourceWorkspaceImage, printOutput=printOutput, pvcAlreadyExists=True, labels=labels)
1109+
createJupyterLab(workspaceName=newWorkspaceName, workspaceSize=workspaceSize, namespace=namespace, workspacePassword=newWorkspacePassword, workspaceImage=sourceWorkspaceImage, requestCpu=requestCpu, requestMemory=requestMemory, requestNvidiaGpu=requestNvidiaGpu, printOutput=printOutput, pvcAlreadyExists=True, labels=labels)
10971110

10981111
if printOutput :
10991112
print("JupyterLab workspace successfully cloned.")
@@ -1143,14 +1156,17 @@ def cloneJupyterLab(newWorkspaceName: str, sourceWorkspaceName: str, sourceSnaps
11431156
11441157
Optional Options/Arguments:
11451158
\t-c, --volume-snapshot-class=\tKubernetes VolumeSnapshotClass to use when creating clone. If not specified, "csi-snapclass" will be used. Note: VolumeSnapshotClass must be configured to use Trident.
1159+
\t-g, --nvidia-gpu=\t\tNumber of NVIDIA GPUs to allocate to new JupyterLab workspace. Format: '1', '4', etc. If not specified, no GPUs will be allocated.
11461160
\t-h, --help\t\t\tPrint help text.
1161+
\t-j, --source-workspace-name=\tName of JupyterLab workspace to use as source for clone. Either -s/--source-snapshot-name or -j/--source-workspace-name must be specified.
1162+
\t-m, --memory=\t\t\tAmount of memory to reserve for new JupyterLab workspace. Format: '1024Mi', '100Gi', '10Ti', etc. If not specified, no memory will be reserved.
11471163
\t-n, --namespace=\t\tKubernetes namespace that source workspace is located in. If not specified, namespace "default" will be used.
1164+
\t-p, --cpu=\t\t\tNumber of CPUs to reserve for new JupyterLab workspace. Format: '0.5', '1', etc. If not specified, no CPUs will be reserved.
11481165
\t-s, --source-snapshot-name=\tName of Kubernetes VolumeSnapshot to use as source for clone. Either -s/--source-snapshot-name or -j/--source-workspace-name must be specified.
1149-
\t-j, --source-workspace-name=\tName of JupyterLab workspace to use as source for clone. Either -s/--source-snapshot-name or -j/--source-workspace-name must be specified.
11501166
11511167
Examples:
1152-
\t./ntap_dsutil.py clone jupyterlab --new-workspace-name=project1-experiment1 --source-workspace-name=project1
1153-
\t./ntap_dsutil.py clone jupyterlab -w project2-mike -s project2-snap1 -n team1
1168+
\t./ntap_dsutil.py clone jupyterlab --new-workspace-name=project1-experiment1 --source-workspace-name=project1 --nvidia-gpu=1
1169+
\t./ntap_dsutil.py clone jupyterlab -w project2-mike -s project2-snap1 -n team1 -g 1 -p 0.5 -m 1Gi
11541170
'''
11551171
helpTextCloneVolume = '''
11561172
Command: clone volume
@@ -1184,13 +1200,16 @@ def cloneJupyterLab(newWorkspaceName: str, sourceWorkspaceName: str, sourceSnaps
11841200
11851201
Optional Options/Arguments:
11861202
\t-c, --storage-class=\tKubernetes StorageClass to use when provisioning backing volume for new workspace. If not specified, default StorageClass will be used. Note: StorageClass must be configured to use Trident.
1203+
\t-g, --nvidia-gpu=\tNumber of NVIDIA GPUs to allocate to JupyterLab workspace. Format: '1', '4', etc. If not specified, no GPUs will be allocated.
11871204
\t-h, --help\t\tPrint help text.
11881205
\t-i, --image=\t\tContainer image to use when creating workspace. If not specified, "jupyter/tensorflow-notebook" will be used.
1206+
\t-m, --memory=\t\tAmount of memory to reserve for JupyterLab workspace. Format: '1024Mi', '100Gi', '10Ti', etc. If not specified, no memory will be reserved.
11891207
\t-n, --namespace=\tKubernetes namespace to create new workspace in. If not specified, workspace will be created in namespace "default".
1208+
\t-p, --cpu=\t\tNumber of CPUs to reserve for JupyterLab workspace. Format: '0.5', '1', etc. If not specified, no CPUs will be reserved.
11901209
11911210
Examples:
1192-
\t./ntap_dsutil_k8s.py create jupyterlab --workspace-name=mike --size=10Gi
1193-
\t./ntap_dsutil_k8s.py create jupyterlab -n dst-test -w dave -i jupyter/scipy-notebook:latest -s 2Ti -c ontap-flexgroup
1211+
\t./ntap_dsutil_k8s.py create jupyterlab --workspace-name=mike --size=10Gi --nvidia-gpu=2
1212+
\t./ntap_dsutil_k8s.py create jupyterlab -n dst-test -w dave -i jupyter/scipy-notebook:latest -s 2Ti -c ontap-flexgroup -g 1 -p 0.5 -m 1Gi
11941213
'''
11951214
helpTextCreateJupyterLabSnapshot = '''
11961215
Command: create jupyterlab-snapshot
@@ -1500,10 +1519,13 @@ def getTarget(args: list) -> str:
15001519
sourceSnapshotName = None
15011520
volumeSnapshotClass = "csi-snapclass"
15021521
namespace = "default"
1522+
requestNvidiaGpu = None
1523+
requestMemory = None
1524+
requestCpu = None
15031525

15041526
# Get command line options
15051527
try :
1506-
opts, args = getopt.getopt(sys.argv[3:], "hw:c:n:s:j:", ["help", "new-workspace-name=", "volume-snapshot-class=", "namespace=", "source-snapshot-name=", "source-workspace-name="])
1528+
opts, args = getopt.getopt(sys.argv[3:], "hw:c:n:s:j:g:m:p:", ["help", "new-workspace-name=", "volume-snapshot-class=", "namespace=", "source-snapshot-name=", "source-workspace-name=", "nvidia-gpu=", "memory=", "cpu="])
15071529
except :
15081530
handleInvalidCommand(helpText=helpTextCloneJupyterLab, invalidOptArg=True)
15091531

@@ -1522,6 +1544,12 @@ def getTarget(args: list) -> str:
15221544
sourceSnapshotName = arg
15231545
elif opt in ("-j", "--source-workspace-name") :
15241546
sourceWorkspaceName = arg
1547+
elif opt in ("-g", "--nvidia-gpu") :
1548+
requestNvidiaGpu = arg
1549+
elif opt in ("-m", "--memory") :
1550+
requestMemory = arg
1551+
elif opt in ("-p", "--cpu") :
1552+
requestCpu = arg
15251553

15261554
# Check for required options
15271555
if not newWorkspaceName or (not sourceSnapshotName and not sourceWorkspaceName) :
@@ -1532,7 +1560,7 @@ def getTarget(args: list) -> str:
15321560

15331561
# Clone volume
15341562
try :
1535-
cloneJupyterLab(newWorkspaceName=newWorkspaceName, sourceWorkspaceName=sourceWorkspaceName, sourceSnapshotName=sourceSnapshotName, volumeSnapshotClass=volumeSnapshotClass, namespace=namespace, printOutput=True)
1563+
cloneJupyterLab(newWorkspaceName=newWorkspaceName, sourceWorkspaceName=sourceWorkspaceName, sourceSnapshotName=sourceSnapshotName, volumeSnapshotClass=volumeSnapshotClass, namespace=namespace, requestCpu=requestCpu, requestMemory=requestMemory, requestNvidiaGpu=requestNvidiaGpu, printOutput=True)
15361564
except (InvalidConfigError, APIConnectionError) :
15371565
sys.exit(1)
15381566

@@ -1622,10 +1650,13 @@ def getTarget(args: list) -> str:
16221650
namespace = "default"
16231651
storageClass = None
16241652
workspaceImage = "jupyter/scipy-notebook:latest"
1653+
requestNvidiaGpu = None
1654+
requestMemory = None
1655+
requestCpu = None
16251656

16261657
# Get command line options
16271658
try :
1628-
opts, args = getopt.getopt(sys.argv[3:], "hw:s:n:c:i:", ["help", "workspace-name=", "size=", "namespace=", "storage-class=", "image="])
1659+
opts, args = getopt.getopt(sys.argv[3:], "hw:s:n:c:i:g:m:p:", ["help", "workspace-name=", "size=", "namespace=", "storage-class=", "image=", "nvidia-gpu=", "memory=", "cpu="])
16291660
except :
16301661
handleInvalidCommand(helpText=helpTextCreateJupyterLab, invalidOptArg=True)
16311662

@@ -1644,14 +1675,20 @@ def getTarget(args: list) -> str:
16441675
storageClass = arg
16451676
elif opt in ("-i", "--image") :
16461677
workspaceImage = arg
1678+
elif opt in ("-g", "--nvidia-gpu") :
1679+
requestNvidiaGpu = arg
1680+
elif opt in ("-m", "--memory") :
1681+
requestMemory = arg
1682+
elif opt in ("-p", "--cpu") :
1683+
requestCpu = arg
16471684

16481685
# Check for required options
16491686
if not workspaceName or not workspaceSize :
16501687
handleInvalidCommand(helpText=helpTextCreateJupyterLab, invalidOptArg=True)
16511688

16521689
# Create JupyterLab workspace
16531690
try :
1654-
createJupyterLab(workspaceName=workspaceName, workspaceSize=workspaceSize, storageClass=storageClass, namespace=namespace, workspaceImage=workspaceImage, printOutput=True)
1691+
createJupyterLab(workspaceName=workspaceName, workspaceSize=workspaceSize, storageClass=storageClass, namespace=namespace, workspaceImage=workspaceImage, requestCpu=requestCpu, requestMemory=requestMemory, requestNvidiaGpu=requestNvidiaGpu, printOutput=True)
16551692
except (InvalidConfigError, APIConnectionError) :
16561693
sys.exit(1)
16571694

0 commit comments

Comments
 (0)